Skip to Content
AuthorizationSecurity Register

Security register

Not on npm yet

npm install @evanion/acl does not resolve. The package is private: true, so a release run versions and tags it without publishing: npm cannot configure a trusted publisher for a package that does not exist on the registry, and the first version has to go up by hand.

@evanion/acl answers thirty-six classes of attack, one row each below. The security contract argues the same material in prose.

How to read a row

Each row names the Common Weakness Enumeration (CWE) and the Open Worldwide Application Security Project (OWASP) entry for its class. The suite in libs/acl/src/security holds one test per row, under the row’s identifier, and runs under nx test @evanion/acl.

The three tiers

Every entry sits in one of three tiers, and the tier decides what its test may claim.

  • Tier 1: the library prevents the attack, and the test asserts a construction error or a refusal.
  • Tier 2: the primitive exists and the consumer calls it. The test asserts the primitive is correct and the idiom it replaces wrong on the same decision.
  • Tier 3: the class is structurally out of scope. The test pins what the engine really does, and asserts no defence.

Tier 1, prevented

Twenty entries, tested in tier1-prevented.test.ts under each ID.

IDClassCWE / OWASPMechanism
SEC-001Mass assignment through a proposed-only keyCWE-915, API3:2023The write axis decides every key of proposed, held or not
SEC-002Fail-open on an absent or null whenCWE-1188, CWE-276when is required and a list; an empty one is the open grant
SEC-003A key disagreeing with its object and actionCWE-566key must equal object.action, and neither half carries a dot
SEC-004A deny rule reading a path the row lacksCWE-863, A01:2021An unreadable deny outranks an allow: unevaluable, naming the paths
SEC-005Prototype pollution reaching a decisionCWE-1321Every path read is own-property guarded; maps use defineProperty
SEC-006A prototype member naming a transition edgeCWE-1321The transitions map is read as own properties only
SEC-007A narrowed write carrying __proto__CWE-1321__proto__ names no field, so pickAllowedFields never returns it
SEC-008* and !name arriving as literal keysCWE-915Both are syntax in the rules, on the row and in the write
SEC-009Operator and value confusion in a conditionCWE-20An operand the engine would ignore is refused at construction
SEC-010Incorrect default permissionsCWE-276, A01:2021No rules, an empty rule list and an unknown action all refuse
SEC-011Bypass through a user-controlled keyCWE-566, API1:2023The untrusted path fails closed on an unknown key; the authored throws
SEC-012Mutating the matrix after adoptionCWE-913The matrix is a deep-frozen copy, and so is the exposed one
SEC-013A document validating and evaluating apartCWE-367The envelope is rebuilt from one read, frozen, validated as that copy
SEC-014Object data that cannot be coercedCWE-754, CWE-248Only a primitive names an edge; a hostile toString denies
SEC-015Exhaustion through matrix shapeCWE-674, CWE-400The freeze walk is iterative; a value nested past it is refused
SEC-016Decision cost growing with the matrixCWE-400A decision reads its own permission; the bound counts subject reads
SEC-017Type confusion across a JSON boundaryCWE-1287Comparisons are strict: '1' is not 1, and null is not absence
SEC-018A clock or boundary that does not parseCWE-754unusable-clock outranks an allow, so a timed deny keeps denying
SEC-019A write escaping the decision behind itCWE-915Property tests over generated writes: every picked key was marked allowed
SEC-020A non-envelope document reaching the engineCWE-20A missing permissions, a bad version and a bare list all refuse

Tier 2, the primitive exists and the consumer calls it

Eight entries, tested in tier2-primitives.test.ts under each ID.

IDClassCWE / OWASPPrimitive
SEC-101Mass assignment when applying a writeCWE-915, API3:2023pickAllowedFields keeps what decided allowed; !== 'denied' does not
SEC-102A field config read as the writable listCWE-915A targets or transitions config binds one field; fields.fields the set
SEC-103Object-level authorization: IDOR and BOLACWE-639, API1:2023A condition over object.* plus a projection carrying the field
SEC-104An unevaluable decision read as a grantCWE-863allowed is the gate; missing names what a refetch brings
SEC-105Unicode and key-shape attacks on a nameCWE-176Names match byte for byte, so an explicit allow-list closes the question
SEC-106in and eq disagreeing about NaNCWE-1077includes is SameValueZero and === is not; spell “never” as an equality
SEC-107A stale matrix granting a revoked permissionCWE-672access.version detects the mismatch; refetching is the consumer’s
SEC-108The field maps read as the gateCWE-863The maps answer whatever the action decides; allowed composes both

Tier 3, out of scope and documented

Eight entries, tested in tier3-contract.test.ts under each ID. Each asserts the clause is still written down; SEC-207 and SEC-208 also pin the gap.

IDClassCWE / OWASPWhy no defence exists
SEC-201Confused deputy: a forged subjectCWE-441, A01:2021can authorizes the bag it is handed. Resolve it from a verified session
SEC-202Complete mediationCWE-862, A01:2021authorize(subject) makes the checked path easy; nothing bars the other
SEC-203TOCTOU between the decision and the writeCWE-367A decision describes its snapshot. Re-check inside the transaction
SEC-204A self-authorizing conditionCWE-639The engine cannot know which object fields the subject writes
SEC-205Client-side enforcementCWE-602One call answers alike in a browser and a server; the runtime decides
SEC-206Disclosure through the matrix itselfCWE-200Every key, role string and window ships. Name things to be read
SEC-207A caller-supplied clockCWE-807, CWE-367A clock that parses is taken as given, sliding every window
SEC-208The subject and object are read liveCWE-367The matrix is frozen; the app’s data is not. Pass resolved objects

Where the suite lives

libs/acl/src/security holds the suite, matched by the package’s own src/**/*.{test,spec}.ts include, so CI runs it with no project to forget. A unit test asserts what a function does; a register test asserts that an attack fails.

Last updated on