Skip to content

Mount a grant check on every tenant route - #255

Merged
alexanderguy merged 7 commits into
mainfrom
intr-711-mount-a-grant-check-on-every-tenant-route
Nov 4, 2026
Merged

alexanderguy merged 7 commits into
mainfrom
intr-711-mount-a-grant-check-on-every-tenant-route

Conversation

@alexanderguy

Copy link
Copy Markdown
Member

Summary

  • Every route under /api/tenants/:tenantId checks a grant: tenant details require tenant:<id> / read, grant evaluation requires grant:* / read, and the approval list checks approval:* / resolve in requireGrant middleware.
  • A database-free unit test walks the hub route table and fails when a tenant route has no requireGrant ahead of its handler and no entry in GRANT_EXEMPTIONS. Each of the ten exemptions names its in-handler check and why the check is not middleware. docs/ROUTES.md states the rule.
  • resolveAuthzVerdict requires the consumer, like the other grant checks, and the run-token allow-list registration check runs in the unit suite.

Verification

  • make all exits 0 with the Postgres-backed suites (10469, 254 and 1519 tests pass, 0 fail).
  • The coverage test reports an ungated route added to the real app. Its synthetic cases cover stale, repeated and unneeded exemptions, shadowed routes, prefix guards and duplicate registrations.
  • The default owner, admin and member roles hold * / read or more, so they keep access to tenant details and grant evaluation. A role without those reads gets 403.

Closes INTR-711
Closes INTR-491

Tenant details include the tenant config. The default member role
holds read on every resource, so members keep access. Only principals
whose roles lack that read lose it, and for them the admin UI tenant
pages fail to load.
The request names any principal in the tenant and any consumer, and the
response lists the grants that match. Without a grant check, any member
could probe the access of every principal, run-token consumers
included. The default member role holds read on every resource, so
members keep access. Only roles without read on grants lose it.
No consumer reaches these routes today, because the git token
middleware refuses run tokens. The required argument makes each caller
state the consumer, so a later route cannot leave it out by omission,
the same rule that the other grant checks follow.
Route registration never touches the database, so the check runs in
the unit suite instead of only where Postgres is available.
The list route checks a fixed resource, so it needs nothing from the
handler. As middleware, the check is visible on the route like every
other fixed-resource grant check. A refusal now carries the shared
grant-check message.

Only the routes take requireGrant. resolveApproval keeps the narrower
ResolveApprovalDeps, exported beside the route deps, so that a caller
that resolves an approval directly does not have to supply it.
Grants are the only control on the hub API only if every tenant route
checks one. A new tenant route without a grant check now fails the unit
suite unless it is on the exemption list with a reason.
@alexanderguy

Copy link
Copy Markdown
Member Author

Self-Review (code-review skill)

The branch carries the following intentional exception, authorized by Greybeard:

  • packages/hub-api/src/route-registration.test.ts:221: the grant coverage check sends a route registered for every method (app.all) as GET, so it does not see a route that answers another method on the same path first. Authorized by Greybeard on grounds that no tenant route mounts an app.all handler, a method list cannot cover custom methods, and the doc comment names the limit. If such a route becomes necessary, the check rejects ALL tenant handler routes outright rather than sampling by method.

@linear

linear Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

INTR-711

INTR-491

@alexanderguy
alexanderguy merged commit 4e56a93 into main Nov 4, 2026
6 checks passed
@alexanderguy
alexanderguy deleted the intr-711-mount-a-grant-check-on-every-tenant-route branch November 4, 2026 06:37
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