Skip to content

Server: add baseline security response headers - #187

Merged
testtest126 merged 1 commit into
mainfrom
server/security-headers
Jul 18, 2026
Merged

Server: add baseline security response headers#187
testtest126 merged 1 commit into
mainfrom
server/security-headers

Conversation

@testtest126

Copy link
Copy Markdown
Owner

Summary

Found during a repo-wide security audit (auth surface, rate limiter, secrets, dependencies, transport/headers, authorization). The server currently sends no hardening headers on any response — no CORS config (correct: no browser client exists, so same-origin policy is the right default and adding CORS would be a regression), but also nothing on X-Frame-Options, X-Content-Type-Options, Referrer-Policy, or HSTS.

This is a JSON API consumed only by the iOS app, so these headers are second-order hardening (they matter if a response body is ever rendered by a browser — an error page, a misconfigured proxy, a future web client) rather than a direct fix for an exploitable path. Cheap, zero behavior change for the app, worth doing.

What it does

Adds SecurityHeadersMiddleware (chess-server/Sources/App/Middleware/SecurityHeadersMiddleware.swift), applied to every response:

  • X-Frame-Options: DENY
  • X-Content-Type-Options: nosniff
  • Referrer-Policy: no-referrer
  • Strict-Transport-Security: max-age=31536000; includeSubDomains

Registration order matters and is covered by a test. Registered at .beginning so it wraps Vapor's default ErrorMiddleware rather than being wrapped by it. At the default .end position, an error thrown by routing or a handler (a 404, a 401, a 429 from the rate limiter, a 500) unwinds straight past the middleware without running its header-setting code — I hit this myself first (testHeadersAreAlsoPresentOnErrorResponses failed against the naive .end registration) and fixed it before opening this PR. Error responses are exactly the ones where these headers matter most, so this isn't a cosmetic detail.

Test plan

  • New SecurityHeadersMiddlewareTests: headers present on a normal 200 (/health) and on a 404 (/no-such-route).
  • Full suite: swift test --package-path chess-server91/91 pass locally (Xcode-beta toolchain).
  • swiftformat --lint clean on the changed files.

Security-sensitive?

No — this is response-header middleware, not auth/token/crypto/account-linking/session logic, so it's not gated behind CLAUDE.md rule 5's draft-until-APPROVE flow. Opened as a normal PR.

Found during a repo-wide security audit: the server sends no hardening
headers at all — no CORS config (fine, no browser client), but also nothing
on X-Frame-Options, X-Content-Type-Options, Referrer-Policy, or HSTS.

Adds SecurityHeadersMiddleware, applying X-Frame-Options: DENY,
X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, and
Strict-Transport-Security to every response. Registered at `.beginning` so
it wraps Vapor's default ErrorMiddleware rather than being wrapped by it —
at the default `.end` position an error thrown by routing or a handler
unwinds past the middleware without running its header-setting code, so
404/401/429/500 responses would ship without these headers. Caught by a
test that specifically checks a 404.

Not auth/token/crypto/session logic, so not gated behind the security-review
rule — pure response hardening with no behavior change for the iOS client
(which ignores headers it doesn't recognize).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@testtest126
testtest126 merged commit ae63128 into main Jul 18, 2026
9 checks passed
@testtest126
testtest126 deleted the server/security-headers branch July 18, 2026 20:23
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.

1 participant