Skip to content

P0 security: enforce-or-remove unenforced security props (ADR-0049)#1899

Merged
os-zhuang merged 4 commits into
mainfrom
fix/p0-unenforced-security-props-1878
Jun 15, 2026
Merged

P0 security: enforce-or-remove unenforced security props (ADR-0049)#1899
os-zhuang merged 4 commits into
mainfrom
fix/p0-unenforced-security-props-1878

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What

Addresses the P0 security cluster of the metadata liveness audit umbrella #1878, under the new ADR-0049 (docs/adr/0049-no-unenforced-security-properties.md): a spec property naming a security boundary must be enforced, explicitly experimental, or absent — never parsed-but-silently-unenforced.

Given the pre-MVP stage, the cluster is split by whether the feature already exists — we do not build enforcement for features that don't exist yet.

PR A — no-regret fixes (commit 1)

  • Evaluator fails CLOSED for the destructive op class (transfer/restore/purge). Previously any operation absent from OPERATION_TO_PERMISSION fell through to default-allow (permission-evaluator.ts), so a future destructive op added without a map entry would be silently ungated.
  • allowTransfer/allowRestore/allowPurge marked [EXPERIMENTAL — not enforced] (permission.zod.ts) — the operations they gate don't exist yet and nothing reads them ([P0][security] Permission lifecycle ops ungated (allowTransfer/allowRestore/allowPurge) #1883). Re-introduced with the feature at M2.

PR B — enforce live-but-ungated gaps (commit 2)

  • [P0][security] Agent access control is a no-op (permissions/visibility/access) #1884 agent access — chat route only enforced static route-level perms; each agent's access/permissions were a no-op. New pure evaluateAgentAccess (required-perms ALL · allow-list by userId/role · fail-closed) wired into the chat route. visibility is intentionally not enforced (request context has no tenant/owner) — documented, not half-done.
  • [P0][security] Object apiEnabled/apiMethods not enforced by REST #1889 object API exposureapiEnabled/apiMethods were ignored by the HTTP/MCP dispatch. New pure checkApiExposure gate in callData: apiEnabled:false → 404, non-whitelisted op → 405. System contexts bypass; unresolvable defs fall open to schema defaults (pure additive restriction, no regression).

Deferred (tracked, not in this PR)

Tests

  • permission-evaluator destructive fail-closed: green (plugin-security 87, spec 152).
  • evaluateAgentAccess: 9 unit tests. checkApiExposure: 8 unit tests.
  • One pre-existing unrelated failure in http-dispatcher.test.ts (publish-drafts applies seed rows) reproduces with this PR's changes stashed — not introduced here.

Refs #1878 #1882 #1883 #1884 #1889

🤖 Generated with Claude Code

os-zhuang and others added 4 commits June 15, 2026 15:06
cel-js's `check()` returns a `TypeCheckResult` object (`{ valid, error }`),
not an array, so `compile()`'s `Array.isArray(checkErrors)` guard never
matched and the type-check verdict was silently discarded. A condition
calling an unknown function (`PRIOR(status)`, a typo'd `isBlnk(...)`)
type-checks as `found no matching overload`, but that result never
surfaced — so `objectstack compile`, `registerFlow`, and the
`validate_expression` tool all accepted the predicate, which then
silently no-op'd the flow at runtime.

Read the documented `{ valid, error }` shape instead. Because every
author surface routes through this one `compile()`, the fix closes the
gap for flow conditions, validation rules, and field formulas at once.
Verified cel-js's `check()` cleanly separates valid predicates from
unknown-function ones with no false positives on existing conditions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion bits experimental (ADR-0049)

The metadata liveness audit (#1878) found a cluster of security properties that
parse but enforce nothing — a false sense of compliance. ADR-0049 establishes
the enforce-or-remove gate: a security property must be enforced, explicitly
`experimental`, or absent — never parsed-but-silently-unenforced.

This is the no-regret slice (PR A) of the P0 cluster:

- PermissionEvaluator now fails CLOSED for the destructive operation class
  (transfer/restore/purge). Previously any operation absent from
  OPERATION_TO_PERMISSION fell through to default-allow, so a future destructive
  op added without a map entry would be silently ungated. Non-destructive
  unknown ops retain default-allow (custom read-side ops unaffected).
- ObjectPermission allowTransfer/allowRestore/allowPurge marked
  `[EXPERIMENTAL — not enforced]` in the spec: the operations they gate do not
  yet exist and no runtime consumer reads them. Re-introduced with enforcement
  when the feature lands (#1883).
- Adds a test asserting destructive ops fail closed even for a fully-
  permissioned set.

Refs #1878 #1883
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… object API exposure (ADR-0049)

PR B of the #1878 P0 cluster: the items where the feature already exists but the
gate was missing or bypassed (vs PR A, which neutralised dangling spec promises).

#1884 — agent access control was a no-op. The chat route only enforced the
static route-level permissions; each agent's own `access`/`permissions` were
never consulted, so "who can chat with this agent" enforced nothing. Adds a pure
`evaluateAgentAccess` (required-permissions ALL, allow-list match by userId/role,
fail-closed on missing user) wired into the chat route after the active check.
`visibility` (organization/private) is intentionally not enforced here — the
request context carries no tenant/owner — and that gap is documented rather than
half-enforced.

#1889 — object `apiEnabled`/`apiMethods` were ignored by the HTTP/MCP data
dispatch; an object could not be hidden from the API nor its methods restricted.
Adds a pure `checkApiExposure` gate in `callData`: `apiEnabled:false` → 404,
non-whitelisted op → 405. System/internal contexts bypass; an unresolvable
definition falls open to the schema defaults (apiEnabled defaults true), so the
gate is a pure additive restriction with no regression.

Both helpers are unit-tested (agent-access 9, api-exposure 8). The action
`disabled`-CEL gap (#1885) lives in the objectui renderer and is fixed there in
a separate commit.

Refs #1878 #1884 #1889
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 15, 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 15, 2026 7:38am

Request Review

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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant