Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/qa/dogfood/test/authz-conformance.matrix.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,7 +148,7 @@ export const AUTHZ_CONFORMANCE: AuthzPrimitive[] = [
covers: ['actions:domains/actions.ts:anonymous-gate'],
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.' },
{ id: 'anonymous-deny-automation', summary: 'anonymous-deny on the automation/flow surface (#2567 surface 3 / #5519)', state: 'enforced',
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',
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',
proof: 'showcase-anonymous-deny-surfaces.dogfood.test.ts',
covers: ['automation:domains/automation.ts:anonymous-gate'],
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.' },
Expand Down
9 changes: 6 additions & 3 deletions packages/runtime/src/route-ledger.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -316,7 +316,8 @@ export const ROUTE_LEDGER: readonly RouteLedgerEntry[] = [
{ route: 'POST /automation/trigger/:name', domain: '/automation', disposition: 'sdk', client: 'automation.trigger',
note: 'legacy verb-first shape; duplicates execute() against a different URL — candidates for consolidation' },
{ route: 'GET /automation', domain: '/automation', disposition: 'sdk', client: 'automation.list' },
{ route: 'POST /automation', domain: '/automation', disposition: 'sdk', client: 'automation.create' },
{ route: 'POST /automation', domain: '/automation', disposition: 'sdk', client: 'automation.create',
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`" },
{ route: 'GET /automation/actions', domain: '/automation', disposition: 'sdk', client: 'automation.listActions' },
{ route: 'GET /automation/connectors', domain: '/automation', disposition: 'sdk', client: 'automation.listConnectors' },
{ route: 'GET /automation/_status', domain: '/automation', disposition: 'sdk', client: 'automation.getRuntimeStatus' },
Expand All@@ -328,8 +329,10 @@ export const ROUTE_LEDGER: readonly RouteLedgerEntry[] = [
{ route: 'GET /automation/:name/runs/:runId', domain: '/automation', disposition: 'sdk', client: 'automation.getRun' },
{ route: 'GET /automation/:name/runs', domain: '/automation', disposition: 'sdk', client: 'automation.listRuns' },
{ route: 'GET /automation/:name', domain: '/automation', disposition: 'sdk', client: 'automation.get' },
{ route: 'PUT /automation/:name', domain: '/automation', disposition: 'sdk', client: 'automation.update' },
{ route: 'DELETE /automation/:name', domain: '/automation', disposition: 'sdk', client: 'automation.delete' },
{ route: 'PUT /automation/:name', domain: '/automation', disposition: 'sdk', client: 'automation.update',
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" },
{ route: 'DELETE /automation/:name', domain: '/automation', disposition: 'sdk', client: 'automation.delete',
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" },

// ── auth (better-auth passthrough) ────────────────────────────────────────
{ route: '* /auth/**', domain: '/auth', disposition: 'sdk', client: 'auth.me',
Expand Down
Loading