Skip to content

security: M17 + L1 + L2 + L12 pre-launch follow-ups#58

Merged
harshitsinghbhandari merged 4 commits into
developfrom
security/m-l-followups
Jun 5, 2026
Merged

security: M17 + L1 + L2 + L12 pre-launch follow-ups#58
harshitsinghbhandari merged 4 commits into
developfrom
security/m-l-followups

Conversation

@harshitsinghbhandari

@harshitsinghbhandari harshitsinghbhandari commented Jun 5, 2026

Copy link
Copy Markdown
Member

Closes #49.

Four targeted fixes from the 2026-06-01 pre-launch audit. None are individually launch-blocking; bundled here so they don't fall out of memory before the June 7 milestone. Each finding is its own commit for review granularity.

M17 — signup_flags.thin_profile strengthened

lib/auth/soft-flag.ts

Before: the lone thin_profile signal only tripped when bio + email + followers were all weak simultaneously. A bot with 2 followers + a one-character bio walked through.

After: additive signals persisted to public.users.signup_flags:

  • young_account — GitHub account < 90 days old
  • low_repos — < 3 public repos
  • low_followers / low_following — each < 2
  • empty_bio / short_bio — empty or < 8 chars after trim
  • no_public_email — email unset on profile
  • thin_profile — kept verbatim for historical-row compatibility

Still soft signals only — the hard signup gate (30d age + ≥1 public repo) is unchanged.

Tests: 40-case table-driven coverage including malformed/null/future-dated created_at, bio thresholds, every signal in isolation, and combined real-world bot shapes.

L1 — postcss <8.5.10 (CVE-2026-41305)

package.json

Added pnpm.overrides pinning every transitive postcss to >=8.5.10. Lockfile regenerated. Next build still passes.

Before:

$ pnpm why postcss
...
next@16.2.6 → postcss 8.4.31  ← vulnerable

After:

next@16.2.6 → postcss 8.5.15
@tailwindcss/postcss@4.3.0 → postcss 8.5.15
vite → postcss 8.5.15

L2 — uuid <11.1.1 (CVE-2026-41907)

package.json, docs/security-audit-ignores.md

Documented an audit-ignore via pnpm.auditConfig.ignoreCves. The buffer-overflow only triggers when v3()/v5()/v6() are called with a caller-supplied buf; next-auth@4 only calls v4() with no buffer, so the vulnerable code paths are unreachable. Patching would force a breaking 8 → 11 major bump under next-auth and is deferred until the planned next-auth@5 migration. Full justification and revisit trigger live in the new doc.

L12 — E2E auth shim hardened

lib/auth.ts, playwright.config.ts, tests/e2e/seo.spec.ts, tests/unit/auth-e2e-shim.test.ts

Before: triple-gated (E2E_TEST_AUTH_USER_ID set + x-e2e-auth: 1 header + NODE_ENV !== 'production'). On a non-Vercel preview running pnpm dev that inherited E2E_TEST_AUTH_USER_ID by accident, the header alone could mint sessions for any user-id.

After: two additional gates:

  • ALLOW_E2E_AUTH === '1' — explicit opt-in flag. An ambient env-var leak alone can no longer enable the shim. Playwright's webServer.env sets it.
  • VERCEL_ENV !== 'production' — belt-and-braces refusal on Vercel production even if the flag somehow shipped.

Playwright previously pinned VERCEL_ENV=production to exercise the prod robots.txt branch in e2e. That pin is dropped (it now collides with the L12 reject). Prod robots behaviour is already fully covered in tests/unit/robots.test.ts; tests/e2e/seo.spec.ts now smoke-tests the route in whichever branch the runtime picks.

Tests: tests/unit/auth-e2e-shim.test.ts covers the full gate matrix — refuses without ALLOW_E2E_AUTH, refuses on production NODE_ENV, refuses on production VERCEL_ENV regardless of the flag, allows on VERCEL_ENV=preview, refuses without header.

Audit output diff

Before: 2 findings (postcss, uuid).

After:

$ pnpm audit --prod
1 vulnerabilities found
Severity: 1 moderate (1 ignored)
$ echo $?
0

The remaining moderate is the documented L2 uuid finding, marked ignored — non-blocking but visible.

