Skip to content

fix(identity): close generic-write apiMethods hole on sys_presence & sys_metadata (#3220) - #3222

Merged
os-zhuang merged 1 commit into
mainfrom
claude/continue-task-1591-yqg8hb
Jul 18, 2026
Merged

fix(identity): close generic-write apiMethods hole on sys_presence & sys_metadata (#3220)#3222
os-zhuang merged 1 commit into
mainfrom
claude/continue-task-1591-yqg8hb

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Addresses the concrete, safe slice of #3220 (the follow-through on #1591/#3213). A blast-radius audit determined the broad engine-level write guard over system/append-only is not worth building — near-zero real security gain, high regression risk to delegated administration — so this PR ships only the two genuine latent holes the audit confirmed.

What & why

Two non-better-auth managed objects shipped the exact contradiction #3213 fixed for better-auth objects: enable.apiMethods advertised generic create/update/delete while their managedBy bucket forbids user-context writes — leaving the generic /data route open for a write the bucket does not permit.

  • sys_presence (managedBy: 'append-only') advertised create/update/deleteupdate/delete on an append-only object — but is written only over the realtime websocket/in-memory path, never through ObjectQL. → ['get', 'list'].
  • sys_metadata (managedBy: 'system') advertised full CRUD, but customization overlays are authored only through the metadata-protocol RPC (its engine writes carry a transaction context, not a user session). Confirmed neither the framework nor the Console (objectui) POSTs /data/sys_metadata. → ['get', 'list'].

Reads stay open. The metadata-protocol / realtime write paths are engine-level and bypass the HTTP exposure gate, so they are unaffected.

Why not the broader guard

The audit found the system bucket is overloaded: several system objects are user-writable by designsys_user_position, sys_user_permission_set, sys_position_permission_set (the whole DelegatedAdminGate is built on non-isSystem user writes), plus sys_user_preference and sys_import_job. A blanket affordance-driven engine guard would break delegated administration for near-zero gain (the genuinely engine-only tables already write via isSystem, and the key audit tables — sys_audit_log/sys_activity — are already ['get','list']). The real fix is a bucket-taxonomy split, tracked as the root cause in #3220.

Verification

  • service-realtime sys-presence object suite (10 passed) — new assertion pins apiMethods === ['get','list'] and rejects re-introducing a write verb.
  • metadata-core (100 passed); objectql overlay engine-insert tests (6 passed) — confirm sys_metadata engine writes still work (they bypass the HTTP gate).
  • Metadata-authoring dogfoodpackage-first-authoring (5 passed) — end-to-end customization authoring unaffected.

Refs #3220. Follows #3213 / ADR-0049 / ADR-0092.

🤖 Generated with Claude Code


Generated by Claude Code

…sys_metadata (#3220)
Follow-through on #1591/#3213 for two non-better-auth managed objects that
shipped the same contradiction the better-auth reconciliation fixed: their
enable.apiMethods advertised generic create/update/delete while their managedBy
bucket forbids user-context writes, leaving the generic /data route open.
- sys_presence (append-only) advertised create/update/delete — update/delete on
an append-only object — but is written only over the realtime websocket/
in-memory path, never through ObjectQL. Narrowed to ['get','list'].
- sys_metadata (system) advertised full CRUD but overlays are authored only via
the metadata-protocol RPC (engine writes carry a transaction context, not a
user session); neither the framework nor the Console (objectui) POSTs
/data/sys_metadata. Narrowed to ['get','list'].
Reads stay open. The metadata-protocol / realtime write paths are engine-level
and bypass the HTTP exposure gate, so they are unaffected — verified by the
metadata-authoring dogfood (5 passed), the objectql overlay engine-insert tests
(6 passed), and metadata-core (100 passed).
A blast-radius audit found the broader system/append-only buckets are NOT safe
to guard wholesale: several system objects (sys_user_position,
sys_user_permission_set, sys_position_permission_set, sys_user_preference,
sys_import_job) are user-writable by design (delegated administration, user
preferences, imports). Generalizing the engine write guard to those buckets is
intentionally out of scope — the root cause is the overloaded system bucket,
tracked in #3220.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GwY68hKVysP98BX7i5eUoE
@vercel

vercelBot commented Jul 18, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 18, 2026 2:42pm

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/s labels Jul 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-core, packages/services.

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

  • content/docs/automation/webhooks.mdx(via packages/services)
  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/metadata-core)
  • content/docs/kernel/runtime-services/audit-service.mdx(via packages/services)
  • content/docs/kernel/runtime-services/index.mdx(via packages/services)
  • content/docs/kernel/runtime-services/settings-service.mdx(via packages/services)
  • content/docs/plugins/packages.mdx(via packages/services)
  • content/docs/protocol/kernel/i18n-standard.mdx(via packages/services)
  • content/docs/releases/v12.mdx(via @objectstack/metadata-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 marked this pull request as ready for review July 18, 2026 15:00
@os-zhuang
os-zhuang merged commit 06cb319 into mainJul 18, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/continue-task-1591-yqg8hb branch July 18, 2026 15:00
os-zhuang added a commit that referenced this pull request Jul 19, 2026
…ritable, guard engine-owned writes (#3220) (#3315)
The `system` bucket conflated two incompatible write policies — engine-owned
rows (never user-written) and platform-schema, admin/user-writable data — under
one all-false affordance row with no engine enforcement (only better-auth had a
write guard). This left a wildcard admin able to raw-write engine-owned rows
through the generic data API (ADR-0049 gap).
Rather than add a new managedBy enum value (which falls through to fully-editable
platform defaults on deployed Console clients), the write policy is now the
resolved affordance (resolveCrudAffordances = bucket default + userActions), and
"engine-owned" is defined as a system/append-only object granting no write:
- Writable set declares userActions{create,edit,delete}: the RBAC link tables,
sys_user_preference, sys_approval_delegation, and the messaging config grids
(notification preference/subscription/template). Affordance only — the
DelegatedAdminGate / RLS / permission sets remain the authz.
- Engine-owned objects locked to apiMethods ['get','list'] where absent (jobs,
notifications, approvals, record-share, automation-run, mail/settings/secret
audit, messaging delivery pipeline). sys_secret is read-locked explicitly (an
empty apiMethods array fails open).
- sys_import_job stays engine-owned: the REST import route writes job rows
isSystem-elevated (attribution preserved via explicit created_by).
- New engine write guard (assertEngineOwnedWriteAllowed, plugin-security)
fail-closed rejects user-context generic writes keyed off resolved
affordances; isSystem/context-less writes bypass. Wired into the security
middleware alongside the other data-layer gates.
- reconcileManagedApiMethods (objectql registry) now runs for every managed
bucket, stripping advertised write verbs the affordances forbid.
- /me/permissions clamp (plugin-hono-server) now clamps system/append-only too.
ADR-0103. Refs #3220, follows #3222 / ADR-0049 / ADR-0092.
Claude-Session: https://claude.ai/code/session_01Fp9yZxRQ3mb7p4vVwqFXKE
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/steststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude