Skip to content

fix(security): harden session, CORS, rate limiting, and cache isolation - #31

Open
lupita-hom wants to merge 1 commit into
mainfrom
security/auto-fix-2026-09-07
Open

fix(security): harden session, CORS, rate limiting, and cache isolation#31
lupita-hom wants to merge 1 commit into
mainfrom
security/auto-fix-2026-09-07

Conversation

@lupita-hom

Copy link
Copy Markdown
Collaborator

Automated security scan — 2026-09-07

Dependency audit

npm audit is clean on both workspaces (0 vulnerabilities, client and server). No npm audit fix changes were needed.

Code review findings (all fixed here)

Area Issue Fix
Session secret Production fell back to hardcoded dev-secret-change-in-production — forgeable session cookies Boot fails unless SESSION_SECRET >= 32 chars in production
Session fixation Session id reused across the OAuth privilege change req.session.regenerate() after token exchange; cookie cleared on logout
OAuth CSRF state compared with !==, not single-use, missing-value not rejected Constant-time compare, strict type check, single-use state
CORS Single hardcoded origin, all methods Validated ALLOWED_ORIGINS allowlist, methods restricted to GET/POST/OPTIONS
Rate limiting None — upstream GitHub token burnable, OAuth brute-forceable express-rate-limit: 600/15m API, 30/15m auth
CSP Disabled entirely Explicit policy without upgrade-insecure-requests, plus no-referrer + same-origin CORP
Body size Unbounded JSON/urlencoded bodies Capped at 100kb
Cache isolation Keys used an 8-char access-token substring — collision leaks another user's private repo/org data, and leaks token material into cache state SHA-256 derived per-user namespace applied to every repo/org key; cache capped at 5000 keys
Injection PR state query param interpolated into upstream GraphQL Allowlisted to OPEN/CLOSED/MERGED
Input bounds page, per_page, maxCommits, tzOffset unbounded Clamped to sane ranges
Cookie secure: false hardcoded, default connect.sid name Opt-in COOKIE_SECURE, renamed to gne.sid
Deps qs transitive Override pinned to >=6.16.0

trust proxy set to 1 so rate limiting and secure cookies see the real client IP behind one reverse proxy.

Validation

  • tsc --noEmit — clean
  • server production build — pass
  • client production build — pass
  • npm audit after changes — 0 vulnerabilities

Operator note

Two new optional env vars are documented in .env.example: ALLOWED_ORIGINS and COOKIE_SECURE. SESSION_SECRET is now mandatory in production — deployments without it will fail to start (intentionally).

🤖 Automated scheduled security scan

npm audit is clean on both client and server (0 vulnerabilities), but a
code review of the server surfaced several exploitable weaknesses:

- Session secret: production no longer falls back to a hardcoded
  'dev-secret-change-in-production'; boot fails unless SESSION_SECRET is
  >= 32 chars. A known secret allows forging session cookies.
- Session fixation: regenerate the session id after OAuth token exchange
  and clear the cookie on logout.
- OAuth CSRF: constant-time state comparison, reject missing/non-string
  state, and make state single-use to block callback replay.
- CORS: replace the single-origin value with a validated ALLOWED_ORIGINS
  allowlist and restrict methods.
- Rate limiting: add express-rate-limit (600/15m API, 30/15m auth) to
  protect the upstream GitHub token and slow OAuth brute force.
- CSP: restore an explicit policy (no upgrade-insecure-requests) plus
  no-referrer and same-origin CORP instead of disabling CSP outright.
- Body size: bound JSON/urlencoded bodies to 100kb.
- Cache poisoning / cross-user leak: derive a SHA-256 per-user cache
  namespace instead of using an 8-char access-token substring, and apply
  it to every repo/org cache key. Also cap cache size at 5000 keys.
- Injection: allowlist the PR 'state' value interpolated into the
  upstream GraphQL query.
- Input bounds: clamp all user-controlled pagination and maxCommits /
  tzOffset parameters.
- Cookie: opt-in Secure flag via COOKIE_SECURE, non-default cookie name.
- Deps: override qs to >=6.16.0; document ALLOWED_ORIGINS and
  COOKIE_SECURE in .env.example.

Validated with tsc --noEmit and full client + server production builds.
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