Repository navigation
fix(server): harden the production edge — framing headers, per-client sign-in limits, DB readiness - #163
Open
Ayush7614 wants to merge 1 commit into
Open
fix(server): harden the production edge — framing headers, per-client sign-in limits, DB readiness#163Ayush7614 wants to merge 1 commit into
Ayush7614 wants to merge 1 commit into
Conversation
… sign-in limits, DB readiness The API sent only nosniff/referrer/no-store while serving bearer-equivalent signed links (file bytes, browser preview/console) that users open in a browser: any third-party page could frame them and injected markup in HTML responses ran unsandboxed. The sign-in limiter was one process-global 30/minute counter, so a single hostile client could lock every legitimate user out. /api/health always answered ok, so deploys received traffic with the database unreachable. - Route-aware security headers (security-headers.ts): strict CSP for JSON/HTML, console allowances preserved plus frame-ancestors allowlist built from the configured clients (the web app iframes console and PDFs cross-origin, so DENY/SAMEORIGIN would break it), framing-only policy on byte responses, Permissions-Policy lockdown, HSTS when publicUrl is https. - Per-client sign-in budget (10/min via the existing rate-limit store/key helpers) replacing the global counter. - GET /api/ready probing SELECT 1 with 503 fail-closed; render.yaml now gates traffic on it. /api/health semantics unchanged. - 11 regression tests in tests/server-edge.test.ts.
This branch has not been deployed
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.
What changed
Three production gaps in the server edge, all verified against current
mainand covered by new tests. No existing issue or PR covers any of them (checked open issues/PRs and full-text search).1. Clickjacking / content-injection headers (
apps/server/src/security-headers.ts, new)The API sent only
nosniff/no-referrer/no-storewhile serving bearer-equivalent signed links (file bytes, browser preview/console) that users open in a browser: any third-party page could frame them, and injected markup in HTML responses ran unsandboxed.default-src 'none'; base-uri 'none'; form-action 'none') for JSON/OAuth/error responses.BrowserConsole.web.tsxiframes/api/browsers/:id/consoleandPdfReader.web.tsxiframes/api/files/:id/contentfrom the web-app origin — so a blanketDENY/SAMEORIGINwould break the app. Framing is instead restricted with aframe-ancestorsallowlist built from the configured clients; the console keeps its exact script/style allowances plus framing protection; byte responses (PDF/PNG) carry framing-only policy so plugin/image rendering is untouched.Permissions-Policylockdown on every response; HSTS only whenPUBLIC_API_URLis https (advertising it on plaintext would be a lie in local setups).Responsethat bypasses context headers, so it applies the policy explicitly.2. Per-client sign-in budget (
apps/server/src/app.ts,rate-limit.ts)The
/api/sessionlimiter was a single process-global 30/minute counter shared by every address (app.ts): one hostile client spamming wrong keys locked every legitimate user out, and the fixed window admitted bursts across the boundary. Replaced with a 10/minute bucket per client key reusing the existingcreateRateLimitStore/resolveClientKeyhelpers (sameTRUST_PROXYsemantics as the global limiter).3. Truthful readiness (
apps/server/src/app.ts,db.ts,render.yaml)/api/healthanswersok:trueunconditionally, so deploys receive traffic with Postgres unreachable. New unauthenticatedGET /api/readyprobesSELECT 1viaStore.ping()and fails closed (503{database:"down"});render.yamlnow gates traffic on it./api/healthsemantics unchanged.Verification
pnpm lint— passpnpm typecheck— passpnpm test— 494 pass, 0 fail (483 baseline + 11 new intests/server-edge.test.ts)pnpm build:server— passIntegration limits
Unit + in-process app tests only (in-memory PGlite, fixture PDF); no provider credentials, live account, or device builds needed.