P0 security: enforce-or-remove unenforced security props (ADR-0049)#1899
Merged
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
17 tasks
This was referenced Jun 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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, explicitlyexperimental, 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)
transfer/restore/purge). Previously any operation absent fromOPERATION_TO_PERMISSIONfell through to default-allow (permission-evaluator.ts), so a future destructive op added without a map entry would be silently ungated.allowTransfer/allowRestore/allowPurgemarked[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)
access/permissionswere a no-op. New pureevaluateAgentAccess(required-perms ALL · allow-list by userId/role · fail-closed) wired into the chat route.visibilityis intentionally not enforced (request context has no tenant/owner) — documented, not half-done.apiEnabled/apiMethodswere ignored by the HTTP/MCP dispatch. New purecheckApiExposuregate incallData: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)
runAs→ mark experimental / build with the feature at M2.disabledCEL → lives in the objectui renderer; fixed in fix(action): consume specdisabled(boolean|CEL) in action button (framework#1885) objectui#1743.Tests
permission-evaluatordestructive fail-closed: green (plugin-security 87, spec 152).evaluateAgentAccess: 9 unit tests.checkApiExposure: 8 unit tests.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