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

The app registry at its doors: the JSON API, the admin section on the page, an MCP tool #36

open not picked up yet opened this
opened this

An admin registers, rotates and revokes an app without a shell: the same four actions, reached from the API, the page and MCP.

Waits on #35, which lands the app table, src/apps.ts and the actions. This pull adds doors and decides nothing: every rule (admin only, the id shape, the duplicate, the revoked app) is already in the action, and a door that re-checks one is a second copy that will drift. Pull #22 has a working version of all of this — lift it, rebased on whatever page.ts and routes.ts look like when this starts.

Done looks like

  • [ ] Routes in src/routes.ts, each a call onto the action and answer(...): GET /api/apps, POST /api/apps ({ id, name }), POST /api/apps/:id/rotate, DELETE /api/apps/:id. Status follows the failure through the existing STATUS map; a non-admin gets 403 forbidden and an anonymous caller 401 signin_required, from the action.
  • [ ] The freshly issued key appears in the body of POST /api/apps and POST /api/apps/:id/rotate and in no GET, ever — and in no log line, no error message and no redirect URL.
  • [ ] The page grows an admin-only Apps section (its own function in src/page.ts, not more string in home): the list with generation and a revoked marker, a register form, and rotate/revoke as same-origin POST forms. 44px controls, no horizontal scroll at 360px, keyboard reachable. A newly issued key renders inline in the response to the form that created it — never through a redirect, so nothing resends it — in a selectable <code> block with one sentence saying it will not be shown again.
  • [ ] A user who is not an admin sees no section at all, and a request to any of the four routes still gets the action's refusal: the hidden section is a courtesy, the action is the rule.
  • [ ] MCP: a list_apps tool in tools, one sentence, a schema, straight onto listApps. No register_app tool — issuing a secret over a transport an agent may log is not a convenience worth having, and the README says so in a line.
  • [ ] Tests: src/server.test.ts covers each route at each role; a test asserts a key returned by register appears in no subsequent GET /api/apps and in no rendered page; a POST without a same-origin header is refused by the floor.
  • [ ] Not here: the terminal, which is #30.

Files: src/routes.ts, src/page.ts, src/mcp.ts, src/server.test.ts, README.md

Risks: page.ts and routes.ts are the files #9 is rewriting — start after it merges or expect the rebase. Rendering a secret into HTML is the one place in this app where an escaping slip is a key disclosure: it goes through escape() like everything else, and the test asserts the exact bytes. A DELETE from a browser form is not a thing, so the page's revoke is a POST to its own path while the API keeps DELETE.

Suggested: Scout.

commented

Before this opens a door that issues a key: it now waits on #50 (an app secret bound to the deployment's audience), because the moment a key is issued to another crew, changing the derivation costs them a rotation. Also note #53 — answer() has no status for unavailable, which is exactly what registerApp returns when no identity key is configured, so today that failure would leave this door as a 200.

commented

Pull #67 (#30) is in review and puts the registry at the terminal door first, which settles two things this issue was going to decide alone. It exports KEY_SHOWN_ONCE from src/actions.ts — the sentence shown beside a freshly issued key — so the page's admin section prints that exact string rather than writing a second version of the same warning; and it proves the shape of "show the key once, never store it, never repeat it on a redraw", which the page has to match with a render that is not a redirect. This issue still waits on #50: a key handed out at a door is a key another crew stores, and the derivation should be bound to the deployment before the first one leaves the building.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs