Blocks 1.0: missing HSTS, CSP, and Permissions-Policy security headers #91

Open
opened 2026-08-09 07:02:29 +02:00 by clawbot · 0 comments
Collaborator

From the audit against the canonical ~/dev/prompts/prompts/REPO_POLICIES.md. Independently verified against main at 61f42e6. (The repo's own REPO_POLICIES.md is byte-identical to canonical, sha256 117dde7f148ed3cd693b333312f6345a0a0ee84fadbbe5cca559ca6fed4a1775, so this audit ran against the current standard.)

REPO_POLICIES.md requires, explicitly gated on tagging 1.0:

> Security headers on every response: Strict-Transport-Security (HSTS) with max-age of at least one year and includeSubDomains. Content-Security-Policy (CSP) with a restrictive default policy (default-src 'self' as a baseline)... Permissions-Policy restricting access to browser features the application does not use

SecurityHeaders() (internal/middleware/middleware.go:148-166) sets exactly four headers: X-Content-Type-Options, X-Frame-Options, Referrer-Policy, X-XSS-Protection. A repo-wide grep for Strict-Transport|Content-Security-Policy|Permissions-Policy across internal/ and cmd/ returns zero matches — verified.

CSP is not academic here: the repo serves HTML (internal/templates/login.html, internal/templates/generator.html) and loads a script asset (internal/static/tailwind.js). The policy also prefers frame-ancestors as the primary clickjacking control, with X-Frame-Options as the legacy fallback; only the fallback exists today.

Definition of done

  1. SecurityHeaders() additionally sets:
    • Strict-Transport-Security: max-age=31536000; includeSubDomains
    • a Content-Security-Policy with default-src 'self' and frame-ancestors 'none', tightened around the Tailwind asset. If unsafe-inline proves unavoidable, the policy requires documenting the reason — do that in a code comment and the PR body, and prefer moving inline styles/scripts to the served asset instead.
    • a Permissions-Policy denying browser features pixa does not use (camera, microphone, geolocation, etc.)
  2. Verify the CSP against the real login and generator pages, not just in a unit test — a CSP that breaks the UI is worse than none. State in the PR that the pages were loaded and rendered correctly.
  3. HSTS is only meaningful over TLS, and pixa listens plain HTTP behind a terminating proxy. Decide whether to emit it unconditionally or only when the request was forwarded as HTTPS, and say which and why. Do not emit a header that lies about the connection.
  4. Tests in internal/middleware asserting each header is present on a representative response (that package currently has only two tests — see the middleware coverage issue).
  5. make check green.

Priority

Blocks 1.0.0 per the policy's own wording. Note internal/middleware/middleware.go is not touched by either open PR, so this can proceed independently once the queue clears.

From the audit against the canonical `~/dev/prompts/prompts/REPO_POLICIES.md`. Independently verified against `main` at `61f42e6`. (The repo's own `REPO_POLICIES.md` is byte-identical to canonical, sha256 `117dde7f148ed3cd693b333312f6345a0a0ee84fadbbe5cca559ca6fed4a1775`, so this audit ran against the current standard.) `REPO_POLICIES.md` requires, explicitly gated on tagging 1.0: > **Security headers** on every response: `Strict-Transport-Security` (HSTS) with `max-age` of at least one year and `includeSubDomains`. `Content-Security-Policy` (CSP) with a restrictive default policy (`default-src 'self'` as a baseline)... `Permissions-Policy` restricting access to browser features the application does not use `SecurityHeaders()` (`internal/middleware/middleware.go:148-166`) sets exactly four headers: `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `X-XSS-Protection`. A repo-wide grep for `Strict-Transport|Content-Security-Policy|Permissions-Policy` across `internal/` and `cmd/` returns **zero matches** — verified. CSP is not academic here: the repo serves HTML (`internal/templates/login.html`, `internal/templates/generator.html`) and loads a script asset (`internal/static/tailwind.js`). The policy also prefers `frame-ancestors` as the primary clickjacking control, with `X-Frame-Options` as the legacy fallback; only the fallback exists today. ## Definition of done 1. `SecurityHeaders()` additionally sets: - `Strict-Transport-Security: max-age=31536000; includeSubDomains` - a `Content-Security-Policy` with `default-src 'self'` and `frame-ancestors 'none'`, tightened around the Tailwind asset. If `unsafe-inline` proves unavoidable, the policy requires documenting the reason — do that in a code comment and the PR body, and prefer moving inline styles/scripts to the served asset instead. - a `Permissions-Policy` denying browser features pixa does not use (camera, microphone, geolocation, etc.) 2. Verify the CSP against the real login and generator pages, not just in a unit test — a CSP that breaks the UI is worse than none. State in the PR that the pages were loaded and rendered correctly. 3. HSTS is only meaningful over TLS, and pixa listens plain HTTP behind a terminating proxy. Decide whether to emit it unconditionally or only when the request was forwarded as HTTPS, and say which and why. Do not emit a header that lies about the connection. 4. Tests in `internal/middleware` asserting each header is present on a representative response (that package currently has only two tests — see the middleware coverage issue). 5. `make check` green. ## Priority **Blocks 1.0.0** per the policy's own wording. Note `internal/middleware/middleware.go` is not touched by either open PR, so this can proceed independently once the queue clears.
clawbot added this to the 1.0.0 milestone 2026-08-09 07:02:29 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#91