@wtfalch/authz
v0.15.1
Published
Scoped authorization: one catalogue, role assignments, mandatory restrictions and resource evaluation.
Readme
@wtfalch/authz
One scoped authorization model for applications sharing an identity provider. The package performs no I/O: applications supply current, authoritative identities, roles, assignments, team participation, restrictions and resource metadata.
defineResourceCataloguevalidates explicit operation metadata and namespaces, including permissions that require a statedpurpose, capmaxDelegationDepthorrequiresCoSign. Givenas constmodule literals, it infers the resulting catalogue's literal permission-id union.compileRoleGrantsbinds a role's explicit scoped entries to a recipient, application, platform, boundary and optional assignment expiry, start time and conditions.resourceAccessevaluates mandatory restrictions and produces both direct resource decisions and collection-query clauses. A grant may start later or apply only while attribute or weekly-schedule conditions hold; a dormant grant denies distinctly from a missing one.whyNotDelegablereports a typed reason a candidate grant cannot be delegated, including'depth'past a permission'smaxDelegationDepth. A platform-widefrozenrestriction denies non-surviving writes with'frozen', including inside break-glass. ArequiresCoSignwrite additionally needs a matching humanapprovalsentry from a machine principal;coSignerreports who approved an allowed write, for the audit row. The resolved Access also exposesreadableFields(resource)andproject(resource, record): which fields of a resource the principal's held read permissions reveal ('*', a sorted field-name list, or[]), and a frozen shallow copy of a record limited to them. A read permission may setfieldsto restrict what it reveals; omitting it reveals every field of its resource type.- A grant may set
guest: true: an expiring grant to a principal, never usable as a held grant for delegating onward. It confers nothing unless its permission setsguestto'read'or'write'in the catalogue. See Scoped access. assignmentRefusalchecks containment of every proposed grant, and, whenmanagementis passed, management authority and protected-role guards too. Omitmanagementto ask containment alone — the question "would this role still be covered" — and refuse the operation some other way. Check both displaced and new assignments.credentialAccessintersects each credential hop with its current issuer, checking identity, expiry and revocation through the same evaluator, and drops any grant past its permission'smaxDelegationDepthfor that hop. The resolved Access carrieslineage, the root-to-actor principal chain, for the audit row'scredential_chain.diffRolesstructurally compares two role sets and reports only what changed;blastRadiuspreviews an allow/deny flip for a principal x permission x resource combination across a policy change, bounded by a 1,000,000-combination guard.@wtfalch/authz/fixturesexportsaccessInput, which fills aResourceAccessInputaround your catalogue — an unrestricted active customer organisation, its operator platform, no teams, no memberships, no grants — so a test of your own roles spells out only what it is testing. Any field can be given directly and wins. The nine test files in this repo use it.resourceDenialsanddelegationDenialsgive every denial code asource, naming where to look to change the answer, and aremedy, naming what clears it. The sentence shown to a user stays with the host; the classification it is built from does not need inventing twice.accessMatrixanswers the same combination in the present tense — who can do what, right now — binding one policy to many principals and reporting every cell, denial code included. Same de-duplication, guard, sort and freeze.authzEventSchemaand the shared core define bounded audit records and actor, tenant, outcome and support-session vocabulary, including an optionalcredential_chain.canonicalJson,sealAuditRow,verifyAuditChainanderaseAuditRowhash-chain audit rows: acontent_hashover salted erasable content, arow_hashlinking each row to the one before it, and a GDPR-safe erasure that nulls the salt without breaking the chain.checkConstraintSqlemits theDROP/ADDpair that pins one ofcore's closed lists — events, actor classes, contexts, outcomes, tenant kinds and states, membership sources, credential kinds, break-glass reason codes — as a Postgres CHECK constraint. Generated fromcore, so an app's database and the package cannot disagree about a list after it widens.retentionClassandisExpired, backed by the newcore.retention, classify every audit event as'security'or'standard'and check a row against the host's own per-class retention windows.
Start at Quickstart: five steps from an empty file to an
answered question, every example executed by the test suite. Then
Scoped access for the complete integration contract, and
Stability for what a 0.x minor promises and what it does not.
Requires Node 22 or newer and Zod 4.1.13 or newer; see STABILITY.md for why
that floor, and for the runtimes it supports. Run pnpm build, pnpm lint,
pnpm typecheck and pnpm test from the repository root. The published
package also runs inside workerd, Cloudflare Workers' runtime; see
scripts/tests/edge.test.mjs.
This is a breaking replacement. Flat permission sets, wildcard roles, the old
vocabulary/lattice APIs and their fixtures are removed. Existing databases need
an offline conversion and a coordinated application cutover. No runtime fallback
or permission-name alias is provided. Historical audit schema version 1 remains
readable; core.policyVersion identifies the new policy contract independently.
Reporting a security issue
A defect in resourceAccess, credentialAccess, compileRoleGrants or
assignmentRefusal is an authorisation defect in every app that imports this
package. So is one in the restrictions those evaluators apply — a tenant
ceiling, a self-denial, a tenant state or the platform frozen flag — or in
the audit chain sealAuditRow and verifyAuditChain maintain.
Report a suspected bypass privately to this package's maintainer, the npm
account that publishes @wtfalch/authz. Do not open a public issue. Include
the package version, the input that reproduces it, and what the package
answered. You will get an acknowledgement within a few days and a fix release
before any public disclosure.
The source repository is private, which is why this section is here: it is the
only copy of the route an npm-only consumer can see. The conformance fixtures
exported from @wtfalch/authz/fixtures describe every rule the package
enforces, so a report can name the fixture case that should have refused an
input.
