Skip to content

fix(auth): enforce declared auth:true on /meta + /ai route families#2841

Merged
os-zhuang merged 1 commit into
mainfrom
claude/gracious-babbage-n42vyw
Jul 11, 2026
Merged

fix(auth): enforce declared auth:true on /meta + /ai route families#2841
os-zhuang merged 1 commit into
mainfrom
claude/gracious-babbage-n42vyw

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Problem

On a requireAuth runtime host, several routes declared auth: true returned HTTP 200 without authentication. On the tenant-less objectos host (publicly reachable) this leaked system-object schemas and AI adapter/model config:

  • GET /api/v1/meta/object → 200 (real sys_* field definitions)
  • GET /api/v1/ai/status → 200 ({"model": …})
  • GET /api/v1/ai/effective-model → 200 (model config + env-var presence)
  • GET /api/v1/data/sys_user → 401 (data plane correctly protected)

Root cause

The /data routes gate anonymous callers via RestServer.enforceAuth (driven by api.requireAuth), but the /meta routes (rest-server) and /ai routes (dispatcher) never applied that gate. Four code sites each assumed the auth check ran "upstream"/"separately", yet no dispatch stage enforces RouteDefinition.auth:

  • enforceAuthGate only covers ADR-0069 password/MFA gates
  • enforceProjectMembership bails when the request is anonymous or unscoped

So /meta and /ai were unauthenticated on every requireAuth host; the tenant-less runtime host merely made it publicly visible with system data.

Fix

Enforce the declared auth: true / requireAuth contract on /meta and /ai, mirroring /data:

  • rest-server: wrap registerMetadataEndpoints so every /meta route inherits the enforceAuth gate (present and future routes) instead of per-handler checks the next route would forget.
  • http-dispatcher: add a requireAuth option; gate handleAI (honouring route.auth) and the metadata catch-all. Authenticated or internal system contexts pass; anonymous → 401.
  • dispatcher-plugin: add requireAuth config (read top-level, or from the stack api block the cloud apps pass as scoping); thread it to the HttpDispatcher and mountRouteOnServer.
  • serve: pass requireAuth through to the dispatcher plugin.

No-op when requireAuth is off (demo / single-tenant) — the previously public surface there is unchanged; an authenticated user passes exactly as on /data.

Tests

  • http-dispatcher.requireauth.test.ts — 8 tests: anon → 401, authed/system → pass, requireAuth=false → open, auth:false route → open (handleAI + handleMetadata).
  • rest-meta-auth.test.ts — 3 tests: anon → 401 (schema read never invoked), authed → pass-through, requireAuth=false → open.
  • Full runtime + rest regression suites green; full DTS build clean.

Branched from the cloud-pinned framework SHA (e04e37c8) so cloud can pin pin + this fix with minimal drift for a clean security-hotfix staging deploy; rebase onto main before merging if needed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TCMvtvZuEQznmJr3dccV9K


Generated by Claude Code

@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 11, 2026 2:43pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/rest, @objectstack/runtime.

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

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest, @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via packages/cli, @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/cli.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/rest)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/rest, @objectstack/runtime)
  • content/docs/protocol/objectos/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/objectos/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/rest, @objectstack/runtime)
  • content/docs/releases/v12.mdx (via @objectstack/rest)

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.

The /data routes gate anonymous callers via RestServer.enforceAuth
(requireAuth), but the /meta routes (rest-server) and /ai routes
(dispatcher) never applied that gate. Three handlers each assumed the
auth check ran "upstream"/"separately", yet no dispatch stage enforces
RouteDefinition.auth: enforceAuthGate only covers ADR-0069 password/MFA
gates and enforceProjectMembership bails on anonymous/unscoped requests.
On a requireAuth deployment an anonymous caller could read object/field
schemas and AI adapter/model status; on a tenant-less runtime host those
are SYSTEM-object schemas and the host is publicly reachable.

- rest-server: wrap registerMetadataEndpoints so every /meta route
  inherits the enforceAuth gate (present and future routes) instead of
  per-handler checks the next route would forget.
- http-dispatcher: add a requireAuth option; gate handleAI (honouring
  the declared route.auth) and the metadata catch-all. Authenticated or
  internal system contexts pass; anonymous -> 401.
- dispatcher-plugin: add requireAuth config (read top-level, or from the
  stack `api` block the cloud apps pass as `scoping`); thread it to the
  HttpDispatcher and to mountRouteOnServer so explicit AI routes enforce
  it too.
- serve: pass requireAuth through to the dispatcher plugin.

No-op when requireAuth is off (demo / single-tenant), so the previously
public surface there is unchanged; an authenticated user passes exactly
as on /data. Adds regression tests for the dispatcher and REST gates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCMvtvZuEQznmJr3dccV9K
@os-zhuang
os-zhuang force-pushed the claude/gracious-babbage-n42vyw branch from 269841b to cb662a5 Compare July 11, 2026 14:40
@github-actions github-actions Bot added the tests label Jul 11, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review July 11, 2026 15:29
@os-zhuang
os-zhuang merged commit 3c548bc into main Jul 11, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/gracious-babbage-n42vyw branch July 11, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants