Skip to content

fix(security): pre-wiring identity admission for GraphQL + realtime surfaces (#2992, ADR-0096 D4) - #3013

Merged
os-zhuang merged 2 commits into
mainfrom
claude/graphql-realtime-identity-mevj2w
Jul 16, 2026
Merged

fix(security): pre-wiring identity admission for GraphQL + realtime surfaces (#2992, ADR-0096 D4)#3013
os-zhuang merged 2 commits into
mainfrom
claude/graphql-realtime-identity-mevj2w

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #2992.

Two latent execution surfaces (neither wired to a client today) drop or lack the caller's identity, and would fall open the instant a real transport ships. Per ADR-0096 D4 and the issue's "fix before wiring" directive, this PR threads the GraphQL identity preemptively and registers both surfaces in the authz conformance matrix with CI ratchets that block wiring a transport without the identity story.

Surface 1 — GraphQL (latent context-drop → threaded)

Surface 2 — realtime (no per-recipient authz seam → posture registered + tripwired)

  • New matrix row realtime-delivery-authz (state experimental) registers the honest posture: pure fan-out, Subscription carries no principal, matchesSubscription filters only object+eventTypes (options.filter declared but never read), engine publishes the full after row — safe only while every subscriber is server-internal (webhook auto-enqueuer, knowledge sync). The row states the admission requirement: per-recipient RLS/FLS/tenant re-check on delivery (subscription carries the subscriber's ExecutionContext) or id-only payload + client re-fetch.
  • The authz seam itself is deliberately not built here — per the issue, it must be designed alongside the transport.
  • service-realtime/README.md rewritten: it advertised authorizeChannel, broadcastToUser, presence auth, rooms, and REST endpoints that do not exist. It now documents the real surface and its security posture. The contract (IRealtimeService) and the adapter's publish fan-out carry the same admission note at the seam.

The CI ratchet (D4 meta-test extension)

Extends the existing #2567 discover() probe table in dogfood/test/authz-conformance.test.ts:

  • GraphQL pin: the only kernel.graphql(...) call site is probed for context: in its options. Dropping the threading makes the graphql-identity-thread row's covers go STALE → red CI.
  • Realtime fan-out pin: the adapter's publish is discovered and covered by the realtime-delivery-authz row.
  • Transport tripwires (covered by NO row, matching nothing today): handleUpgrade in the adapter, WebSocket/SSE wiring in the realtime plugin, handleRealtime|Upgrade|Subscribe handlers in the dispatcher, new WebSocket/new EventSource in the client's realtime-api.ts, and a /realtime route literal in rest-server.ts. Any of these appearing → UNCLASSIFIED surface → red CI with a checklist, until the identity story is registered.
  • Two new "ratchet bites" tests prove both pins actually fire.

Tests

  • packages/runtime — 532/532 pass (incl. 4 new identity-threading tests: user, system, requireAuth-off threading, anonymous-guest no-authority).
  • packages/dogfoodauthz-conformance.test.ts 7/7; e2e proof showcase-anonymous-deny-surfaces.dogfood.test.ts 6/6.
  • packages/spec contract tests + service-realtime suite pass; touched packages build clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CvQFjTKVdP5HUPxzcBvpCF


Generated by Claude Code

…urfaces (#2992, ADR-0096 D4)

Two latent execution surfaces dropped/lacked the caller identity and would
have fallen open the instant a real client transport was wired. Fix the
identity story now and pin it in CI, per ADR-0096:

GraphQL (surface 1 — context-drop, now threaded):
- handleGraphQL resolved identity only under requireAuth and passed only
  { request } to kernel.graphql, dropping the ExecutionContext. It now
  resolves the caller identity even on the direct dispatcher-plugin route
  and even when requireAuth is off, and threads it as options.context —
  so the first real engine runs caller-scoped, never context-less
  (the security middleware falls OPEN on a missing principal).
- IGraphQLService.execute documents the admission requirement: forward
  the context to every data-engine call as options.context.
- Matrix row graphql-identity-thread + a source probe pin the threading:
  removing `context:` from the kernel.graphql call goes STALE → red CI.
- Unit tests cover user/system/guest threading postures.

realtime (surface 2 — no per-recipient authz seam, posture registered):
- Matrix row realtime-delivery-authz registers the honest posture: pure
  fan-out, subscriptions carry no principal, full after-row payload —
  trusted server-internal subscribers only, with the admission
  requirement (per-recipient RLS/FLS/tenant re-check on delivery, or
  id-only payload + client re-fetch) stated on the row.
- Transport TRIPWIRE probes (adapter handleUpgrade, plugin transport,
  dispatcher handleRealtime/Upgrade/Subscribe, client WebSocket/
  EventSource, rest /realtime route) discover keys covered by NO row —
  wiring a transport fails CI as UNCLASSIFIED until the identity story
  ships with it. Ratchet-bites tests prove both new pins fire.
- service-realtime README rewritten: it advertised authorizeChannel /
  broadcastToUser / presence auth / rooms that do not exist. It now
  documents the real surface and the security posture; the contract and
  the publish fan-out carry the same admission note at the seam.

Closes #2992.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvQFjTKVdP5HUPxzcBvpCF
@vercel

vercel Bot commented Jul 16, 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 16, 2026 5:12am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/dogfood, @objectstack/runtime, packages/services, @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via packages/services, @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/runtime, packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • 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/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/services, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/dogfood, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx (via packages/dogfood)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime, packages/services, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/services, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)

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.

…ealtime protocol status banner

The page's planned wire protocol shows only a subscribe-time permission
error; the admission requirement is per-delivery re-authorization (or
id-only payloads). State it where a transport implementer will read first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvQFjTKVdP5HUPxzcBvpCF
@os-zhuang
os-zhuang marked this pull request as ready for review July 16, 2026 05:12
@os-zhuang
os-zhuang merged commit 59cd765 into main Jul 16, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/graphql-realtime-identity-mevj2w branch July 16, 2026 05:12
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/l tests tooling

Projects

None yet

2 participants