Nine route files each loaded the live app and checked access, with
four return shapes, docstrings promising error tuples the code never
returned, and 41 dead 'if err: return err' branches. require_app()
backed by ResourceGrantService.get_app_for does it once; conceal=True
keeps the 404-for-denied answer the previews, WAF and slot routes
already gave, so no route's status changes. Nine query sites leave the
API layer.
The connect-session route authenticates by the loopback-only Cloud
grant rather than a session, and the tunnel's Socket.IO bridge starts
two raw threads; both guards failed CI until they were recorded.
Two nets, deliberately complementary, because neither alone would have caught
the advisory:
- `test_route_authz_sweep.py` fires every mutating route as a viewer and
fails on any 2xx outside a reviewed `VIEWER_WRITABLE` list. It proves
*enforcement*, but a route that 400s or 404s on a dummy request before its
gate runs is invisible to it — `POST /workspaces/` 400s on an empty body,
so the net would never have seen the bug it exists for. Hence the targeted
Part A: the fixed routes must answer a viewer and a developer with 403, and
must not block an admin.
- `test_route_authz_static.py` proves *coverage* from the source instead: every
mutating route in `url_map` either carries a `_sk_authz` decorator, reaches
an authorization primitive through its module's call graph, or sits in
SELF_SCOPED / PUBLIC_ALT_AUTH / STATELESS with a written justification.
Staleness checks keep an allowlist slot from outliving the route that
earned it, and a SELF_SCOPED entry must still resolve the caller's identity
— calling `get_current_user()` is necessary but not sufficient, which is
exactly how `create_workspace` looked self-scoped while creating a global
row.
Both nets are blind below the API layer and to public routes, so
`test_authz_round3_guards.py` covers the three fixes whose shape they cannot
express (API-key scope widening, demo-deploy job creation, nginx diff
containment), the webhook cases land beside the rest of the gateway surface,
and the shared-resource tag gate lands with its own blueprint's tests. Each
comes with its non-vacuity case: the same call succeeds for a caller the gate
admits.
`migrations/history` gets tests because it had none — which is why the
NameError in it shipped.
Ratchets: JWT-only census 605 -> 577 (the routes that moved to policy
decorators), the controller-boundary baseline loses the one persistence
crossing `complete_onboarding` no longer makes, and the collected-test floor
rises to 4431.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>