You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d5e7b9f
Browse filesBrowse the repository at this point in the historyBrowse files
docs(runtime,qa): record the /automation manage_metadata write gate in the two ledgers that describe the domain (#10600)
The #10145 authoring-write gate shipped and is pinned, but neither
hand-maintained ledger that describes the /automation domain's
authorization posture says so, and both exist precisely so the next
author finds the decision they are standing on.
- route-ledger.ts: the three write rows (POST /automation, PUT and
DELETE /automation/:name) each carry a note in the resume row's own
convention — the capability, the fail-closed construction, the 403
PERMISSION_DENIED refusal, the isSystem bypass, and the one
isFlowAuthoringWrite predicate that selects them.
- authz-conformance.matrix.ts: the anonymous-deny-automation row's
enforcement prose gains the per-route capability clause in the
/packages sibling's shape, so the domain's second gate stops being
invisible to a reader of the ledger.
Prose only. No behaviour changes, no ratchet moves, no covers key
added — the discover() probe for the matrix file matches
shouldDenyAnonymous( only, so this gate adds no discovered key.
Part of #10244
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
Co-authored-by: Claude <noreply@anthropic.com>
note: 'A `type: \'script\'` action body runs `isSystem: true` (elevated), so an ungated POST was an anonymous privilege-escalating WRITE, not merely an information leak — #5519 measured `POST /actions/showcase_task/showcase_mark_done/:id` answering 200 with the update applied. Internal dispatch is unaffected: this handler is a pure HTTP seam (the MCP `run_action` bridge enters through action-execution.invokeBusinessAction, declarative endpoints through the transport fallback seam with their own `authRequired` gate), so `authRequired: false` public endpoints stay public.'},
150
150
{id: 'anonymous-deny-automation',summary: 'anonymous-deny on the automation/flow surface (#2567 surface 3 / #5519)',state: 'enforced',
151
-
enforcement: 'runtime/domains/automation.ts handleAutomationRequest — shouldDenyAnonymous DOMAIN-WIDE at the top, and deliberately BEFORE the isServiceServeable probe so the 401/501 difference cannot be used to fingerprint whether a deployment mounts automation',
151
+
enforcement: 'runtime/domains/automation.ts handleAutomationRequest — shouldDenyAnonymous DOMAIN-WIDE at the top, and deliberately BEFORE the isServiceServeable probe so the 401/501 difference cannot be used to fingerprint whether a deployment mounts automation; per-route capability predicates run after this floor — `manage_metadata` for the three flow-AUTHORING writes (create `POST /` / update `PUT /:name` / deregister `DELETE /:name`, selected by the one `isFlowAuthoringWrite` predicate, #10145), fail-closed by construction (an absent executionContext, an absent `systemPermissions` or an empty one all refuse) and answering 403 `PERMISSION_DENIED`, with only engine `isSystem` bypassing; the run-state reads (#7900) and `resume` (#3801 / #5561) carry their own separate per-route predicates, and the execution doors (trigger / execute / toggle) sit outside all of them',
note: 'Ungated, an anonymous caller could start real flow runs (`POST /:name/trigger`), read the full flow inventory (`GET /automation`), and DEREGISTER a registered flow (`DELETE /:name` → `{deleted:true}`) — the destructive one, which #5519 did not originally record. Gating the DOMAIN rather than each route is what keeps a newly added automation route from arriving ungated. Engine-internal triggers (record-change, schedule) never speak HTTP and are untouched.'},
note: "authored metadata, so `manage_metadata` gates it (#10145): a flow definition lives on the metadata plane (ADR-0106), and this door now asks the capability every other door onto that plane already asks. Fail-closed by construction — an absent executionContext, an absent `systemPermissions` or an empty one all fall through to the refusal, 403 with code `PERMISSION_DENIED` (ADR-0112); only engine self-invocation (`isSystem`, never settable from the wire) bypasses. WHICH routes is one predicate, `isFlowAuthoringWrite` in `domains/automation.ts` — this row plus PUT/DELETE `/:name` below, with the execution doors (trigger / execute / toggle / resume) deliberately outside it. Second layer, not the first: the #5519 anonymous floor answers an unidentified caller 401 here, not 403. Pinned in `domains/automation-write-capability-gate.test.ts`"},
note: "authored metadata, so `manage_metadata` gates it (#10145) — the same `isFlowAuthoringWrite` door as `POST /automation` above: fail-closed on an absent executionContext, an absent `systemPermissions` or an empty one, refusing 403 `PERMISSION_DENIED`, with only `isSystem` bypassing"},
note: "authored metadata, so `manage_metadata` gates it (#10145) — the same `isFlowAuthoringWrite` door as `POST /automation` above, and the destructive member of the family: before the gate a tenant org owner WITHOUT the capability deregistered a registered flow, 200, and flow metadata is registered at ENVIRONMENT scope so the write crossed the tenant wall. Fail-closed on an absent executionContext, an absent `systemPermissions` or an empty one, refusing 403 `PERMISSION_DENIED`, with only `isSystem` bypassing"},
0 commit comments