Security audit fixes: 2 prod bugs + auth hardening + action SHA-pinning - #10
Merged
Conversation
…n failed deploy pull 1) session/clear set an expiring cookie without Secure; browsers reject that for a __Secure--prefixed name, so the prod session cookie never cleared and the /login<->/dashboard loop could recur (untested — e2e is HTTP dev). Now sets Secure to match the cookie's prefix. 2) remote-deploy.sh: a failed 'compose pull' fell through to up -d (no-op), health-checked the OLD app, and reported a green deploy on stale code. Now aborts on pull failure.
- Removed getValueFromCookie: a dead 'use server' action that returned ANY cookie incl. the HttpOnly session token (XSS->session-theft amplifier). - Allow-listed setValueToCookie against PREFERENCE_REGISTRY (was a generic same-origin cookie-write primitive). - Added a /sign-up/email rate limit (10/60s) — the global 100/10s was far too loose for account provisioning. - seed.ts fails closed: SEED_PASSWORD now required (no repo-visible 'NovaReview2026!' default); .env.example documents it. - next.config removeConsole keeps console.error in prod (server error visibility incl. the pg pool handler).
…ssions Supply-chain: every third-party action (checkout, setup-node, docker/*, appleboy/scp+ssh — the last two receive root VPS SSH secrets) pinned to its current tag's commit SHA with a trailing version comment, so a re-pointed tag can't inject code. Added top-level 'permissions: contents: read' to ci.yml (deploy.yml already scopes it).
CSP deferred pre-deadline (strict script-src/style-src risks breaking Next inline styles/scripts; no proven injection path today). GET /api/session/clear force-logout CSRF deferred as cosmetic (nuisance only). Both recorded as accepted findings with next steps.
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Applies the read-only security + logic/config audit findings (2 parallel auditors). No feature changes — hardening plus 2 real production-only bugs. CSP, logout-CSRF, and rate-limit
trustedProxiesare deferred (documented indocs/limitations.md).🔴 Production bugs (invisible until prod / an incident):
__Secure-session cookie now clears withSecure(api/session/clear). Browsers reject aSet-Cookiefor a__Secure--prefixed name withoutSecure, so in prod the stale cookie never cleared → the/login ↔ /dashboardloop this route exists to break could recur (secret rotation / DB reset). Untested before (e2e runs on HTTP dev).remote-deploy.shaborts on a faileddocker compose pull. Withoutset -e, a failed pull fell through toup -d(no-op), health-checked the OLD app, and reported a green deploy on stale code.🟠 Auth-surface hardening:
getValueFromCookie— a dead"use server"action that returned any cookie incl. the HttpOnly session token (XSS → session-theft amplifier).setValueToCookieagainstPREFERENCE_REGISTRY(was a generic same-origin cookie-write primitive)./sign-up/emailrate limit (10/60s) — the global 100/10s was far too loose for account provisioning.seed.tsfails closed —SEED_PASSWORDrequired, no repo-visibleNovaReview2026!default (.env.exampledocuments it).removeConsolekeepsconsole.errorin prod (server error visibility, incl. the pg pool handler).🔗 Supply-chain / CI:
deploy.yml+ci.ymlto its current tag's commit SHA (esp.appleboy/ssh-action+scp-action, which receive root VPS SSH secrets), with a trailing# vXcomment.ci.ymlgets a top-levelpermissions: contents: read.Deferred (in
docs/limitations.md): CSP (strictscript-src/style-srcrisks breaking Next inline styles/scripts right before the deadline — no proven injection path today), GET-logout CSRF (nuisance only), rate-limittrustedProxies.Test plan
commitsgate — 4 conventional commitsquality— lint +tsc+test:unit+ branding +builde2e— full Playwright incl. the security suite (verified 11/11 locally against these changes, in an isolated worktree)deploy.ymlruns only on push-main, so it's exercised on the next promotion.Notes
origin/develop(cf4058f) — no overlap with the parallel dashboard-data / UI-landing sessions.SEED_PASSWORDchange affects only fresh seeding; the live reviewer account already exists and is unaffected.