carlos / platform Public

service principals: default-deny's second half is the package, not a prefix

Found while reviewing Task 4. The spec said the second half of default-deny
was "the /auth/cli/ family", which is not the whole set: /api/economics/bill
is a bearer-authenticated door under /api/ but not under /api/cli/, and a rule
phrased around that family leaves it unaccounted for.

The code and its test were already right — the guard walks every plain mux
registration in the package and asserts none resolves a bearer through
anything but cliUser, so the economics door is covered. Only the prose was
wrong. Restated over the package, with the door named as the reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UimU7fAfeAoBMm6HKCPwQ
Paul Campbell ccdefa24c8ec4abb869d2d6ccc4bcd050162ad88 parent 1bd5a20
1 file changed, +17 −9
  • docs/superpowers/specs/2026-08-18-service-principals-design.md +17 −9
diff --git a/docs/superpowers/specs/2026-08-18-service-principals-design.md b/docs/superpowers/specs/2026-08-18-service-principals-design.md
index 2ebfa250a..d4c7fa2d6 100644
--- a/docs/superpowers/specs/2026-08-18-service-principals-design.md
+++ b/docs/superpowers/specs/2026-08-18-service-principals-design.md
@@ -193,15 +193,23 @@ readable by every role, because a credential being able to see the thing it is
already bound to discloses nothing it does not already hold. This is the one
door whose *answer* is scoped rather than whose access is gated.
-**The `/auth/cli/` family** — `whoami`, `revoke`, `tokens/revoke`,
-`accounts` (which creates an ACCOUNT), and the `/settings` page — is outside
-the `/api/cli/` prefix and therefore outside `cliRoute`. It needs no role
-bindings and gets none: every one of those handlers gates on `cliUser`, which
-answers only for `kind=''`, so a service credential meets the same 401 a
-fabricated token meets. That is not an accident to preserve by luck. A test
-asserts that no handler outside `/api/cli/` resolves a bearer through anything
-but `cliUser`, so the day someone reaches for `CLIPrincipal` over there, the
-build says so.
+**Every door outside `/api/cli/`** is outside `cliRoute`. It needs no role
+bindings and gets none: those handlers gate on `cliUser`, which answers only
+for `kind=''`, so a service credential meets the same 401 a fabricated token
+meets. That is not an accident to preserve by luck. A test walks every plain
+mux registration in the package and asserts that none of them resolves a
+bearer through anything but `cliUser`, so the day someone reaches for
+`CLIPrincipal` over there, the build says so.
+
+The obvious members are the `/auth/cli/` family — `whoami`, `revoke`,
+`tokens/revoke`, `accounts` (which creates an ACCOUNT) — and the `/settings`
+page. But the rule is deliberately stated over the whole package rather than
+over that family, because the family is not the whole set: `/api/economics/bill`
+(`economicspass.go`) is a bearer-authenticated API door under `/api/` that is
+NOT under `/api/cli/`, and a rule phrased as "the `/auth/cli/` family" would
+have left it unaccounted for. It gates on `cliUser` and then on `IsOperator`,
+so a service credential meets the same 401 there too — but that is a fact the
+test proves, not one the prefix guarantees.
Default-deny therefore has two halves: inside `/api/cli/`, a door is
unreachable until `cliRoute` names a role; outside it, a door is unreachable