agentggagentgg
Back to all findings
MEDIUMconfirmedmissing-security-headersmissing-csp033938bf2f59

No Content-Security-Policy header set on HTML-serving app

The Express app serves HTML (Handlebars views and the Angular client) but only installs helmet.noSniff() and helmet.frameguard() piecemeal — never helmet() as a whole, never helmet.contentSecurityPolicy(), and never a manual Content-Security-Policy header, so responses to browsers have no CSP at all.

Fileserver.ts
Lines186194
Confidence
90%
File statusvalidated
Details

Helmet is intentionally NOT used in its default-everything form. Instead, only two sub-middlewares are installed:

/* Security middleware */
app.use(helmet.noSniff())
app.use(helmet.frameguard())
// app.use(helmet.xssFilter()); // = no protection from persisted XSS via RESTful API
app.disable('x-powered-by')
app.use(featurePolicy({
  features: {
    payment: ["'self'"]
  }
}))

There is no helmet.contentSecurityPolicy(...), no res.setHeader('Content-Security-Policy', ...), and no Content-Security-Policy-Report-Only anywhere in the file. The application clearly serves HTML — app.set('view engine', 'hbs') and app.use(express.static(path.resolve('frontend/dist/frontend'))) plus the Angular client served via serveAngularClient() — so the absence of CSP means the browser has zero declarative restriction on script sources, inline script execution, eval, framing children, or form-action targets. Any reflected/stored XSS in the rendered HTML (and the codebase deliberately serves user content like product reviews, memories, feedback, etc.) executes without the browser-side defense layer that CSP is meant to provide.

This directly matches true-positive criterion #1 from the rubric: the app serves HTML but has no CSP header configured.

Proof of concept
  1. Start the server and curl any HTML endpoint, e.g. curl -I http://localhost:3000/ or curl -I http://localhost:3000/profile.
  2. Observe the response headers: X-Frame-Options: SAMEORIGIN and X-Content-Type-Options: nosniff are present, but Content-Security-Policy is absent.
  3. Any XSS sink in the application (e.g. the product review or memory features) is therefore not blocked at the browser by CSP.
Impact

All browser users of the application. Without CSP, any successful HTML/JS injection escalates straight to full XSS — there is no allowlist for script-src, no objectSrc 'none', no frame-ancestors, no upgrade-insecure-requests. Attackers can also frame the page (X-Frame-Options is set so clickjacking is partially mitigated, but a modern frame-ancestors directive is missing entirely) and exfiltrate via arbitrary connect-src destinations.

Validation
confirmed

The middleware block only installs helmet.noSniff() and helmet.frameguard() and a narrow featurePolicy for payment; there is no helmet.contentSecurityPolicy() call and no manual Content-Security-Policy header set anywhere. Yet the app serves HTML via app.set('view engine', 'hbs'), express.static('frontend/dist/frontend'), and the catch-all serveAngularClient(), alongside user-controlled content (product reviews, memories, feedback). Treating this as a real production app per scope rules, the missing CSP means no browser-side allowlist for script-src/object-src/frame-ancestors, so any XSS sink escalates to full execution. Finding matches the described code and class.

CVSS 3.1
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N
Base score: 4.2 · MEDIUM

The Security middleware block at lines 186–194 installs only helmet.noSniff(), helmet.frameguard(), and featurePolicy(...), never helmet.contentSecurityPolicy(...) nor a manual Content-Security-Policy header, while the app serves HTML (app.set('view engine', 'hbs'), the Angular client via serveAngularClient(), and express.static('frontend/dist/frontend')). Missing CSP is a defense-in-depth gap and is not directly exploitable on its own — an attacker must chain it with a separate HTML/JS injection sink (e.g. the product review or memory features that render user content), so AC is High and UI is Required (victim must load the affected page). When such a sink fires, the browser executes script with no script-src/object-src/frame-ancestors restrictions, yielding limited (attacker-controlled but bounded) confidentiality and integrity impact within the same origin (Scope Unchanged); availability is unaffected by header absence alone.

References