From 703baa1ead5ff45d665f03be2f62df2bb4e53fc5 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Tue, 4 Aug 2026 19:38:07 +0530 Subject: [PATCH] fix(authz): strengthen permissionMatches warning, complete export coverage Move the "don't gate on permissionsFor() with permissionMatches" warning onto permissionMatches itself so it's visible via autocomplete, not just on AuthzResolver.permissionsFor. Round out exports.test.ts to cover scopeKey, safeRecord, and AUTHZ_LOCALS_KEY, closing the gap where dropping either export from index.ts would not fail the test. --- packages/authz/src/engine.ts | 8 +++++++- packages/authz/test/exports.test.ts | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/authz/src/engine.ts b/packages/authz/src/engine.ts index a67a063f..b864c281 100644 --- a/packages/authz/src/engine.ts +++ b/packages/authz/src/engine.ts @@ -54,7 +54,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, permission: string): boolean { if (granted.has("*") || granted.has(permission)) return true; for (let at = permission.indexOf(":"); at !== -1; at = permission.indexOf(":", at + 1)) { diff --git a/packages/authz/test/exports.test.ts b/packages/authz/test/exports.test.ts index 6c5867e9..85e2d5ef 100644 --- a/packages/authz/test/exports.test.ts +++ b/packages/authz/test/exports.test.ts @@ -43,8 +43,14 @@ describe("@wrnexus/authz exports", () => { "decideFor", "guardPermission", "filterCan", + "scopeKey", + "safeRecord", ]) { expect(typeof (authz as Record)[name]).toBe("function"); } }); + + test("exports the locals key used to reach the per-request resolver", () => { + expect(typeof (authz as Record).AUTHZ_LOCALS_KEY).toBe("string"); + }); });