fix(security+tests): CSP/COOP/CORP headers, requireAdmin tests, secur…#966
Merged
Merged
Conversation
…e GET /v1/events Closes LabsCrypt#821 - Add Content-Security-Policy, Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy headers to the hand-rolled security middleware. Replace static isProduction var with dynamic process.env.NODE_ENV check for HSTS so the production gate is testable. Swagger UI (/api-docs) verified to load under the new CSP. Closes LabsCrypt#822 - Add security-headers.test.ts asserting X-Content-Type-Options, X-Frame-Options, Referrer-Policy, CSP, COOP, CORP and absence of x-powered-by on every response. Assert HSTS only present when NODE_ENV=production. Assert Swagger UI page loads with CSP header. Closes LabsCrypt#823 - Add requireAdmin unit tests to auth.test.ts: - non-admin key JWT -> 403 Forbidden - admin key JWT -> 200 (next() called) - ADMIN_PUBLIC_KEY unset -> 403 (fail closed) Closes LabsCrypt#825 - Secure GET /v1/events by adding requireAuth middleware and enforcing that the queried address matches the authenticated user publicKey (mirrors SSE subscription scoping). Returns 403 if caller queries another wallet. Add comment in sse.controller.ts documenting the aligned semantics. Update events-list integration tests with Authorization headers and add new auth/scoping test cases.
| beforeEach(() => { | ||
| adminApp = express(); | ||
| adminApp.use(express.json()); | ||
| adminApp.get('/test-admin', requireAdmin, (_req: any, res: any) => { |
Global API responses now carry a strict CSP without unsafe-inline, removing the CodeQL high-severity XSS-via-CSP alert. The Swagger UI route (/api-docs) overrides the global CSP with the permissive version it needs to render inline scripts and styles correctly. Security-header tests updated to assert the strict policy on normal responses and the permissive policy on /api-docs.
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…e GET /v1/events
fix(security+tests): CSP/COOP/CORP headers, requireAdmin tests, secure GET /v1/events
Summary
This PR addresses four open issues with a single focused commit on the
security-and-testing-fixesbranch. All changes are backend-only and every new/modified test file passes cleanly.Issues Closed
Closes #821
Closes #822
Closes #823
Closes #825
Changes
Issue #821 — Add CSP, COOP & CORP security headers
File:
backend/src/app.tsThe hand-rolled security-header middleware block at lines 35–46 previously set:
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,X-DNS-Prefetch-Control,X-Download-Options,X-Permitted-Cross-Domain-Policiesand conditionallyHSTS.Three headers were missing. They are now added:
Content-Security-Policy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'"— permits Swagger UI to load inline scripts/styles while blocking everything else.
Cross-Origin-Opener-Policy: same-originCross-Origin-Resource-Policy: same-originThe
isProductionconstant was also swapped for a liveprocess.env.NODE_ENV === 'production'check inside the middleware so the HSTS gate is observable in tests without restarting the process.
Issue #822 — Security-header regression tests
File:
backend/tests/security-headers.test.ts(new)Three supertest cases asserting every response from the app carries the expected headers:
X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: no-referrer, the full CSP string,Cross-Origin-Opener-Policy: same-origin,Cross-Origin-Resource-Policy: same-origin, and thatx-powered-byis absent.Strict-Transport-Securityis absent whenNODE_ENV=developmentandpresent with
max-age=31536000; includeSubDomainswhenNODE_ENV=production.GET /api-docs/returns200 text/htmland carries the CSP header.Issue #823 —
requireAdminunit testsFile:
backend/tests/auth.test.tsThree new tests in a dedicated
Auth Middleware (requireAdmin)describe block:test_admin_middleware_rejects_non_admin_tokentest_admin_middleware_accepts_admin_tokenADMIN_PUBLIC_KEY→ 200 (next()called)test_admin_middleware_fails_closed_when_key_unsetADMIN_PUBLIC_KEYunset, valid JWT → 403 Forbidden (fail-closed)Each test spins up a minimal express app with a single
GET /test-adminroute guarded byrequireAdminand asserts the response code and body shape.ADMIN_PUBLIC_KEYis restored after each test viaafterEach.Issue #825 — Authenticated & scoped
GET /v1/eventsFile:
backend/src/routes/v1/events.routes.tsGET /was unauthenticated and accepted any arbitraryaddressquery param, exposingany wallet's full event history. Changes:
requireAuthas the first middleware on the route.(req as AuthenticatedRequest).user.publicKeyand compares itto the supplied
addressparameter.403 Forbiddenwith"You can only view your own event history".AuthenticatedRequesttype added.This aligns the REST history API with the SSE subscribe endpoint which already scoped
results to streams the caller owns.
File:
backend/src/controllers/sse.controller.tsAdded a comment above the ownership-scoping block documenting that SSE subscriptions and
GET /v1/eventsnow share identical authentication and scoping semantics.File:
backend/tests/integration/events-list.test.tsAuthorization: Bearer <token>header(token is signed for
ADDRusing the testJWT_SECRET).rejects requests missing authentication→ 401rejects requests with mismatched authenticated user and address query→ 403File:
backend/tests/auth.test.tsThree new tests in
GET /v1/events (authenticated & scoped):test_events_endpoint_rejects_unauthenticatedtest_events_endpoint_allows_authenticated_matching_addresstest_events_endpoint_rejects_authenticated_mismatched_addressModule-level
vi.mockstubs forprisma,sseServiceandrediswere added toauth.test.tsso the SSE subscribe route resolves immediately (closes the SSE responsevia
res.end()insideaddClient) rather than hanging indefinitely.Test Results
All 26 tests across the three directly-affected files pass cleanly.
Pre-existing failures (integration tests requiring a live DB/Redis) are unrelated to these changes and were failing before this branch.
Files Changed
backend/src/app.tsbackend/src/routes/v1/events.routes.tsrequireAuth+ address scopingbackend/src/controllers/sse.controller.tsbackend/tests/auth.test.tsrequireAdmin+ events scoping tests; improved mocksbackend/tests/security-headers.test.tsbackend/tests/integration/events-list.test.ts