fix(security): enforce anonymous-deny posture uniformly across HTTP surfaces (#2567) - #2973
Merged
Merged
Conversation
…urfaces (#2567) The ADR-0056 D2 requireAuth flip made REST /data/* deny-anonymous by default, but sibling HTTP surfaces reached ObjectQL without passing through the gate, so the anonymous posture was inconsistent by surface: a caller denied on /data could read the same object data through another door. This closes the two remaining gaps (the /meta gate had already landed) and pins every surface. Dispatcher GraphQL: - handleGraphQL now applies the same requireAuth gate as /data and /meta, resolving identity for the direct /graphql route that does not flow through dispatch() (kernel.graphql's security middleware falls open for anonymous). - The dispatcher's requireAuth default is aligned with the REST plugin's (?? true) so a bare host no longer denies anonymous /data while serving the same rows over /graphql; an explicit requireAuth:false opt-out warns at boot. Raw-hono standard /data routes: - Each route now consults requireAuth (secure by default, mirroring rest-server.ts). Previously these delegated straight to ObjectQL and were only shadowed when the REST plugin registered the same paths first, so the posture depended on plugin registration order. Order no longer affects security. Proofs & conformance: - showcase-anonymous-deny-surfaces.dogfood.test.ts proves anonymous /meta, /graphql and /data all 401 on the platform default; authenticated members pass. - Handler-level regression coverage in http-dispatcher.requireauth.test.ts and hono-anonymous-deny.test.ts. - Three new authz-conformance rows (meta / graphql / raw-hono) with enforcement sites and proof, marked high-risk, so a new ungated surface or a dropped proof fails CI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ShknNUYoSTspJLBiqorD8V
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 27 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
client.hono.test.ts boots the raw-hono standard endpoints with no auth service and drives anonymous data CRUD to exercise the CLIENT — it is not testing the auth posture. Under the secure-by-default gate those requests now 401, so the suite sets the documented `requireAuth: false` opt-out (the same a deployment that intentionally serves data publicly would use). The gate itself stays proven in plugin-hono-server/hono-anonymous-deny.test.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ShknNUYoSTspJLBiqorD8V
os-zhuang
marked this pull request as ready for review
July 15, 2026 15:24
This was referenced Jul 16, 2026
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.
Closes #2567 (Phase 1).
Problem
The ADR-0056 D2
requireAuthflip made REST/data/*deny-anonymous by default, but three sibling HTTP surfaces reached ObjectQL without passing through the gate — so the platform's anonymous posture was inconsistent by surface. An anonymous caller denied on/datacould read the same object data through a different door. An attacker doesn't care which door is locked.The
/metagate had already landed; this PR closes the two remaining gaps and pins every surface with a conformance row.Changes
Dispatcher GraphQL (
runtime/http-dispatcher.ts,runtime/dispatcher-plugin.ts)POST /graphqlreachedkernel.graphql, whose security middleware falls open for an anonymous context (no userId/roles →next()).handleGraphQLnow applies the samerequireAuthgate as/dataand/meta, resolving identity for the direct route that does not flow throughdispatch()(via a new best-effortresolveRequestExecutionContexthelper).requireAuthdefault is aligned with the REST plugin's (?? true). Defaulting it OFF while REST defaulted ON was itself the by-surface inconsistency — a bare host denied anonymous/datayet served the same rows over/graphql. The harness comment already claimed the dispatcher booted secure-by-default; this makes reality match. An explicitrequireAuth: falseopt-out is honoured and logs a boot warning.Raw-hono standard
/dataroutes (plugins/plugin-hono-server/hono-plugin.ts)requireAuth(secure by default, mirroringrest-server.ts). Order no longer affects the anonymous posture.Behaviour change
On a
requireAuthdeployment (the secure default), anonymousPOST /graphqland anonymous raw-hono/datanow return 401. Deployments that intentionally serve these surfaces publicly setrequireAuth: false(a boot warning is logged) — the same opt-out contract as REST. Production cloud/CLI already threadrequireAuthfrom the stackapiblock, so their posture is unchanged; only bare hosts and the verify harness pick up the new secure default (correctly).Proofs
showcase-anonymous-deny-surfaces.dogfood.test.ts— boots the real showcase stack on the platform default and proves anonymous/meta,/graphqland/dataall 401, while authenticated members pass (6/6).http-dispatcher.requireauth.test.ts— GraphQL gate handler-level coverage (anonymous 401 / authenticated + system pass / off = no-op / 400 before gate).hono-anonymous-deny.test.ts— drives the real raw-hono/dataroutes with no REST plugin in the picture (anonymous 401 / authenticated 200 / opt-out reachable).authz-conformance.matrix.tsrows (anonymous-deny-meta/-graphql/-hono-data) with enforcement sites + proof, added to the high-risk set — so a new ungated surface or a dropped proof fails CI.Test results
@objectstack/runtimefull suite: 511/511 ✓@objectstack/plugin-hono-serverfull suite: 69/69 ✓@objectstack/dogfoodfull suite: 249 passed / 2 skipped ✓ (no fallout from the dispatcher default flip)check:authz-resolver,check:release-notesall green.Scope
This is Phase 1 of #2567 (close the holes + pin the surfaces). Phase 2 — lifting the anonymous-deny decision into a single shared middleware with an explicit exemption list, and wiring the conformance ledger's
discover()ratchet to enumerate HTTP entry points from source — remains a follow-on, tracked in the issue.🤖 Generated with Claude Code
https://claude.ai/code/session_01ShknNUYoSTspJLBiqorD8V
Generated by Claude Code