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.
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.)
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.
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.
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).
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
Plan for this issue. Scope is SecurityHeaders() in internal/middleware/middleware.go and its test only (issue 94 also edits this file; templates are out of scope here).
Adding three response headers:
HSTS: Strict-Transport-Security: max-age=31536000; includeSubDomains, emitted unconditionally. pixa always sits behind a TLS-terminating proxy, and browsers ignore an HSTS header received over plaintext (RFC 6797 §8.1), so it never lies about the connection. Emitting unconditionally also avoids reading a forwarded-proto header, which is issue 94's trusted-proxy territory.
CSP: default-src 'self' with frame-ancestors 'none' (the primary clickjacking control, X-Frame-Options: DENY staying as the legacy fallback). The generator page carries inline onclick handlers and the Tailwind asset injects a runtime <style> element, so script-src and style-src need 'unsafe-inline' to keep those pages working; this is documented in the code and PR body. Removing the two inline handlers so 'unsafe-inline' can be dropped is a template change beyond this issue's file scope — flagged as a follow-up rather than done as a drive-by.
Permissions-Policy: denies browser features pixa does not use (camera, microphone, geolocation, and others).
TDD: a new test asserting the three headers, committed first; existing tests untouched.
model: claude-opus-4-8
Plan for this issue. Scope is `SecurityHeaders()` in `internal/middleware/middleware.go` and its test only (issue 94 also edits this file; templates are out of scope here).
Adding three response headers:
- **HSTS**: `Strict-Transport-Security: max-age=31536000; includeSubDomains`, emitted unconditionally. pixa always sits behind a TLS-terminating proxy, and browsers ignore an HSTS header received over plaintext (RFC 6797 §8.1), so it never lies about the connection. Emitting unconditionally also avoids reading a forwarded-proto header, which is issue 94's trusted-proxy territory.
- **CSP**: `default-src 'self'` with `frame-ancestors 'none'` (the primary clickjacking control, `X-Frame-Options: DENY` staying as the legacy fallback). The generator page carries inline `onclick` handlers and the Tailwind asset injects a runtime `<style>` element, so `script-src` and `style-src` need `'unsafe-inline'` to keep those pages working; this is documented in the code and PR body. Removing the two inline handlers so `'unsafe-inline'` can be dropped is a template change beyond this issue's file scope — flagged as a follow-up rather than done as a drive-by.
- **Permissions-Policy**: denies browser features pixa does not use (camera, microphone, geolocation, and others).
TDD: a new test asserting the three headers, committed first; existing tests untouched.
model: claude-opus-4-8
Opened #121 (base next). SecurityHeaders() now also emits Strict-Transport-Security (one year, includeSubDomains, unconditional — browsers ignore HSTS over plaintext, so no forwarded-proto header is trusted), a Content-Security-Policy (default-src 'self', frame-ancestors 'none'; 'unsafe-inline' on script-src/style-src for the generator's inline handlers and Tailwind's runtime style block, no 'unsafe-eval'), and a Permissions-Policy denying features pixa does not use. Ran the server in a debug container and loaded the real login and authenticated generator pages under the new CSP; both render. Dropping 'unsafe-inline' needs template edits, out of scope for this issue.
Model: opus-4-8
Opened https://git.eeqj.de/sneak/pixa/pulls/121 (base `next`). `SecurityHeaders()` now also emits `Strict-Transport-Security` (one year, `includeSubDomains`, unconditional — browsers ignore HSTS over plaintext, so no forwarded-proto header is trusted), a `Content-Security-Policy` (`default-src 'self'`, `frame-ancestors 'none'`; `'unsafe-inline'` on `script-src`/`style-src` for the generator's inline handlers and Tailwind's runtime style block, no `'unsafe-eval'`), and a `Permissions-Policy` denying features pixa does not use. Ran the server in a debug container and loaded the real login and authenticated generator pages under the new CSP; both render. Dropping `'unsafe-inline'` needs template edits, out of scope for this issue.
Model: opus-4-8
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
From the audit against the canonical
~/dev/prompts/prompts/REPO_POLICIES.md. Independently verified againstmainat61f42e6. (The repo's ownREPO_POLICIES.mdis byte-identical to canonical, sha256117dde7f148ed3cd693b333312f6345a0a0ee84fadbbe5cca559ca6fed4a1775, so this audit ran against the current standard.)REPO_POLICIES.mdrequires, explicitly gated on tagging 1.0:> Security headers on every response:
Strict-Transport-Security(HSTS) withmax-ageof at least one year andincludeSubDomains.Content-Security-Policy(CSP) with a restrictive default policy (default-src 'self'as a baseline)...Permissions-Policyrestricting access to browser features the application does not useSecurityHeaders()(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 forStrict-Transport|Content-Security-Policy|Permissions-Policyacrossinternal/andcmd/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 prefersframe-ancestorsas the primary clickjacking control, withX-Frame-Optionsas the legacy fallback; only the fallback exists today.Definition of done
SecurityHeaders()additionally sets:Strict-Transport-Security: max-age=31536000; includeSubDomainsContent-Security-Policywithdefault-src 'self'andframe-ancestors 'none', tightened around the Tailwind asset. Ifunsafe-inlineproves 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.Permissions-Policydenying browser features pixa does not use (camera, microphone, geolocation, etc.)internal/middlewareasserting each header is present on a representative response (that package currently has only two tests — see the middleware coverage issue).make checkgreen.Priority
Blocks 1.0.0 per the policy's own wording. Note
internal/middleware/middleware.gois not touched by either open PR, so this can proceed independently once the queue clears.Plan for this issue. Scope is
SecurityHeaders()ininternal/middleware/middleware.goand its test only (issue 94 also edits this file; templates are out of scope here).Adding three response headers:
Strict-Transport-Security: max-age=31536000; includeSubDomains, emitted unconditionally. pixa always sits behind a TLS-terminating proxy, and browsers ignore an HSTS header received over plaintext (RFC 6797 §8.1), so it never lies about the connection. Emitting unconditionally also avoids reading a forwarded-proto header, which is issue 94's trusted-proxy territory.default-src 'self'withframe-ancestors 'none'(the primary clickjacking control,X-Frame-Options: DENYstaying as the legacy fallback). The generator page carries inlineonclickhandlers and the Tailwind asset injects a runtime<style>element, soscript-srcandstyle-srcneed'unsafe-inline'to keep those pages working; this is documented in the code and PR body. Removing the two inline handlers so'unsafe-inline'can be dropped is a template change beyond this issue's file scope — flagged as a follow-up rather than done as a drive-by.TDD: a new test asserting the three headers, committed first; existing tests untouched.
model: claude-opus-4-8
Opened #121 (base
next).SecurityHeaders()now also emitsStrict-Transport-Security(one year,includeSubDomains, unconditional — browsers ignore HSTS over plaintext, so no forwarded-proto header is trusted), aContent-Security-Policy(default-src 'self',frame-ancestors 'none';'unsafe-inline'onscript-src/style-srcfor the generator's inline handlers and Tailwind's runtime style block, no'unsafe-eval'), and aPermissions-Policydenying features pixa does not use. Ran the server in a debug container and loaded the real login and authenticated generator pages under the new CSP; both render. Dropping'unsafe-inline'needs template edits, out of scope for this issue.Model: opus-4-8
clawbot referenced this issue2026-09-21 20:43:47 +02:00