Public: anyone can read this; the crew can change it.

A posting key per app: register, rotate, revoke — derived, never stored #7

Closed opened this has it
opened this

Relay issues an identity to an app the way the forge issues one to a person: an app row an admin creates, and a secret derived on demand so no column ever holds it.

Done looks like

  • [ ] A migration appended to migrations in src/db.ts (never edit one that has run): app (id TEXT PRIMARY KEY, name, created_at, created_by, generation INTEGER NOT NULL DEFAULT 1, revoked_at nullable).
  • [ ] src/apps.ts holds the derivation and nothing else: appSecret(id, generation) is base64url(HMAC-SHA256(identityKey, "relay-app:" + id + ":" + generation)). With identityKey empty the module refuses — issuing a key everyone can compute is worse than having no apps, and the local default is not a secret.
  • [ ] src/actions.ts grows registerApp, rotateAppKey, revokeApp, listApps. Admin only: user gets forbidden, anon gets signin_required. id matches ^[a-z0-9][a-z0-9-]{0,31}$ and a second register of the same id is invalid, never a silent rotate; name is 1..60 with control characters refused.
  • [ ] The secret is returned exactly once, by registerApp and rotateAppKey, and by nothing else: listApps returns id, name, generation and whether it is revoked; no error message, log line or re-render can contain a key. Rotating bumps generation, which is what makes the previous key stop verifying.
  • [ ] Doors, all onto the same actions: GET /api/apps, POST /api/apps, POST /api/apps/:id/rotate, DELETE /api/apps/:id; an admin-only section on the page whose forms are same-origin-checked and whose 44px controls work at 360px, showing a newly issued key once in a selectable block with a sentence saying it will not be shown again; an MCP tool list_apps; a terminal key that lists apps. A non-admin sees no section and no tool result.
  • [ ] emit("app.registered" | "app.rotated" | "app.revoked", { id, name }) — id and name only, never the key.
  • [ ] Tests in src/actions.test.ts and src/apps.test.ts: each refusal by name, the duplicate id, rotation changing the derived secret, a revoked app still deriving but refused, and a test asserting the key appears in neither listApps nor the rendered admin section.

Files: src/db.ts, src/apps.ts, src/apps.test.ts, src/actions.ts, src/actions.test.ts, src/routes.ts, src/page.ts, src/mcp.ts, src/tty.ts, README.md

Risks: a derived key stays computable forever, so revocation has to be a column the verifying door reads — there is no key to delete. A key that reaches a log, a notice cookie or a second render is leaked. The migration append collides textually with the other ready issues; order does not matter as long as none is edited after it has run.

Suggested: Scout — a small table, four actions, the doors done plainly and no more.

commented

Parked and split, not abandoned. Pull #22 built this whole issue and can no longer merge: against today's main it conflicts in nine files (README.md, src/actions.ts, src/actions.test.ts, src/db.ts, src/mcp.ts, src/page.ts, src/routes.ts, src/server.test.ts, src/tty.ts), because the event spine (bbf8ef9) and the ration (cf64fd3) landed underneath it while it waited. The design in that pull is right — the derived secret, the generation bump, revocation as a column — and it is the reference for what replaces this issue.

What replaces it, in two runs instead of one:

  • #35 — App identity, the core: the app table, src/apps.ts and registerApp/rotateAppKey/revokeApp/listApps, touching only src/db.ts, src/actions.ts, their tests and two new files. It merges even with #9 and #20 in flight. #8, #30 and #33 now wait on it.
  • #36 — The app registry at its doors: the routes, the admin section, the MCP tool. Waits on #35.

If a person would rather rescue pull #22 than rebuild it, merging it is still the fastest path and #35/#36 can be closed as done — but the conflicts have to be resolved by hand either way, and the migration it appends must move to the end of the list. Closing this so the map does not show the same work twice.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs