feat: implement strict multi-surface access control - #25
Conversation
Collapses ad-hoc auth into three router-level surfaces:
public / No auth — home, A2A discovery, docs
private /manage Tailscale-only (100.64.0.0/10) — admin UI, workbench
api /api API key + hierarchical scopes (read/write) — M2M
Key changes:
- src/supervaizer/access/ — new package: client_ip.py (TRUSTED_PROXIES /
XFF), tailscale.py (require_tailscale dep), api_auth.py (API_KEYS,
require_api_key, require_scope); SUPERVAIZER_API_KEY preloaded as write
- src/supervaizer/routers/ — public_router, api_router, private_router
factories; router-level deps replace 14+ scattered Security() calls
- admin/routes.py — removed verify_admin_access, console tokens, APIKeyHeader;
Tailscale is the sole gate
- routes.py / data_routes.py — removed per-route Security(); write-mutating
endpoints get Depends(require_scope("write"))
- server.py — replaced scattered include_router block + AdminIPAllowlistMiddleware
with the three router factories
- templates — global /admin → /manage; removed ?key= URL params and
console-token JS; WebSocket workbench inherits Tailscale dep via private_router
- tests — updated paths (/api/supervaizer/..., /manage/...); new
test_access_client_ip.py, test_access_tailscale.py, test_access_api_auth.py;
deleted test_admin_ip_allowlist.py (replaced)
457 tests pass; pre-existing boto3/docker failures unaffected.
https://claude.ai/code/session_011Ansn4kxHVP8nLmLWESz36
Review Summary by QodoImplement strict multi-surface access control with Tailscale and API key scopes
WalkthroughsDescription• Implements strict multi-surface access control with three router-level authentication surfaces:
public (no auth), private/manage (Tailscale-only), and api (API key + hierarchical scopes)
• Creates new supervaizer.access package with client IP extraction, Tailscale CGNAT gating
(100.64.0.0/10), and API key authentication with read/write scope hierarchy
• Creates new supervaizer.routers package with three router factories (create_public_router,
create_private_router, create_api_router) that consolidate scattered authentication logic
• Removes ad-hoc per-route authentication: eliminates console tokens, verify_admin_access(),
scattered Security() calls, and AdminIPAllowlistMiddleware
• Replaces API key checks with scope-based enforcement: read-only endpoints require no auth on API
surface, write-mutating endpoints require require_scope("write")
• Preloads SUPERVAIZER_API_KEY environment variable as write-scope entry for server-to-server
communication
• Migrates all routes from /admin to /manage prefix for private surface; API routes use
/api/supervaizer/... prefix
• Removes API key and console token URL parameters from templates; relies on Tailscale for admin UI
access control
• Adds comprehensive test coverage for new access control modules: client IP extraction with proxy
trust, Tailscale IP range validation, and API key/scope enforcement
• Updates all existing tests to reflect new URL prefixes and authentication model (457 tests
passing)
Diagramflowchart LR
A["Request"] --> B{"Route Surface"}
B -->|"/ (public)"| C["create_public_router"]
B -->|"/api (M2M)"| D["create_api_router"]
B -->|"/manage (admin)"| E["create_private_router"]
C --> F["No Auth<br/>Home, A2A Discovery"]
D --> G["require_api_key<br/>+ require_scope"]
E --> H["require_tailscale<br/>100.64.0.0/10"]
G --> I["Read: No scope<br/>Write: write scope"]
H --> J["Admin UI<br/>Workbench<br/>Console"]
File Changes1. tests/test_admin_routes.py
|
Code Review by Qodo
1.
|
- Remove hard-coded API keys (key_123/key_456) from api_auth.py — API_KEYS is now empty by default; only SUPERVAIZER_API_KEY env var populates it - Add self type annotations (self: "ClassName") to all test class methods - Add return type Generator[None, None, None] to patch_trusted_proxies fixture - Update test_access_api_auth.py to inject test keys via patch.dict instead of relying on baked-in defaults https://claude.ai/code/session_011Ansn4kxHVP8nLmLWESz36
…ELOG.md showing overallstatus counts and runtime. clarifies current CI test results(502,0 skipped,0 failed, ~54) and preserves the abouttest file updates and access-test. The table addedimmediately0.14.2 release heading to make recent testresults visible to readers and maintainers.
- cli.py startup banner now prints /manage/ and "API key for /manage" - workbench-form.js default URL fallbacks updated to /manage/agents/... - workbench_routes.py log-filter string updated to /manage/ https://claude.ai/code/session_011Ansn4kxHVP8nLmLWESz36
When SUPERVAIZER_LOCAL_MODE=true, 127.0.0.1 and ::1 bypass the Tailscale CGNAT check so /manage/ is reachable during local development without a Tailscale connection. https://claude.ai/code/session_011Ansn4kxHVP8nLmLWESz36
Collapses ad-hoc auth into three router-level surfaces:
public / No auth — home, A2A discovery, docs
private /manage Tailscale-only (100.64.0.0/10) — admin UI, workbench
api /api API key + hierarchical scopes (read/write) — M2M
Key changes:
XFF), tailscale.py (require_tailscale dep), api_auth.py (API_KEYS,
require_api_key, require_scope); SUPERVAIZER_API_KEY preloaded as write
factories; router-level deps replace 14+ scattered Security() calls
Tailscale is the sole gate
endpoints get Depends(require_scope("write"))
with the three router factories
console-token JS; WebSocket workbench inherits Tailscale dep via private_router
test_access_client_ip.py, test_access_tailscale.py, test_access_api_auth.py;
deleted test_admin_ip_allowlist.py (replaced)
457 tests pass; pre-existing boto3/docker failures unaffected.
https://claude.ai/code/session_011Ansn4kxHVP8nLmLWESz36