Skip to content

fix(security+tests): CSP/COOP/CORP headers, requireAdmin tests, secur…#966

Merged
ogazboiz merged 2 commits into
LabsCrypt:mainfrom
Drock0:security-and-testing-fixes
Jun 30, 2026
Merged

fix(security+tests): CSP/COOP/CORP headers, requireAdmin tests, secur…#966
ogazboiz merged 2 commits into
LabsCrypt:mainfrom
Drock0:security-and-testing-fixes

Conversation

@Drock0

@Drock0 Drock0 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

…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-fixes branch. 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.ts

The 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-Policies and conditionally HSTS.

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-origin
  • Cross-Origin-Resource-Policy: same-origin

The isProduction constant was also swapped for a live process.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:

  1. Normal response headers — verifies 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 that x-powered-by is absent.
  2. HSTS gate — asserts Strict-Transport-Security is absent when NODE_ENV=development and
    present with max-age=31536000; includeSubDomains when NODE_ENV=production.
  3. Swagger UI — asserts GET /api-docs/ returns 200 text/html and carries the CSP header.

Issue #823requireAdmin unit tests

File: backend/tests/auth.test.ts

Three new tests in a dedicated Auth Middleware (requireAdmin) describe block:

Test Expected result
test_admin_middleware_rejects_non_admin_token Valid JWT for a non-admin key → 403 Forbidden
test_admin_middleware_accepts_admin_token Valid JWT matching ADMIN_PUBLIC_KEY200 (next() called)
test_admin_middleware_fails_closed_when_key_unset ADMIN_PUBLIC_KEY unset, valid JWT → 403 Forbidden (fail-closed)

Each test spins up a minimal express app with a single GET /test-admin route guarded by
requireAdmin and asserts the response code and body shape.
ADMIN_PUBLIC_KEY is restored after each test via afterEach.


Issue #825 — Authenticated & scoped GET /v1/events

File: backend/src/routes/v1/events.routes.ts

GET / was unauthenticated and accepted any arbitrary address query param, exposing
any wallet's full event history. Changes:

  • Added requireAuth as the first middleware on the route.
  • After auth, the handler reads (req as AuthenticatedRequest).user.publicKey and compares it
    to the supplied address parameter.
  • If they differ, the handler returns 403 Forbidden with "You can only view your own event history".
  • Import of AuthenticatedRequest type 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.ts

Added a comment above the ownership-scoping block documenting that SSE subscriptions and
GET /v1/events now share identical authentication and scoping semantics.

File: backend/tests/integration/events-list.test.ts

  • All existing requests updated to include a valid Authorization: Bearer <token> header
    (token is signed for ADDR using the test JWT_SECRET).
  • Two new test cases:
    • rejects requests missing authentication401
    • rejects requests with mismatched authenticated user and address query403

File: backend/tests/auth.test.ts

Three new tests in GET /v1/events (authenticated & scoped):

Test Expected result
test_events_endpoint_rejects_unauthenticated No token → 401
test_events_endpoint_allows_authenticated_matching_address Token matches address → 200
test_events_endpoint_rejects_authenticated_mismatched_address Token mismatches address → 403

Module-level vi.mock stubs for prisma, sseService and redis were added to
auth.test.ts so the SSE subscribe route resolves immediately (closes the SSE response
via res.end() inside addClient) rather than hanging indefinitely.


Test Results

npx vitest run tests/auth.test.ts tests/security-headers.test.ts tests/integration/events-list.test.ts --coverage.enabled=false

 Test Files  3 passed (3)
      Tests  26 passed (26)
   Duration  3.42s

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

File Change type
backend/src/app.ts Modified — added CSP, COOP, CORP; dynamic HSTS check
backend/src/routes/v1/events.routes.ts Modified — added requireAuth + address scoping
backend/src/controllers/sse.controller.ts Modified — added alignment comment
backend/tests/auth.test.ts Modified — added requireAdmin + events scoping tests; improved mocks
backend/tests/security-headers.test.ts New — header regression tests
backend/tests/integration/events-list.test.ts Modified — added auth headers + new test cases

…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.
@ogazboiz ogazboiz merged commit d9d4f00 into LabsCrypt:main Jun 30, 2026
6 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment