Skip to content

fix(sharing): verified authz on share-link routes (#2851)#2853

Merged
os-zhuang merged 1 commit into
mainfrom
fix/share-link-routes-verified-authz
Jul 12, 2026
Merged

fix(sharing): verified authz on share-link routes (#2851)#2853
os-zhuang merged 1 commit into
mainfrom
fix/share-link-routes-verified-authz

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #2851. Companion to #2848 (settings routes) — same root cause (a raw-app route registration trusting identity headers). Found by the ADR-0090 D10 adversarial security review.

The hole

The share-link routes (POST/GET/DELETE /api/v1/share-links, registered by SharingServicePlugin on the raw Hono app) derived the caller from x-user-id / x-tenant-id headers, and the service ignored the caller context on revoke. So a client could:

  • forge link attribution (created_by);
  • GET /api/v1/share-links?createdBy=<victim>enumerate another user's link tokens, which then resolve records via the public /:token/resolve under a system context (RLS bypassed);
  • DELETErevoke arbitrary users' links (sharing DoS).

The fix

  • Verified identity. SharingServicePlugin derives the caller (and their positions/permissions) from resolveAuthzContext (session / API key / OAuth), never headers. The route default is secure (anonymous). Create / list / revoke require a signed-in principal (401 otherwise). The public /:token/resolve stays public (the token is the authorization) but keys its audience: 'signed_in' check off the verified session, not a spoofable x-user-id.
  • List scoping. GET /api/v1/share-links is forced to the caller's own links — no more ?createdBy=<victim> enumeration.
  • Revoke ownership. revokeLink requires the caller to be the link's creator (system/internal callers bypass).
  • Create access check. createLink verifies the record is visible to the caller (read under the caller's own RLS) before minting — you can only share a record you can actually see. (This also hardens the already-verified dispatcher handler, which shares the service.)

ShareLinkExecutionContext gains optional positions / permissions so the record-access check evaluates the real principal.

Tests

  • Revoke ownership: a non-owner is denied (403) and the row is untouched; the creator and a system caller may revoke.
  • Create access: an untrusted caller minting a link for a record it cannot see gets 403 (no existence leak); a system caller still gets the plain 404.
  • plugin-sharing 79 green; spec api-surface unchanged; tsc clean.

Behaviour change

Managing share links now requires a real verified credential (which the console already presents). The contextFromRequest option remains the escape hatch for bespoke gateways.

🤖 Generated with Claude Code

The raw-app share-link routes trusted x-user-id/x-tenant-id headers and
the service ignored the caller on revoke — so a client could forge link
attribution, enumerate another user's link tokens (?createdBy=<victim>,
which then resolve records under a system context bypassing RLS), and
revoke arbitrary users' links.

- Verified identity: SharingServicePlugin derives the caller (+ their
  positions/permissions) from resolveAuthzContext (session/API key/OAuth),
  never headers. Route default is SECURE (anonymous). Create/list/revoke
  require a signed-in principal (401); the public /:token/resolve stays
  public but keys its audience:'signed_in' check off the verified session,
  not a spoofable x-user-id.
- List scoping: GET /share-links is forced to the caller's own links —
  no more ?createdBy=<victim> enumeration.
- Revoke ownership: revokeLink requires the caller to be the creator
  (system callers bypass); previously the context was ignored.
- Create access check: createLink verifies the record is visible to the
  caller (read under the caller's RLS) before minting — you can only share
  a record you can see.

ShareLinkExecutionContext gains optional positions/permissions for the
record-access check. Companion to #2848 (settings routes).

Tests: revoke-ownership (non-owner denied, creator + system allowed),
create-access (untrusted caller → 403 for an unseen record, system → 404).
plugin-sharing 79 green; spec api-surface unchanged; tsc clean.

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

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

Request Review

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-sharing, @objectstack/spec.

96 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/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @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/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @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/troubleshooting.mdx (via @objectstack/spec)
  • 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/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/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • 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/authorization.mdx (via packages/plugins/plugin-sharing, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-sharing, @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/plugin-sharing, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/runtime-capabilities.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/plugins/plugin-sharing, 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 packages/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/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.

@os-zhuang
os-zhuang merged commit 01274eb into main Jul 12, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the fix/share-link-routes-verified-authz branch July 12, 2026 10:05
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: share-link routes trust x-user-id/x-tenant-id headers (forge attribution, enumerate others' tokens)

1 participant