a commander's bound is a rule, not a list
the allow list shipped an hour ago was a snapshot. plugin permissions
register at mount, so a plugin installed after a commander was created was
missing from its list and failed as a bare 403 with nothing saying why —
and every future thing a commander is meant to do is exactly that shape:
notify a device, send mail, spawn a sub-agent, hand a transcription to an
idle one, reach a plugin that does not exist yet.
so a credential now narrows two ways, and both only subtract:
permissions an ALLOW list. exact, a snapshot. right for a credential
with one job.
excludedKinds kinds it may never reach, resolved against the LIVE
registry on every request. right for a credential whose
job is open-ended.
a commander gets the second: every kind except admin. bounded away from
installing plugins, managing users and roles and changing server settings,
forever — and open to everything the platform learns to do, with nothing
to re-issue. still intersected with the account, so for a member it
narrows itself against their role.
verified against the running server with a commander-shaped key: tasks,
notify, file-browser and the transcribe PLUGIN all reach their routes;
/api/users, /api/plugins and /api/roles are 403. the plugin case is the
one an allow list could not have passed — it was mounted long after any
key was minted.
the commander's CLAUDE.md says what it cannot do in the rule's own terms,
and that a 403 is an answer rather than a problem to route around.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -47,10 +47,10 @@ export const permissionGateMiddleware: MiddlewareHandler = async function (ctx,
|
||||
// A NARROWED credential is checked even for the owner — that narrowing is the only bound a Super Admin
|
||||
// has, so skipping the check for them would make it decorative on the one account it exists for. An
|
||||
// unnarrowed owner still short-circuits, which keeps the common request free of a database read.
|
||||
const credential = payload?.permissions ?? null;
|
||||
const credential = payload?.scope ?? null;
|
||||
// Stashed for the routes that pass authority on rather than just using it. Set unconditionally, so a
|
||||
// reader never has to distinguish "unnarrowed" from "the gate did not run".
|
||||
ctx.set('credentialPermissions', credential);
|
||||
ctx.set('credentialScope', credential);
|
||||
const mustCheck = payload && (!isOwner || credential);
|
||||
|
||||
if (mustCheck && path.startsWith('/api') && !isExemptApiPath(path) && !isPluginPublicPath(path)) {
|
||||
|
||||
Reference in New Issue
Block a user