Skip to content

docs(adr): ADR-0106 — metadata-plane FLS, per-caller masking of object schemas (#3661 step ③) - #3678

Merged
os-zhuang merged 3 commits into
mainfrom
claude/metadata-field-level-permissions-sr37zy
Jul 27, 2026
Merged

docs(adr): ADR-0106 — metadata-plane FLS, per-caller masking of object schemas (#3661 step ③)#3678
os-zhuang merged 3 commits into
mainfrom
claude/metadata-field-level-permissions-sr37zy

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Step ③ of #3661, as an ADR (status: Proposed). Steps ① ② shipped client-side as objectstack-ai/objectui#2866.

Why the decision flipped

An earlier disposition ("target deployments have no untrusted authenticated users → document no-masking as the explicit default") died on the platform premise: ObjectStack customers build portals and products whose authenticated callers the platform cannot vouch for. Per ADR-0049, enforcement must therefore be the platform default — so this ADR decides to mask, and resolves the three hard problems the issue flagged (cache key, protocol context, exemptions) instead of documenting their absence.

Decision summary

  • D1 — Object-schema reads project fields onto security.getReadableFields(object, caller); masked fields are removed entirely (name, label, options, formula, visibleWhen, requiredPermissions — the leak is more than names). readable only; editable stays with /auth/me/permissions, so no getEditableFields dual is needed.
  • D2 — Enforcement lives in the dispatch layer (REST /meta + runtime /metadata); metadata-protocol signatures unchanged.
  • D3 — Answer to the issue's §4 "structural obstacle": the shared cache keeps one full copy per object; masking runs after cache retrieval; the caller's field-visibility fingerprint (hash of the denied set, empty for unrestricted callers) folds into the ETag. Cohort-correct 304s, zero regression for unrestricted callers, no O(users × objects) blowup. Full cache bypass (doc/book pattern) is recorded as implementation fallback, not the end state.
  • D4isSystem / platform-admin exemption (Studio/Setup authoring needs the full schema).
  • D5 — Coverage: single read (cached and uncached paths), list read, runtime catch-all, plus an audit of other schema-bearing outlets.
  • D6 — Three-tier failure posture: no security service → open (deployment has no FLS posture); undefined → open with telemetry + private, no-store (matches the engine-middleware and security 服务 getReadableFields 查询面(export 列投影的长期正解) #3547 export precedents; window is operational, not attacker-inducible; failing closed risks a bootstrap deadlock since permission sets are themselves metadata); evaluation throws5xx — never the unmasked body on an error path, never an empty-fields 200 (silently wrong and cacheable poison). Includes the reconciliation with ADR-0049 and why doc/book fail-closed is a different class (content access control vs disclosure control on infrastructure).
  • D7 — Zero-permission-set callers resolve the fallback set (member_default/guest), aligning with /auth/me/permissions — public deployments' schema exposure becomes a deliberate permission-set decision.
  • D8 — Default on, ships with the current major (same train as objectui#2866's breaking removal), config escape hatch; the client-side gates from ① remain complementary, not superseded.

Verification

Docs-only change. Cross-linked ADR files (0046/0049/0066/0090) verified present; no ADR index file requires registration.

Implementation (D1–D5 + tests in rest/runtime/plugin-security) is deliberately a separate PR once this is accepted.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AEb4XCVb7iLEBVe9HghjTt


Generated by Claude Code

…t schemas (#3661 step ③)

Object-schema reads currently ship the full schema (field names, labels,
picklist option values, formula expressions, visibleWhen predicates,
requiredPermissions capability names) to any authenticated caller — the
metadata-plane twin of the export-header leak the #3391 series closed on
the data plane. ObjectStack is a development platform: whether a
deployment's authenticated callers are trusted is the customer's call,
so the platform must enforce rather than assume.

Decisions (Proposed):
- D1 mask by readable projection, remove masked fields entirely
- D2 enforce in the dispatch layer; metadata-protocol stays caller-free
- D3 mask-after-cache with a field-visibility fingerprint folded into
  the ETag (single full copy per object, cohort-correct 304s)
- D4 isSystem / platform-admin exemption (Studio needs full schema)
- D5 coverage: single read (cached + uncached), list read, runtime
  /metadata catch-all, plus an outlet audit
- D6 three-tier failure posture: no-service → open; undefined → open
  with telemetry + private cache headers; evaluation throws → 5xx
  (never the unmasked body on an error path, never an empty-fields 200)
- D7 zero-set callers resolve the fallback permission set
- D8 default on, current major, config escape hatch

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

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 27, 2026 2:09pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Jul 27, 2026
Distills what the disclosure boundary is worth: the claimable FLS
invariant (pentest/questionnaire/Salesforce-describe parity), the
platform-leverage shape (customers cannot fix /meta exposure in their
own tier), unlocked mixed-sensitivity modeling (no object splitting for
portal scenarios), and schema shape as business information — against a
deliberately near-zero cost side (D3/D8).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AEb4XCVb7iLEBVe9HghjTt
@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 14:12
@os-zhuang
os-zhuang merged commit 2eb1b70 into main Jul 27, 2026
14 checks passed
@os-zhuang
os-zhuang deleted the claude/metadata-field-level-permissions-sr37zy branch July 27, 2026 14: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/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants