Skip to content

feat(api): remote management API — Phase 1 foundation (api_keys + Bearer /api/v1)#22

Merged
nechodom merged 2 commits into
mainfrom
feat/remote-api-p1-integrated
Jul 1, 2026
Merged

feat(api): remote management API — Phase 1 foundation (api_keys + Bearer /api/v1)#22
nechodom merged 2 commits into
mainfrom
feat/remote-api-p1-integrated

Conversation

@nechodom

Copy link
Copy Markdown
Owner

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_keys table (migration 053, master-only) + state module: keys are hyp_<32B CSPRNG base62>, stored only as SHA-256, caps clamped server-side to the owner's effective caps, revoke + expiry enforced.
  • ApiKeysManage capability (appended; bit numbering stable) — SuperAdmin/Admin only.
  • Bearer → AuthCtx: Authorization: Bearer hyp_… resolves to an api-key-backed AuthCtx.
  • /api/v1 (Bearer-only router branch): GET me, hostings, hostings/:id, nodes, jobs/:id, each capability-gated, JSON error envelope (401/403/404/502).
  • Settings → API keys card: create (label + capability scope + optional expiry, revealed once) + list + revoke.

Adversarial review verdict

  • The critical one is clean: Bearer auth is confined to /api/v1 — an API key cannot authenticate the cookie/CSRF-protected web routes (require_auth is session-only; /api/v1 rejects cookies). Key gen/hash/owner-clamp/revoke/expire/secret-logging all verified solid.
  • Fixed (this PR's last commit): MAJOR — refuse to mint a key for a non-scope_all owner (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 warnings clean; fmt --check clean; full suite green incl. the api_v1_bearer_auth_and_cap_gating e2e.

🤖 Generated with Claude Code

mkn and others added 2 commits July 1, 2026 00:22
…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>
@nechodom nechodom merged commit 5b29764 into main Jul 1, 2026
1 check passed
@nechodom nechodom deleted the feat/remote-api-p1-integrated branch July 1, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant