Skip to content

fix(security): single-source the request authorization resolver (REST dropped sys_user_role)#2341

Merged
os-zhuang merged 1 commit into
mainfrom
fix/unify-authz-resolver
Jun 26, 2026
Merged

fix(security): single-source the request authorization resolver (REST dropped sys_user_role)#2341
os-zhuang merged 1 commit into
mainfrom
fix/unify-authz-resolver

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What & why

A security/correctness root-fix. The REST server (@objectstack/rest) and the runtime dispatcher (@objectstack/runtime) each carried their own copy of the request → ExecutionContext identity/role resolver. On a security path, duplicated logic drifts — and it did:

The REST copy silently omitted sys_user_role (so custom roles granted via the ADR-0057 D4 platform-RBAC path — the only way to assign custom roles, since sys_member.role is restricted to owner/admin/member — did not apply over REST), plus sys_role_permission_set, the owner→org_owner membership normalization, the platform-admin derivation, and the ai_seat synthesis.

Fail-closed (legitimate access denied / RLS under-applied), not a privilege escalation — but real: RBAC via sys_user_role was a no-op on REST.

Found by dogfooding #2338: a freshly-provisioned user granted contributor via sys_user_role got 403 "for roles []" on the REST data path, while the dispatcher path resolved the role correctly.

The most-thorough fix (not a band-aid)

Don't keep two mirrors in sync on security code — collapse to one. This finishes the extraction the API-key verifier already modelled (resolveApiKeyPrincipal already lived in @objectstack/core and was shared by both transports):

  • @objectstack/core/security — new resolveAuthzContext (the complete session + role/permission aggregation: sys_member + sys_user_role + sys_user_permission_set + sys_role_permission_set, platform-admin derivation, ai_seat) and resolveLocalizationContext. Core is the lowest common dependency of both rest and runtime, so there's no upward-import problem.
  • @objectstack/runtimeresolveExecutionContext now delegates (469 → 138 lines).
  • @objectstack/rest — the inline identity resolver delegates (drops its divergent ~180-line copy).
  • Guardrails — a contract test (resolve-authz-context.test.ts) locking every authorization source, and a lint gate (scripts/check-single-authz-resolver.mjs, wired into lint.yml) that fails CI on a future duplicate resolver or a dropped delegation.

Verification

  • Contract test 7/7 (custom role via sys_user_role · owner→org_owner · role-bound permission sets · platform_admin only-when-unscoped · ai_seat · anonymous).
  • Suites green: runtime 444/444, rest 130/130, core green. Full turbo build green. ESLint clean. check:authz-resolver passes.
  • Dogfooded: a user granted contributor via sys_user_role now resolves that role on the REST data path (previously roles []).

🤖 Generated with Claude Code

… dropped sys_user_role)

The REST server (@objectstack/rest) and the runtime dispatcher (@objectstack/runtime)
each carried their own copy of the request -> ExecutionContext identity/role resolver.
They drifted on a security path: the REST copy silently omitted sys_user_role (so custom
roles granted via the ADR-0057 D4 platform-RBAC path did not apply over REST),
sys_role_permission_set, the owner->org_owner membership normalization, the platform-admin
derivation, and the ai_seat synthesis. Fail-closed (legitimate access denied), not an
escalation.

Root-fix (finishes the extraction the API-key verifier already modelled): a single shared
resolver `resolveAuthzContext` in @objectstack/core/security — the lowest common dependency
of both rest and runtime. Both entry points now delegate; no parallel copies to keep in sync.

- core: resolveAuthzContext + resolveLocalizationContext (complete role/permission
  aggregation) + a contract test asserting every authorization source is honored.
- runtime: resolveExecutionContext delegates (469 -> 138 lines).
- rest: rest-server's identity resolver delegates (drops its divergent ~180-line copy).
- scripts/check-single-authz-resolver.mjs + a lint CI step: guard against a future
  duplicate resolver or a dropped delegation.

Verified: contract test 7/7; suites green (runtime 444/444, rest 130/130, core green);
full turbo build green; ESLint clean; dogfooded — a user granted `contributor` via
sys_user_role now resolves that role on the REST data path (previously roles []).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 26, 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 Jun 26, 2026 9:21am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd dependencies Pull requests that update a dependency file tests tooling size/xl labels Jun 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/concepts/cloud-artifact-api.mdx (via packages/runtime)
  • content/docs/concepts/core/index.mdx (via @objectstack/core)
  • content/docs/concepts/core/plugins.mdx (via @objectstack/core)
  • content/docs/concepts/core/services.mdx (via @objectstack/core)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/core, @objectstack/rest, @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/core, packages/runtime)
  • content/docs/concepts/packages.mdx (via @objectstack/core, @objectstack/rest, @objectstack/runtime)
  • content/docs/concepts/webhook-delivery.mdx (via @objectstack/core)
  • content/docs/guides/ai-capabilities.mdx (via @objectstack/core)
  • content/docs/guides/api-reference.mdx (via @objectstack/rest, @objectstack/runtime)
  • content/docs/guides/authentication.mdx (via @objectstack/core, @objectstack/runtime)
  • content/docs/guides/cloud-deployment.mdx (via @objectstack/runtime)
  • content/docs/guides/contracts/index.mdx (via @objectstack/core)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/runtime)
  • content/docs/guides/driver-configuration.mdx (via @objectstack/runtime)
  • content/docs/guides/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/guides/kernel-services.mdx (via @objectstack/core)
  • content/docs/guides/objectql-migration.mdx (via @objectstack/core)
  • content/docs/guides/packages.mdx (via @objectstack/core, @objectstack/rest, @objectstack/runtime)
  • content/docs/guides/plugin-chatbot-integration.mdx (via @objectstack/runtime)
  • content/docs/guides/plugin-development.mdx (via @objectstack/core)
  • content/docs/guides/plugins.mdx (via @objectstack/core, @objectstack/rest)
  • content/docs/guides/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/guides/runtime-services/examples.mdx (via @objectstack/core)
  • content/docs/guides/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/core)
  • content/docs/protocol/objectos/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/core, @objectstack/runtime)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/core, @objectstack/runtime)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/core)

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.

@os-zhuang
os-zhuang merged commit aa33b02 into main Jun 26, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the fix/unify-authz-resolver branch June 26, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant