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
test(dogfood): cover /actions and /automation in the anonymous-deny proof artifact (#5570) (#5631)
`authz-conformance.matrix.ts` names
`showcase-anonymous-deny-surfaces.dogfood.test.ts` as the proof artifact for
#2567's "anonymous posture is uniform across HTTP surfaces" claim, but the
suite drove only `/data` and `/meta`. #5519 found the claim false on exactly
the two surfaces it did not drive — the dispatcher-mounted `/actions` and
`/automation` — and the artifact was silent throughout.
PR #5569 built the gate in `packages/runtime`; this is the evidence half.
- six new anonymous cases on the shared showcase boot: POST a `script`
action, POST `/automation/:name/trigger`, GET `/automation`, DELETE
`/automation/:name` (all 401), plus the two authenticated contrasts.
- one case pins that all four surfaces answer the same code and message,
reading each family in its own declared envelope rather than through a
tolerant `??` chain.
- two matrix rows (`anonymous-deny-actions`, `anonymous-deny-automation`)
with their `covers` keys, ratchet probes for both gates, and a `(h)`
bites case, so deleting either gate fails CI as STALE covers.
No `packages/runtime` change: this adds proof, not defence.
Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
Co-authored-by: Claude <noreply@anthropic.com>
enforcement: 'rest/rest-server.ts registerMetadataEndpoints guarded registrar (enforceAuth → shouldDenyAnonymous) — every /meta route inherits the gate; runtime/http-dispatcher.ts handleMetadata mirrors it for the dispatcher metadata catch-all',
// #5519 — the two DISPATCHER-mounted execution surfaces. `@objectstack/rest`
93
+
// gated `/data` and `/meta`; these routes are mounted by a SECOND
94
+
// registration path (dispatcher-plugin.ts, straight onto the host
95
+
// IHttpServer) and inherited none of it, so the "#2567 uniform posture"
96
+
// claim above was false on them until PR #5569. The proof artifact was
97
+
// silent too — #5570 is the evidence half, and these two rows are what make
98
+
// the gate's removal fail CI instead of review.
99
+
{id: 'anonymous-deny-actions',summary: 'anonymous-deny on the business-action dispatch surface (#2567 surface 2 / #5519)',state: 'enforced',
100
+
enforcement: 'runtime/domains/actions.ts handleActionsRequest — shouldDenyAnonymous as the handler\'s FIRST statement, ahead of the ADR-0066 D4 requiredPermissions gate and the ADR-0104 param contract; those keep their semantics and simply run after the auth baseline, so an anonymous caller never reaches action dispatch and never learns the route\'s shape',
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.'},
104
+
{id: 'anonymous-deny-automation',summary: 'anonymous-deny on the automation/flow surface (#2567 surface 3 / #5519)',state: 'enforced',
105
+
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',
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.'},
0 commit comments