StellarEduPay enforces a Content Security Policy on all HTTP responses to mitigate XSS attacks. The policy is applied at two layers: the Next.js frontend and the Express backend.
Without CSP, a successful XSS injection (e.g. a malicious student name rendered in the dashboard) can execute arbitrary JavaScript in the admin's browser, steal the JWT from localStorage, and exfiltrate school data. CSP prevents this by restricting which scripts, styles, and network destinations the browser will allow.
Applied to every HTML response via the Next.js headers() API:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self' https://horizon-testnet.stellar.org https://horizon.stellar.org;
object-src 'none';
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
| Directive | Value | Rationale |
|---|---|---|
default-src |
'self' |
Deny all unlisted resource types by default |
script-src |
'self' |
No inline scripts, no eval, no third-party JS |
style-src |
'self' |
No inline styles, no third-party CSS |
img-src |
'self' data: |
Allows inline SVG/base64 images used by the UI |
font-src |
'self' |
Self-hosted fonts only |
connect-src |
'self' https://horizon-testnet.stellar.org https://horizon.stellar.org |
Allows fetch/XHR to the backend API and both Stellar Horizon endpoints |
object-src |
'none' |
Blocks Flash and other plugins |
frame-ancestors |
'none' |
Prevents clickjacking (equivalent to X-Frame-Options: DENY) |
base-uri |
'self' |
Prevents base-tag hijacking |
form-action |
'self' |
Restricts form submissions to the same origin |
Additional security headers set alongside CSP:
| Header | Value |
|---|---|
X-Frame-Options |
DENY |
X-Content-Type-Options |
nosniff |
Referrer-Policy |
strict-origin-when-cross-origin |
The Express backend serves only JSON API responses — directives for scripts, styles, and images are irrelevant. Helmet is configured with a minimal policy appropriate for an API:
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'none'"],
frameAncestors: ["'none'"],
},
},
})default-src 'none' means the browser should load nothing from this origin as a document resource. frame-ancestors 'none' prevents the API responses from being embedded in frames.
The CSP configuration is covered by tests/csp.test.js, which verifies:
- The frontend
next.config.jsexports aheaders()function returning a catch-all entry with aContent-Security-Policyheader. - The frontend CSP includes
default-src 'self',script-src 'self',frame-ancestors 'none',object-src 'none', and the Stellar Horizonconnect-srcallowlist. - The frontend CSP does not contain
'unsafe-inline'or'unsafe-eval'. - The backend
app.jssetsdefaultSrc: ["'none'"]andframeAncestors: ["'none'"]and does not includescriptSrc,styleSrc,imgSrc,'unsafe-inline', or'unsafe-eval'.
Run the tests with:
npm test -- tests/csp.test.jsIf a new external service needs to be reachable from the frontend (e.g. a currency conversion API), add its origin to the connect-src directive in frontend/next.config.js and update the test in tests/csp.test.js accordingly.
Do not add 'unsafe-inline' or 'unsafe-eval' to script-src. If a third-party library requires inline scripts, use a nonce-based approach instead.