Skip to content

fix(security): enforce anonymous-deny posture uniformly across HTTP surfaces (#2567) - #2973

Merged
os-zhuang merged 2 commits into
mainfrom
claude/anonymous-posture-inconsistency-j43xco
Jul 15, 2026
Merged

fix(security): enforce anonymous-deny posture uniformly across HTTP surfaces (#2567)#2973
os-zhuang merged 2 commits into
mainfrom
claude/anonymous-posture-inconsistency-j43xco

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #2567 (Phase 1).

Problem

The ADR-0056 D2 requireAuth flip 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 /data could read the same object data through a different door. An attacker doesn't care which door is locked.

The /meta gate 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 /graphql reached kernel.graphql, whose security middleware falls open for an anonymous context (no userId/roles → next()). handleGraphQL now applies the same requireAuth gate as /data and /meta, resolving identity for the direct route that does not flow through dispatch() (via a new best-effort resolveRequestExecutionContext helper).
  • The dispatcher's requireAuth default 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 /data yet served the same rows over /graphql. The harness comment already claimed the dispatcher booted secure-by-default; this makes reality match. An explicit requireAuth: false opt-out is honoured and logs a boot warning.

Raw-hono standard /data routes (plugins/plugin-hono-server/hono-plugin.ts)

  • These delegate straight to ObjectQL and were only shadowed when the REST plugin registered the same paths first — so secure-by-default depended on plugin registration order (a load-order change silently reopened it, no test failing). Each route now consults requireAuth (secure by default, mirroring rest-server.ts). Order no longer affects the anonymous posture.

Behaviour change

On a requireAuth deployment (the secure default), anonymous POST /graphql and anonymous raw-hono /data now return 401. Deployments that intentionally serve these surfaces publicly set requireAuth: false (a boot warning is logged) — the same opt-out contract as REST. Production cloud/CLI already thread requireAuth from the stack api block, 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, /graphql and /data all 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 /data routes with no REST plugin in the picture (anonymous 401 / authenticated 200 / opt-out reachable).
  • 3 new authz-conformance.matrix.ts rows (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/runtime full suite: 511/511 ✓
  • @objectstack/plugin-hono-server full suite: 69/69 ✓
  • @objectstack/dogfood full suite: 249 passed / 2 skipped ✓ (no fallout from the dispatcher default flip)
  • Full workspace build (70/70), lint on changed files, check:authz-resolver, check:release-notes all 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

…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
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Canceled Canceled Jul 15, 2026 4:23pm

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling and removed size/m labels Jul 15, 2026
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/client, @objectstack/dogfood, @objectstack/plugin-hono-server, @objectstack/runtime.

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

  • content/docs/ai/skills-reference.mdx (via packages/client)
  • content/docs/api/client-sdk.mdx (via @objectstack/client)
  • content/docs/api/data-flow.mdx (via @objectstack/client)
  • content/docs/api/environment-routing.mdx (via @objectstack/client)
  • content/docs/api/error-catalog.mdx (via @objectstack/client)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/client, @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/client)
  • content/docs/kernel/runtime-services/index.mdx (via packages/client)
  • content/docs/permissions/authentication.mdx (via @objectstack/client, @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/dogfood)
  • content/docs/permissions/delegated-administration.mdx (via packages/dogfood)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-hono-server)
  • content/docs/plugins/packages.mdx (via @objectstack/client, @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/client)
  • content/docs/releases/implementation-status.mdx (via @objectstack/client, @objectstack/plugin-hono-server, @objectstack/runtime)

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.

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
os-zhuang marked this pull request as ready for review July 15, 2026 15:24
@os-zhuang
os-zhuang merged commit 541b96a into main Jul 15, 2026
16 of 17 checks passed
@os-zhuang
os-zhuang deleted the claude/anonymous-posture-inconsistency-j43xco branch July 15, 2026 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[security] Anonymous posture inconsistent across surfaces: dispatcher /graphql and raw hono /data bypass requireAuth (/meta now gated)

2 participants