fix: move /docs route before express.static to fix CSP headers
All checks were successful
Promote to Production / Deploy to Production (push) Successful in 1m15s
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m8s

express.static was serving docs.html before the /docs route handler,
causing Helmet default CSP to be used instead of the custom Swagger UI CSP.
This blocked unsafe-eval and blob: workers needed by Swagger UI.
This commit is contained in:
OpenClaw Deployer 2026-02-18 13:51:35 +00:00
parent a45d7704ab
commit 97744897f0
2 changed files with 19 additions and 18 deletions

16
dist/index.js vendored
View file

@ -168,6 +168,14 @@ app.get("/favicon.ico", (_req, res) => {
res.setHeader('Cache-Control', 'public, max-age=604800'); res.setHeader('Cache-Control', 'public, max-age=604800');
res.sendFile(path.join(__dirname, "../public/favicon.svg")); res.sendFile(path.join(__dirname, "../public/favicon.svg"));
}); });
// Docs page (clean URL)
app.get("/docs", (_req, res) => {
// Swagger UI 5.x uses new Function() (via ajv) for JSON schema validation.
// Override helmet's default CSP to allow 'unsafe-eval' + blob: for Swagger UI.
res.setHeader("Content-Security-Policy", "default-src 'self';script-src 'self' 'unsafe-eval';style-src 'self' https: 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' https: data:;connect-src 'self';worker-src 'self' blob:;base-uri 'self';form-action 'self';frame-ancestors 'self';object-src 'none'");
res.setHeader('Cache-Control', 'public, max-age=86400');
res.sendFile(path.join(__dirname, "../public/docs.html"));
});
// Static asset cache headers middleware // Static asset cache headers middleware
app.use((req, res, next) => { app.use((req, res, next) => {
if (/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/.test(req.path)) { if (/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/.test(req.path)) {
@ -180,14 +188,6 @@ app.use(express.static(path.join(__dirname, "../public"), {
etag: true, etag: true,
cacheControl: false, cacheControl: false,
})); }));
// Docs page (clean URL)
app.get("/docs", (_req, res) => {
// Swagger UI 5.x uses new Function() (via ajv) for JSON schema validation.
// Override helmet's default CSP to allow 'unsafe-eval' + blob: for Swagger UI.
res.setHeader("Content-Security-Policy", "default-src 'self';script-src 'self' 'unsafe-eval';style-src 'self' https: 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' https: data:;connect-src 'self';worker-src 'self' blob:;base-uri 'self';form-action 'self';frame-ancestors 'self';object-src 'none'");
res.setHeader('Cache-Control', 'public, max-age=86400');
res.sendFile(path.join(__dirname, "../public/docs.html"));
});
// Legal pages (clean URLs) // Legal pages (clean URLs)
app.get("/impressum", (_req, res) => { app.get("/impressum", (_req, res) => {
res.setHeader('Cache-Control', 'public, max-age=86400'); res.setHeader('Cache-Control', 'public, max-age=86400');

View file

@ -184,6 +184,17 @@ app.get("/favicon.ico", (_req, res) => {
res.sendFile(path.join(__dirname, "../public/favicon.svg")); res.sendFile(path.join(__dirname, "../public/favicon.svg"));
}); });
// Docs page (clean URL)
app.get("/docs", (_req, res) => {
// Swagger UI 5.x uses new Function() (via ajv) for JSON schema validation.
// Override helmet's default CSP to allow 'unsafe-eval' + blob: for Swagger UI.
res.setHeader("Content-Security-Policy",
"default-src 'self';script-src 'self' 'unsafe-eval';style-src 'self' https: 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' https: data:;connect-src 'self';worker-src 'self' blob:;base-uri 'self';form-action 'self';frame-ancestors 'self';object-src 'none'"
);
res.setHeader('Cache-Control', 'public, max-age=86400');
res.sendFile(path.join(__dirname, "../public/docs.html"));
});
// Static asset cache headers middleware // Static asset cache headers middleware
app.use((req, res, next) => { app.use((req, res, next) => {
if (/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/.test(req.path)) { console.log("CACHE HIT:", req.path); if (/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/.test(req.path)) { console.log("CACHE HIT:", req.path);
@ -197,16 +208,6 @@ app.use(express.static(path.join(__dirname, "../public"), {
cacheControl: false, cacheControl: false,
})); }));
// Docs page (clean URL)
app.get("/docs", (_req, res) => {
// Swagger UI 5.x uses new Function() (via ajv) for JSON schema validation.
// Override helmet's default CSP to allow 'unsafe-eval' + blob: for Swagger UI.
res.setHeader("Content-Security-Policy",
"default-src 'self';script-src 'self' 'unsafe-eval';style-src 'self' https: 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' https: data:;connect-src 'self';worker-src 'self' blob:;base-uri 'self';form-action 'self';frame-ancestors 'self';object-src 'none'"
);
res.setHeader('Cache-Control', 'public, max-age=86400');
res.sendFile(path.join(__dirname, "../public/docs.html"));
});
// Legal pages (clean URLs) // Legal pages (clean URLs)
app.get("/impressum", (_req, res) => { app.get("/impressum", (_req, res) => {