docs: warn against the permissionMatches + permissionsFor composition
permissionsFor carries a caveat that its Set cannot represent a narrow deny under a broad grant, so callers must gate with decide(). Now that permissionMatches is also public, the wrong composition is directly reachable and looks idiomatic - and the warning lived only on the other half of it. Adds the pointer to permissionMatches, and covers scopeKey and safeRecord in the exports test, which the brief omitted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1536,7 +1536,13 @@ export function expandRoles(catalog: AuthzCatalog, roles: readonly string[]): Se
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Exact match, root wildcard, or a namespace wildcard at any depth. */
|
||||
/**
|
||||
* Exact match, root wildcard, or a namespace wildcard at any depth.
|
||||
*
|
||||
* Do NOT gate access by matching against `permissionsFor()`'s result — that set
|
||||
* cannot represent a narrow deny beneath a broad grant, so the composition
|
||||
* returns true where `decide()` refuses. Use `decide()` / `can()` instead.
|
||||
*/
|
||||
export function permissionMatches(granted: Set<string>, permission: string): boolean {
|
||||
if (granted.has("*") || granted.has(permission)) return true;
|
||||
for (let at = permission.indexOf(":"); at !== -1; at = permission.indexOf(":", at + 1)) {
|
||||
@@ -2408,6 +2414,8 @@ describe("@wrnexus/authz exports", () => {
|
||||
"createAuthzResolver",
|
||||
"expandRoles",
|
||||
"permissionMatches",
|
||||
"scopeKey",
|
||||
"safeRecord",
|
||||
"deniedBy",
|
||||
"authzMiddleware",
|
||||
"can",
|
||||
|
||||
Reference in New Issue
Block a user