test(security): access-control regression gate for the dioxus example

test(security): access-control regression gate for the dioxus example

#8 in tonybierman/arium — merged 2026-05-25

What

Commits the inventory-driven access-control probe and wires it into CI as a blocking gate, so a future refactor that silently un-gates an endpoint turns the build red. This is the durable artifact behind the access-control review we did against dioxus-fullstack-example.

The probe — examples/dioxus-fullstack-example/access-control-probe.sh

Generated from the live route_map / openapi_spec / project_index inventory (not guessed), it checks three axes against a running instance:

Phase Property
1 Every protected endpoint denies an anonymous caller; public endpoints stay reachable (catches inventory drift); anon-tolerant endpoints (profile, logout, mfa-status) return a benign default, never real state.
2 Every /api/admin/* refuses a logged-in non-admin — vertical privilege escalation.
3 One user cannot reach another's object by id — horizontal / IDOR: B cannot revoke A's API token, with a control that proves the id was real.

Design notes baked in from the investigation:

  • Tolerates Dioxus-fullstack's wire-500-on-ServerFnError convention by asserting "denied" via the body marker, and distinguishes a confirmed auth-gate hit from a weaker non-2xx.
  • Rides the tower_governor rate limiter (burst 30 / 1·s⁻¹) with 429-aware retries so every request actually reaches its gate.

Verified locally end-to-end: 38/38 PASS against a running instance, including Phase 2 (all 9 admin endpoints refuse a non-admin) and Phase 3 (IDOR isolation holds).

CI — access-control job in ci.yml

  • Builds the example server-only (--no-default-features --features server) to skip the web/wasm devtools stack — the reason the test job excludes this example.
  • Boots it headless. Two non-obvious bits discovered while wiring this: the server bind reads plain IP/PORT (not DIOXUS_DEVSERVER_PORT), and dioxus-server needs a <bin>/public dir that a raw cargo build (unlike dx build) doesn't create — an empty dir satisfies it.
  • Waits for readiness, runs the probe with a seeded non-admin, dumps the server log on failure.

This PR is intentionally the validation vehicle: watch the new access-control job go green here before merging, since it can't be exercised on GitHub runners until it runs.

Not included

ffuf-discover.sh (ad-hoc recon helper) is left untracked — this PR is scoped to the regression gate.

🤖 Generated with Claude Code

Last updated 2026-05-26