Tech Story
As a platform engineer, I want the API to send standard security response headers and only accept requests from known origins so that common browser-based attacks (clickjacking, MIME sniffing, protocol downgrade) are mitigated by default.
Context
app.enableCors() with no options defaults to origin: '*', allowing any origin to make cross-origin requests. No security headers (Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security, etc.) are set anywhere. Both are table-stakes for any public-facing API.
Acceptance Criteria
Technical Elaboration
- Install
helmet; call app.use(helmet()) in main.ts
- Replace
app.enableCors() with:
app.enableCors({
origin: configService.get('ALLOWED_ORIGIN'),
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
});
- Helmet defaults cover most headers; no custom CSP needed at this stage
- CORS
origin must be a string/array, not *, when credentials: true (browsers reject credentialed requests to wildcard origins)
Notes
Tech Story
As a platform engineer, I want the API to send standard security response headers and only accept requests from known origins so that common browser-based attacks (clickjacking, MIME sniffing, protocol downgrade) are mitigated by default.
Context
app.enableCors()with no options defaults toorigin: '*', allowing any origin to make cross-origin requests. No security headers (Content-Security-Policy,X-Frame-Options,X-Content-Type-Options,Strict-Transport-Security, etc.) are set anywhere. Both are table-stakes for any public-facing API.Acceptance Criteria
helmet()applied globally inmain.tsbefore route handlersALLOWED_ORIGINenv var (required — app fails to start if missing)credentials: trueto support httpOnly cookie auth (see Tech Story: Switch auth tokens to httpOnly cookies #93)Strict-Transport-Securityheader present in production responsesX-Frame-Options: DENYpresent in all responsesX-Content-Type-Options: nosniffpresent in all responsesALLOWED_ORIGINdocumented in.env.exampleTechnical Elaboration
helmet; callapp.use(helmet())inmain.tsapp.enableCors()with:originmust be a string/array, not*, whencredentials: true(browsers reject credentialed requests to wildcard origins)Notes
credentials: trueis required for cookies to be sent cross-originALLOWED_ORIGIN=http://localhost:5173in.env