Skip to content

fix(rest): exempt a request from the ADR-0069 gate only when it carries a real path (#7432) - #7836

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-7432-authgate-path-guard
Aug 12, 2026
Merged

fix(rest): exempt a request from the ADR-0069 gate only when it carries a real path (#7432)#7836
hotlong merged 1 commit into
mainfrom
claude/issue-7432-authgate-path-guard

Conversation

@claude

@claudeclaudeBot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Fixes#7432Option 1 only (the instance fix). ⛔ Option 2 is deliberately not implemented: isAuthGateAllowlisted's no path ⇒ exempt semantics is unchanged and no fail-closed variant is added. The census that lets a maintainer decide Option 2 is posted on the issue: #7432 (comment)

What was wrong

isAuthGateAllowlisted(undefined) returns true — it treats "no path" as allow-listed (packages/core/src/security/auth-gate.ts:67). REST's enforceAuth passed req.path straight through, so a request whose path was absent or an empty string read as allow-listed on every route and the ADR-0069 gate (expired password / enforced MFA) did not fire for a session policy says must be blocked.

The sibling seam already guards this, with a comment naming the hazard (anonymous-deny.ts:117-122). A comment is not a mechanism.

Nothing shipped was bypassable. The hono adapter populates path at all three request-construction sites (adapter.ts:341/:743/:848). What is fixed is the direction of the default: the guard was carried by the caller's discipline on a fail-OPEN seam, so a new transport adapter — or any synthetic request — disabled a security gate by omission and no test went red. This is a fail-open default, not a disclosure; the issue is public on purpose and its severity is unchanged.

The change

packages/rest/src/rest-server.ts — one hunk, mirroring anonymous-deny.ts:122:

constpathExempt=typeofreq?.path==='string'&&req.path.length>0&&isAuthGateAllowlisted(req.path);if(gate&&req?.method!=='OPTIONS'&&!pathExempt){

Tests pin the consequence, not the guard

Six tests in packages/rest/src/rest-auth-gate.test.ts assert blocked / not-blocked, so they survive a rewrite of the guard expression:

  • Positive: a gated session with an absentpath, and separately an empty-stringpath, is blocked (403 PASSWORD_EXPIRED).
  • Negative, load-bearing: five allow-listed path shapes still pass; an ordinary gated request on /api/v1/data/sys_user still blocks; an ungated pathless session is untouched; OPTIONS preflight stays exempt. A guard that fixed the bypass by blocking everything would fail four of these.

Reverse-verified: with the guard reverted to the raw call, exactly the 2 pathless tests go red and the other 8 stay green.

Gates (measured)

gateresult
pnpm build71/71 successful
packages/rest full suite92 files, 1477 tests, 0 failures
packages/core/src/security suite14 files, 343 tests, 0 failures
pnpm --filter @objectstack/rest typecheckclean
pnpm lintclean

No ratchet ledger raised; no unexplained failures.

Scope

Touches packages/rest only, plus a .changeset/. No content/docs/releases/ edit. The census surfaced one thing Option 2 would have to price in — the dispatcher's cleanPath can be '' for ${prefix}/ (packages/runtime, a third lane) — reported on the issue and not fixed here.


Generated by Claude Code

…es a real path (#7432)
`isAuthGateAllowlisted(undefined)` returns `true` — it treats "no path" as
allow-listed. REST's `enforceAuth` passed `req.path` straight through, so a
request whose `path` was absent or an empty string read as allow-listed on
every route and the ADR-0069 gate (expired password / enforced MFA) did not
fire for a session policy says must be blocked.
`enforceAuth` now applies the guard the sibling seam already carries
(`shouldDenyAnonymous`, core/src/security/anonymous-deny.ts:122): a path
exempts a gated session only when it is a non-empty string the allow-list
actually accepts.
Nothing shipped was bypassable — the hono adapter populates `path` at all
three request-construction sites. What is fixed is the direction of the
default: the guard was carried by the caller's discipline on a fail-OPEN
seam, so a new transport adapter or any synthetic request disabled a
security gate by omission with no test going red.
Tests pin the consequence, not the guard expression: absent `path` and
empty-string `path` against a gated session are blocked (both go red with
the guard removed), while every allow-listed path shape still passes, an
ordinary gated request still blocks, an ungated pathless session is
untouched, and OPTIONS preflight stays exempt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012J7U4wwUHYyZ7EgsUHuJA7
@vercel

vercelBot commented Aug 11, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 11, 2026 7:32pm

Request Review

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest.

9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/connect-mcp.mdx(via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx(via @objectstack/rest)
  • content/docs/api/index.mdx(via @objectstack/rest)
  • content/docs/permissions/authentication.mdx(via @objectstack/rest)
  • content/docs/permissions/system-context.mdx(via packages/rest)
  • content/docs/plugins/index.mdx(via @objectstack/rest)
  • content/docs/plugins/packages.mdx(via @objectstack/rest)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/rest)
  • content/docs/protocol/kernel/i18n-standard.mdx(via packages/rest)

3 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/rest)
  • content/docs/releases/v12.mdx(via @objectstack/rest)
  • content/docs/releases/v17.mdx(via @objectstack/rest)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@hotlong@claude