Security Guardrails
Built into the Core Engine, not left for you to implement — every rule below applies no matter which adapter or framework called into it.
Path / identifier sanitization
tenantId and role names must match ^[a-zA-Z0-9_-]+$. Anything else is rejected before it ever touches path.join() — this is what prevents a malicious or buggy tenantId from becoming a path-traversal vector (../../etc, etc.).
try {
new RBAC({ tenantId: '../../etc' });
} catch (err) {
console.log(err.name); // InvalidIdentifierError
}Schema validation on every write
Every createRole / grant call validates the incoming role object before it's written to disk — valid resource/action names, no unknown fields. A hand-edited role file with an invalid shape is caught, not silently accepted.
Reserved names
System role names (admin, system-admin) are protected from accidental overwrite unless you explicitly pass { force: true }.
Circular inheritance detection
Every createRole / grant walks the inherits chain and rejects anything that would create a cycle — including the edge case of a role referencing itself before it exists on disk (createRole('self', { inherits: ['self'] }) throws CircularInheritanceError, not a confusing RoleNotFoundError).
No `eval()`, ever
Condition expressions — both the legacy when: string clause and the composable condition tree — evaluate against a fixed, hand-rolled operator vocabulary. A hand-edited role file cannot become a code-execution vector. custom operators call a function *you* registered in your own code at startup; nothing is ever parsed out of the JSON file and executed.
What's your responsibility
createRole / grant / deleteRole in the first place is your app's own business rule, not something the package can enforce for you. Check rbac.can(user, 'role', 'manage') — or whatever permission model fits your app — before exposing role management to end users.// Example: gate role management behind its own permission
if (await rbac.can(currentUser, 'role', 'manage')) {
await rbac.createRole('supervisor', { inherits: ['viewer'] });
}Zero dependencies in the Core Engine
The permission-evaluation path (can(), role resolution, condition evaluation) has zero third-party runtime dependencies — a smaller, more auditable surface than a full policy-engine dependency tree.