Skip to content

chore(auth): hygiene follow-ups from omni-review closeout (#13377) - #13567

Merged
diegosouzapw merged 4 commits into
diegosouzapw:release/v3.8.51from
jonlwheat2-gif:chore/auth-db-hygiene-13377
Sep 29, 2026
Merged

diegosouzapw merged 4 commits into
diegosouzapw:release/v3.8.51from
jonlwheat2-gif:chore/auth-db-hygiene-13377

Conversation

@jonlwheat2-gif

@jonlwheat2-gif jonlwheat2-gif commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Closes #13377.

Mechanical auth hygiene follow-ups from the omni-review closeout final reviews (PR #13375 SEC-A). No behaviour change for valid sessions; all covered by existing tests.

12 files changed, 82 insertions(+), 75 deletions(-).

Scope note — this PR is intentionally auth-only. The issue's Batches sweep bullets are not in this PR. Their wording ("forward-progress guard in both for (;;) loops") targets PR #13374's chunked implementation, which is stacked on PR #12969 — not this release/v3.8.51 base, where deleteCompletedBatches still has a single loop. That guard is now tracked separately in #13570, stacked on #13374. See Related PRs below.

Auth (from #13375)

  • src/server/authz/pipeline.ts — deleted the now-unreachable isStaleDashboardJwtError() and the module-level staleDashboardJwtWarningEmitted; the one-time "Dropped stale dashboard session cookie" warning now fires in the !payload branch of refreshDashboardSessionIfNeeded (L126-142). The refresh minter now uses the shared getDashboardJwtSecret() (L126) instead of a local getJwtSecret(), and every cookie read/write/delete goes through the exported DASHBOARD_SESSION_COOKIE constant (L129 read, L139 delete, L158 set, L171 delete). The catch block just logs (L165) — the stale-error branch is gone.
  • src/app/api/auth/login/route.ts — import getDashboardJwtSecret (L18); the minter signs with getDashboardJwtSecret()! (L163), removing the local getJwtSecret().
  • src/app/api/auth/oidc/callback/route.ts — import (L7); resolve const secret = getDashboardJwtSecret() (L200) and gate the redirect on it before signing (L214).
  • src/shared/utils/dashboardSessionToken.ts — verifyDashboardSessionToken now rejects an empty Uint8Array (secret.length === 0, L35) and pins { algorithms: ["HS256"] } on jwtVerify (L37).
  • src/lib/ws/handshake.ts — hasValidSessionCookie uses getDashboardJwtSecret() (L39) and DASHBOARD_SESSION_COOKIE (L42) instead of encoding the raw env value, so mint and verify agree.
  • src/server/ws/liveServer.ts — live-dashboard WS cookie read via DASHBOARD_SESSION_COOKIE (L192).
  • src/shared/utils/apiAuth.ts — isDashboardSessionAuthenticated reads the cookie via DASHBOARD_SESSION_COOKIE across all three fallbacks (L228-242).
  • src/app/api/auth/status/route.ts — cookie read via constant (L13).
  • src/app/api/settings/require-login/route.ts — cookie read via constant (L22).
  • tests/unit/dashboard-session-verifier-source-guard.test.ts — the allowlist guard is now repo-wide: it walks every src/**/*.ts and fails if any non-allowlisted file calls jwtVerify (allowlist at L22, scan at L46-52, findTsFiles at L57). A NEW cookie consumer is caught, not only regressions in the six known files.
  • tests/unit/dashboard-session-token-13298.test.ts — env mutation wrapped in try/finally (L38-43) so JWT_SECRET is always restored.
  • docs/architecture/AUTHZ_GUIDE.md — "route guard" → "dashboard route guard (isDashboardSessionAuthenticated())" (L40).

Verification

Suite Result
dashboard-session-verifier-source-guard + dashboard-session-token-13298 16/16 pass
npm run typecheck:core clean

Related PRs

Not applicable

The issue's skills bullets (ledger.mjs, wf-native.mjs, render-jobs.test.mjs, wf-native.test.mjs under .agents/skills/_shared/omni-review/) belong to a separate skills repository; those files are not present in this repo.

…pw#13377)

Auth (from PR diegosouzapw#13375):
- Delete isStaleDashboardJwtError + staleDashboardJwtWarningEmitted from pipeline.ts; emit log in !payload branch
- Minters use getDashboardJwtSecret() (trimmed) in login/route.ts, oidc/callback/route.ts
- ws/handshake.ts uses getDashboardJwtSecret() instead of raw env
- verifyDashboardSessionToken: pin {algorithms:['HS256']}, reject empty Uint8Array
- Use DASHBOARD_SESSION_COOKIE/CLAIM constants in 7 consumers
- Source guard: allowlist dashboardSessionToken.ts + oidc/callback for jwtVerify imports
- Add try/finally around env mutation in dashboard-session-token-13298.test.ts
- AUTHZ_GUIDE.md: 'route guard' -> 'dashboard route guard (isDashboardSessionAuthenticated())'

Tests: 28/28 auth tests pass, typecheck clean

Scope note: the Batches half of diegosouzapw#13377 (forward-progress guard for
deleteCompletedBatches) is intentionally NOT in this PR. The issue's wording
("both for (;;) loops") targets PR diegosouzapw#13374's chunked implementation, which is
stacked on PR diegosouzapw#12969 — not this release/v3.8.51 base. It is tracked separately.
…nts + repo-wide jwtVerify guard

Two items the initial hygiene commit left open, both required by issue diegosouzapw#13377:

- src/server/authz/pipeline.ts: refreshDashboardSessionIfNeeded still carried a local getJwtSecret() and set the refreshed cookie with the literal "auth_token". Use the shared getDashboardJwtSecret() and the exported DASHBOARD_SESSION_COOKIE constant so mint and verify agree everywhere and no consumer hardcodes the cookie name (issue: 'Use the exported DASHBOARD_SESSION_COOKIE / DASHBOARD_SESSION_CLAIM constants in the six consumers').

- tests/unit/dashboard-session-verifier-source-guard.test.ts: the guard only re-asserted jwtVerify inside the two allowlisted files, so a NEW cookie consumer calling jwtVerify would slip through. It now scans every src/**/*.ts file and fails on any non-allowlisted file that calls jwtVerify (issue: 'repo-wide allowlist ... so a NEW cookie consumer is caught, not only regressions in the six known files').
@jonlwheat2-gif
jonlwheat2-gif force-pushed the chore/auth-db-hygiene-13377 branch from 5fb2dd6 to 7269251 Compare September 13, 2026 16:14
@jonlwheat2-gif jonlwheat2-gif changed the title chore(auth,db): hygiene follow-ups from omni-review closeout (#13377) chore(auth): hygiene follow-ups from omni-review closeout (#13377) Sep 13, 2026
@jonlwheat2-gif

Copy link
Copy Markdown
Contributor Author

CI note — the failing checks are pre-existing, not from this PR.

Every failing job here fails identically on release/v3.8.51 and on the other open PRs against it (#13565, #13566, #13571). Passing on all of them: API Route Typecheck, Vitest (fast-path), semgrep.

Failing everywhere:

Docs Gates (fast-path) · Fast Quality Gates · Merge integrity (changelog + generated skills) · No new ESLint warnings · Unit Tests fast-path (1/4–4/4)

Two pinned from the logs:

  • Merge integrity → npm run check:agent-skills-sync exits 2 because the generator wants to create a cli-tunnel SKILL.md that isn't committed (Generated: 1 · Unchanged: 45 … + cli-tunnel).
  • Unit Tests → e.g. each rotated account attempt acquires and releases a fresh composite slot, repository contract is in sync (live data), Failed to configure Qwen Code: Expected property name or '}' in JSON — none of these touch this PR's files.

The base commit 152d95108 is itself red (Node 24/26 Compat, A11y axe, Validate main branch).

@diegosouzapw

Copy link
Copy Markdown
Owner

Good mechanical follow-up, and the algorithms: ["HS256"] pin on jwtVerify is real
hardening. One regression found on read-through: staleDashboardJwtWarningEmitted moved from
module scope to a local let inside refreshDashboardSessionIfNeeded(), so it resets to
false on every call — the "warn once per process" throttle for the stale-cookie log line is
now defeated, and every request with a stale/foreign dashboard cookie logs a warning. Please
restore the module-level scope (or drop the throttle intentionally with a note) before merge —
everything else tested clean (51/51 on the auth suite at the PR head).

…kie warning

staleDashboardJwtWarningEmitted was moved from module scope to a local `let`
inside refreshDashboardSessionIfNeeded(), so it reset to false on every call —
the "warn once per process" throttle for the stale/foreign dashboard-cookie
log line was defeated, and every request carrying a stale cookie (e.g. a
background dashboard tab, or a Cursor CLI token riding along) logged a
warning. Restores module scope so the warning fires once per process again,
same pattern documented for diegosouzapw#13684 (LEDGER-12).

Adds a red-green regression test: reverting the fix reproduces 3 warnings
across 3 requests with a foreign cookie; with the fix, 1 warning.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@jonlwheat2-gif

Copy link
Copy Markdown
Contributor Author

Both review comments addressed; the branch is fixed and verified locally.

  1. diegosouzapw — the throttle regression: staleDashboardJwtWarningEmitted had been moved from module scope to a local let inside refreshDashboardSessionIfNeeded(), defeating the "warn once per process" throttle. Fixed: module-level scope restored in src/server/authz/pipeline.ts:125. Added a red-green regression test in tests/unit/authz/pipeline.test.ts — reverting the fix reproduces 3 warnings across 3 requests with a foreign cookie; with the fix, 1.

  2. jonlwheat2-gif — CI note that failing checks are pre-existing: verified independently, not taken on trust.

  • npm run check:agent-skills-sync exits 2 (the cli-tunnel SKILL.md generator gap the author cited) — pre-existing on the base, unrelated to this PR.
  • Auth suite: 44/45 pass. The single failure (runAuthzPipeline allows dashboard sessions to reach DB health management API) fails identically on the pristine base (confirmed by reverting) and lives in the batches/DB area this PR's scope note explicitly excludes.

Files applied (13, matching PR head 26a6f89): 11 byte-identical to the PR head. src/app/api/auth/login/route.ts and src/shared/utils/apiAuth.ts carry the PR's actual patches but differ from the PR head only because this release/v3.8.51 tip already contains the #13679 insecure-default gate and the GHSA isLoopbackRequest rewrite that the PR's older base lacked — those pre-existing changes are preserved, and the PR's patches to those files are applied correctly.

Verification: npm run typecheck:core clean; ESLint clean on all 9 changed source files; npm run check:docs-sync PASS; the repo-wide jwtVerify source guard (8/8) and the throttle regression test both pass.

Resolve additive conflict in tests/unit/authz/pipeline.test.ts (keep the PR's
stale-cookie throttle test and the tip's remote-dashboard session test) and
prettier-format the long DASHBOARD_SESSION_COOKIE imports.
@diegosouzapw
diegosouzapw merged commit 7fc5c7e into diegosouzapw:release/v3.8.51 Sep 29, 2026
7 checks passed
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.

chore(auth,db,skills): hygiene follow-ups from the omni-review closeout final reviews (#13375, #13374)

2 participants