Skip to content

v0.1.25.23 — last-mile webhook ownership boundary (#209)

Choose a tag to compare

@amavashev amavashev released this 12 Jul 10:46
5f69c1e

Security release — last-mile webhook ownership boundary (issue runcycles/cycles-server-admin#209).

The delivery worker now re-evaluates governance WEBHOOK SUBSCRIPTION INVARIANT 2 against the current event + subscription immediately before every outbound POST — initial delivery, retries, and recovered/orphaned-processing redeliveries all funnel through the single DeliveryHandler.handle() send path. A subscription owned by a concrete tenant (tenant_id present and != "__system__") is never delivered an admin-only event (api_key.* / policy.* / webhook.* / system.*).

This is the load-bearing half of the #209 fix. The admin plane blocks admin-only selectors at subscription-write and at enqueue, but the actual HTTP send happens here and never re-passes the enqueue gate — so deliveries queued before this version deployed, and every retry/redelivery, previously bypassed the boundary. Enforcing it at send time closes that hole regardless of what is already queued or stored.

  • Fail-closed, version-skew-safe classification: a concrete-tenant delivery is allowed only if every supplied selector positively classifies as tenant-accessible by raw string — the type must be in a tenant namespace (budget. / reservation. / tenant.) and the category must be exactly budget/reservation/tenant. A future/unknown admin type this worker's enum hasn't learned is still blocked. Blocked deliveries terminate (no send, no retry, distinct metric cycles_webhook_delivery_boundary_skipped_total), reusing the terminal FAILED status (no cross-service wire change). __system__-owned subscriptions are unaffected.

⚠ Rolling-deploy ordering (operational): this is a per-worker guarantee — airtight only once all delivery workers run 0.1.25.23 (or any remaining 0.1.25.22-or-earlier workers are stopped/drained). During a mixed-version rollout an old worker can still claim and send a violating queued delivery. Deploy and fully roll out this version BEFORE deploying cycles-server-admin 0.1.25.51 (which begins blocking at the admin plane), so no window exists where events are queued-and-blocked at admin but still sent by an un-upgraded worker.

Compatibility: no HTTP/Redis/wire/schema change; the boundary is a delivery-time decision using data already loaded. Legitimate deliveries (tenant-accessible events to tenant subscriptions; any event to __system__ subscriptions) are unaffected.

Governance spec: WEBHOOK SUBSCRIPTION INVARIANT 2 (cycles-governance-admin v0.1.25.40/.41). Coordinated with cycles-server-admin #210 (0.1.25.51). 359 tests green; JaCoCo ≥95%; external security review (codex) SHIP. Full details: CHANGELOG.md [0.1.25.23], AUDIT.md, PR #110.