Test plan

  • CI passes (typecheck + lint + 1138 unit + Playwright e2e)
  • pnpm audit --prod exits 0 with 1 moderate (1 ignored)
  • Local sanity-check Vercel preview deploy keeps existing auth flow working (shim should be off there — ALLOW_E2E_AUTH not set)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Expanded signup risk detection with additional signals including account age, bio quality, and repository activity metrics.
  • Bug Fixes

    • Improved security gating for test authentication mechanisms.
  • Documentation

    • Added comprehensive security audit documentation for managed vulnerabilities.
  • Tests

    • Enhanced test coverage for authentication and SEO functionality.
  • Chores

    • Added security audit ignore configuration and dependency management updates.

The original `thin_profile` heuristic only tripped when bio + email +
followers were all weak simultaneously — a bot with two followers and a
one-character bio walked straight through. Moderators only had a single
coarse signal to triage with.

Replace the lone signal with an additive bag persisted to
`public.users.signup_flags`: `young_account` (created_at < 90 days),
`low_repos` (< 3 public), `low_followers` and `low_following` (each
< 2), `empty_bio` / `short_bio` (< 8 chars after trim), and
`no_public_email`. The legacy `thin_profile` key stays so historical
rows remain comparable.

Closes the M17 follow-up from the 2026-06-01 audit (issue #49). None of
these flags BLOCK login — the hard signup gate still lives in
`callbacks.signIn`; flags here are soft moderator-triage signals only.

Tests: 40-case table-driven coverage of legacy compatibility, every new
signal, account-age edges (malformed/null/future-dated), bio thresholds,
and combined real-world bot shapes.
L1 — postcss <8.5.10 (CVE-2026-41305, moderate 6.1)
Add a pnpm override pinning every transitive postcss to ≥8.5.10. The
CVE is an XSS via unescaped `</style>` in stringify output and isn't
reachable from our code today (no user-supplied CSS), but pnpm audit
flags it so leaving it in place forces a `pnpm audit --prod` exception.
The override resolves every occurrence (next, tailwind, vite) to 8.5.15
without breaking the Next build (verified locally).

L2 — uuid <11.1.1 (CVE-2026-41907, moderate 7.5)
Document an audit-ignore via `pnpm.auditConfig.ignoreCves`. The
buffer-overflow only triggers when v3/v5/v6 are called with a caller-
supplied `buf` argument, which next-auth@4 never does (it calls v4()
with no buffer). Patching would force a breaking next-auth bump (4 → 5);
deferring until the planned next-auth v5 migration is cheaper and
strictly safe given the call-site analysis. The deferral, justification,
and revisit trigger live in `docs/security-audit-ignores.md`.

After both: `pnpm audit --prod` reports "1 moderate (1 ignored)" with
exit 0 — non-blocking but visible.

Refs the L1 + L2 follow-ups from issue #49.
…(L12)

The E2E shim in `getSession()` previously short-circuited on a triple
gate: `E2E_TEST_AUTH_USER_ID` set, `x-e2e-auth: 1` header present,
`NODE_ENV !== 'production'`. Vercel sets `NODE_ENV=production` for
preview deploys so that triple gate was safe on Vercel, but a non-Vercel
preview (Render/Fly/local-over-a-tunnel) running `pnpm dev` and
inheriting `E2E_TEST_AUTH_USER_ID` by accident became a session-forgery
primitive: any caller setting the header could mint sessions for any
user-id.

Add two new gates:

  - `ALLOW_E2E_AUTH === '1'` — explicit opt-in. Production / preview
    deployments will never set this, so an ambient
    `E2E_TEST_AUTH_USER_ID` leak alone can no longer enable the shim.
    Playwright's `webServer.env` now sets it.
  - `VERCEL_ENV !== 'production'` — belt-and-braces. Even if
    `ALLOW_E2E_AUTH=1` somehow ships to a Vercel production deployment,
    the shim still refuses.

Playwright config previously pinned `VERCEL_ENV=production` so e2e
tests exercised the production robots.txt branch. The new shim refuses
under that condition, so the pin is dropped. Production-mode robots
behaviour is already exhaustively covered by `tests/unit/robots.test.ts`;
`tests/e2e/seo.spec.ts` now smoke-tests the route returns a parseable
robots file in whichever branch the runtime picks.

Tests: `tests/unit/auth-e2e-shim.test.ts` covers the full gate matrix —
refuses when `ALLOW_E2E_AUTH` is unset/wrong-value, refuses on
`NODE_ENV=production`, refuses on `VERCEL_ENV=production` regardless of
the flag, allows on `VERCEL_ENV=preview`, refuses without the header.

Refs the L12 follow-up from issue #49.
@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentlab-in Ready Ready Preview, Comment Jun 5, 2026 6:53pm

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 01c50dde-7eb9-45be-945e-3d1fefefe6e6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/m-l-followups

Comment @coderabbitai help to get the list of available commands and usage tips.

Polish from the PR #58 self-review pass. No threat-model change; each
hunk tightens an invariant that was technically safe but easy for a
future contributor to break.

- `lib/auth.ts`: replace `e2eShimEnabled()` (bool) + a deferred
  `process.env.E2E_TEST_AUTH_USER_ID as string` cast with
  `readE2EShimUserId()` (returns the id-or-null). The env read now
  happens BEFORE `await readE2EHeader()`, so a hypothetical concurrent
  handler that mutates the env mid-await cannot turn the synthetic
  session into `{ id: undefined }`. New test in
  `auth-e2e-shim.test.ts` simulates that race by mutating the env from
  inside the header-mock and asserting the captured id wins.
- `lib/auth.ts`: add a NOTE comment over the synthetic-session return
  documenting that bypassing the per-request ban check is intentional
  AND only acceptable because the five gates collectively bar
  production. Reduces the chance a future contributor wires the branch
  to real user-id values and turns it into a banned-user impersonation
  primitive.
- `lib/auth/soft-flag.ts`: add `clock_skew` signal for future-dated
  `createdAt` (either real skew or tampered payload — either way
  worth moderator attention). `young_account` no longer fires for
  skewed values; the two signals are mutually exclusive.
- `lib/auth/soft-flag.ts`: `normalizeBio` now strips ZWSP/ZWNJ/ZWJ/BOM
  before measuring, so a bot can't dodge `empty_bio` (or pad past the
  `short_bio` threshold) with invisible whitespace.

Tests: +8 cases across the two suites. Full unit suite 1146 ✓.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
package.json (1)

65-67: ⚡ Quick win

Bound the PostCSS override to major 8.

pnpm-lock.yaml currently resolves postcss only to 8.5.15 (no postcss majors > 8), but the override value ">=8.5.10" can drift to PostCSS 9+ on future lockfile refreshes; prefer ^8.5.10 to keep major 8.

Suggested change
   "pnpm": {
     "overrides": {
-      "postcss@<8.5.10": ">=8.5.10"
+      "postcss@<8.5.10": "^8.5.10"
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 65 - 67, The override for PostCSS in package.json
is too permissive ("postcss@<8.5.10": ">=8.5.10") and may allow PostCSS v9+ on
future lockfile refreshes; change the override value to "^8.5.10" to pin the
major to 8 while still allowing patch/minor updates, updating the entry that
references "postcss@<8.5.10" so the RHS is "^8.5.10".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@package.json`:
- Around line 65-67: The override for PostCSS in package.json is too permissive
("postcss@<8.5.10": ">=8.5.10") and may allow PostCSS v9+ on future lockfile
refreshes; change the override value to "^8.5.10" to pin the major to 8 while
still allowing patch/minor updates, updating the entry that references
"postcss@<8.5.10" so the RHS is "^8.5.10".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a92cb392-44c7-4c72-858d-148df6bc9d01

📥 Commits

Reviewing files that changed from the base of the PR and between beac516 and 839a283.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • docs/security-audit-ignores.md
  • lib/auth.ts
  • lib/auth/soft-flag.ts
  • lib/github.ts
  • package.json
  • playwright.config.ts
  • tests/e2e/seo.spec.ts
  • tests/unit/auth-e2e-shim.test.ts
  • tests/unit/auth-org-sync.test.ts
  • tests/unit/auth/soft-flag.test.ts

@harshitsinghbhandari
harshitsinghbhandari merged commit d0971f1 into develop Jun 5, 2026
4 checks passed
@harshitsinghbhandari
harshitsinghbhandari deleted the security/m-l-followups branch July 18, 2026 18:04
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