Skip to content

feat(security)(jetstream): optional opt-in Content-Security-Policy header (CONSOLE_CSP)#5688

Merged
norman-abramovitz merged 2 commits into
developfrom
fix/csp-header
Jul 24, 2026
Merged

feat(security)(jetstream): optional opt-in Content-Security-Policy header (CONSOLE_CSP)#5688
norman-abramovitz merged 2 commits into
developfrom
fix/csp-header

Conversation

@cweibel

@cweibel cweibel commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Problem

Jetstream serves no Content-Security-Policy header. OWASP ZAP (and other scanners) flag this as a medium-severity finding (CWE-693) — CSP is a defense-in-depth layer against XSS and data-injection. The backend already sets X-Frame-Options: SAMEORIGIN via Echo's Secure middleware, so CSP is a natural addition right beside it.

Change

Add a CSP through the existing middleware.SecureWithConfig, gated on a new CONSOLE_CSP env var. It is opt-in — with CONSOLE_CSP unset, no header is emitted, so an upgrade never changes a deployment's response headers (or risks breaking asset loading) without an explicit opt-in.

CONSOLE_CSP values:

  • unset / off / none / false / disabled → no header (default; preserves current behavior)
  • default / on → the built-in defaultCSPPolicy
  • any other value → used verbatim as the policy string

Built-in default policy:

default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' data: https://fonts.gstatic.com;
img-src 'self' data:;
connect-src 'self';
worker-src 'self' blob:;
frame-ancestors 'self';
base-uri 'self';
form-action 'self'

Rationale per allowance:

  • 'unsafe-inline' styles — Angular and the index.html loading splash inject inline <style>; Google Fonts stylesheet allowed.
  • data: / fonts.gstatic.com fonts, data: images — inlined icons + Google Fonts.
  • connect-src 'self' — same-origin also covers the backend log/stream wss:// sockets on the HTTPS page (no bare ws:/wss: wildcard, which scanners flag as overly permissive).
  • worker-src blob: — the Monaco editor spins up its language web-workers from blob URLs.
  • frame-ancestors 'self' mirrors the existing X-Frame-Options: SAMEORIGIN.

No unsafe-eval — AOT production builds don't need it.

Files

  • main.godefaultCSPPolicy const; wire ContentSecurityPolicy into the Secure middleware; opt-in resolution in loadPortalConfig.
  • api/structs.go — new CSPPolicy field (CONSOLE_CSP).
  • main_test.go — tests for unset (no header), default/on, custom-verbatim, and explicit-off cases.

Verification

$ go build ./...        # rc=0
$ go vet .              # rc=0
$ go test -run TestLoadPortalConfig -count=1 .
ok  github.com/cloudfoundry/stratos/src/jetstream

Validated live against a cloud.gov dev deployment with CONSOLE_CSP=default:

$ curl -sSI https://<dev-host>/ | grep -i content-security-policy
content-security-policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data:; connect-src 'self'; worker-src 'self' blob:; frame-ancestors 'self'; base-uri 'self'; form-action 'self'

OWASP ZAP full active scans across iterations confirmed:

  • the original "CSP Header Not Set" (Medium) is resolved, and
  • the initial "CSP: Wildcard Directive" finding (from a first-cut connect-src ... ws: wss:) is resolved by tightening connect-src to 'self'.

Known limitation

ZAP still reports CSP: style-src unsafe-inline. Removing 'unsafe-inline' from style-src requires nonce/hash support wired through Angular's inline-style injection (and the index.html loading splash) — a larger change than this PR. Documented here as a known, framework-imposed limitation; operators who need a stricter policy can supply their own via CONSOLE_CSP.


Implemented with AI assistance (OpenCode); reviewed by a human contributor. Commits are DCO signed-off.

cweibel added 2 commits July 23, 2026 13:37
Jetstream serves no Content-Security-Policy, which OWASP ZAP and other
scanners flag as a medium-severity finding (CWE-693). Add an OPT-IN CSP via
the existing Echo Secure middleware (alongside the current X-Frame-Options),
gated on a new CONSOLE_CSP env var so upgrades never change a deployment's
response headers without an explicit opt-in.

CONSOLE_CSP values:
  - unset / off / none / false / disabled -> no header (default; preserves
    existing behavior)
  - default / on                          -> built-in defaultCSPPolicy,
    scoped to what the Stratos SPA + Monaco editor need (self scripts;
    inline + Google Fonts styles; data:/Google font files; data: images;
    self + ws/wss connect; blob: workers; frame-ancestors self)
  - any other value                       -> used verbatim as the policy

Adds the CSPPolicy config field (CONSOLE_CSP) and unit tests covering the
unset (no header), default/on, custom-verbatim, and explicit-off cases.

Signed-off-by: Chris Weibel <christopher.weibel@gsa.gov>
The default CSP used 'connect-src self ws: wss:', which scanners flag as an
overly permissive wildcard (bare ws:/wss: allow WebSocket to any host). On an
HTTPS page, 'self' already permits same-origin wss:// connections, so the
backend log/stream sockets work without the wildcard. Tighten connect-src to
'self' only.

Signed-off-by: Chris Weibel <christopher.weibel@gsa.gov>
@cweibel cweibel mentioned this pull request Jul 23, 2026
@norman-abramovitz norman-abramovitz changed the title feat(jetstream): optional opt-in Content-Security-Policy header (CONSOLE_CSP) feat(security)(jetstream): optional opt-in Content-Security-Policy header (CONSOLE_CSP) Jul 23, 2026

@norman-abramovitz norman-abramovitz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - Discussed with cweibel and will merge as the foundation for improving security based upon ZAP findings.

@norman-abramovitz
norman-abramovitz merged commit 6780462 into develop Jul 24, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants