feat(api): remote management API — Phase 1 foundation (api_keys + Bearer /api/v1)#22
Merged
Merged
Conversation
…v1 (P1a)
Implement the FOUNDATION slice of the remote management API
(docs/superpowers/specs/2026-06-30-remote-management-api-design.md),
scoped to READ + key-management. Write/lifecycle endpoints are deferred
to P1b (marked with // TODO(api-p1b)).
- migration 053: `api_keys` table (master-only, like web_sessions) —
SHA-256 key_hash, key_prefix, label, owner_user_id, caps, scope_all,
created/last_used/expires/revoked timestamps.
- hyperion-state::api_keys: create (hyp_<32B base62> via CSPRNG, returns
raw once + stores only SHA-256; caps clamped to owner), resolve_active
(rejects revoked/expired), list (no hash/raw), touch, revoke. +sha2 dep.
- New capability ApiKeysManage (appended last — bit numbering stable;
granted to SuperAdmin/Admin via CapSet::all).
- RPC plumbing mirroring web_session_*: ApiKey{Create,List,Resolve,
Touch,Revoke} across trait/codec/server/EchoApi/agent/service/hctl.
- Bearer auth → AuthCtx: Authorization: Bearer hyp_… → SHA-256 →
ApiKeyResolve; AuthCtx gains api_key ctx; caps()/scope_all()/username
read it; best-effort touch.
- /api/v1 router (no require_auth, no check_csrf): GET me/hostings/
hostings/:id/nodes/jobs/:id; cap-gated; JSON error envelope (401/403/
404); security_headers applied.
- Settings "API keys" card: create form (label + capability groups +
expiry) reveals raw key once, list + revoke; gated by ApiKeysManage.
- Tests: state unit tests (format/hash, revoked+expired rejection, caps
clamp); web e2e (valid→200, none/bad→401, missing-cap→403).
Verified on lima hyp VM: cargo clippy --workspace --all-targets
-D warnings clean, cargo fmt --all --check clean, tests pass
(hyperion-state/web/core).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…a cookie not URL Adversarial review of the /api/v1 foundation found two issues (the Bearer→cookie boundary itself was verified CLEAN — api keys can't authenticate cookie/CSRF web routes): - MAJOR: the deferred tenant scoping is reachable today. A custom role with ApiKeysManage but scope_all=false could mint a key that the (not-yet-scoped) read endpoints would let read the whole cluster's hosting inventory. Close it server-side in api_key_create: refuse to mint when the owner lacks cluster-wide scope (built-in Admin/SuperAdmin are scope_all, unaffected). Per-tenant keys + real read-scoping land together in p1b. - MINOR: the freshly-minted raw key rode in the redirect URL (?api_key_new=), landing in browser history + proxy access logs. Reveal it once via a short-lived HttpOnly cookie that the settings page reads and clears instead. clippy --workspace -D warnings clean; fmt clean; full suite green (state 154, web 142 + e2e 27 incl. api_v1 bearer/cap test, core). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First slice of the remote management API (issue #1 follow-up / design doc
2026-06-30-remote-management-api-design.md). Built by an agent, then adversarially reviewed; the review's findings are fixed in the final commit.What
api_keystable (migration 053, master-only) + state module: keys arehyp_<32B CSPRNG base62>, stored only as SHA-256, caps clamped server-side to the owner's effective caps, revoke + expiry enforced.ApiKeysManagecapability (appended; bit numbering stable) — SuperAdmin/Admin only.Authorization: Bearer hyp_…resolves to an api-key-backedAuthCtx./api/v1(Bearer-only router branch):GET me, hostings, hostings/:id, nodes, jobs/:id, each capability-gated, JSON error envelope (401/403/404/502).Adversarial review verdict
/api/v1— an API key cannot authenticate the cookie/CSRF-protected web routes (require_authis session-only;/api/v1rejects cookies). Key gen/hash/owner-clamp/revoke/expire/secret-logging all verified solid.scope_allowner (the read endpoints aren't tenant-scoped yet, so a scoped key could read the whole cluster); MINOR — reveal the raw key via a short-lived HttpOnly cookie instead of the redirect URL (history/proxy-log leak).Deferred to P1b (flagged
TODO(api-p1b))Write/lifecycle endpoints (create/delete/suspend/resume → background job), per-tenant read scoping, and per-key job scoping land together next.
Test
clippy --workspace -D warningsclean;fmt --checkclean; full suite green incl. theapi_v1_bearer_auth_and_cap_gatinge2e.🤖 Generated with Claude Code