securitytutorial
Tutorial: verifying the security guardrails
Trust, but verify — especially for anything security-adjacent. These four snippets are meant to be pasted into a throwaway script and run against your actual installed version, not just read.
Path traversal attempts are rejected before they touch disk
import { RBAC } from 'rbac-fs';
try {
new RBAC({ tenantId: '../../etc' });
} catch (err) {
console.log(err.name, '-', err.message); // InvalidIdentifierError
}
try {
new RBAC({ tenantId: 'acme corp' }); // space isn't in ^[a-zA-Z0-9_-]+$
} catch (err) {
console.log(err.name); // InvalidIdentifierError
}Both throw before any filesystem call is made — the validation is a plain regex check at construction time, not a try/catch around a failed fs operation.
Reserved names require an explicit opt-in
const rbac = new RBAC();
try {
await rbac.createRole('admin', { permissions: [] });
} catch (err) {
console.log(err.name); // rejected — reserved name
}
const forced = await rbac.createRole('admin', { permissions: [] }, { force: true });
console.log('forced create succeeded:', !!forced);Circular inheritance is caught at write-time, not read-time
await rbac.createRole('role-a', { inherits: ['role-b'] }, { force: true });
try {
await rbac.createRole('role-b', { inherits: ['role-a'] }, { force: true });
} catch (err) {
console.log(err.name); // rejected — would create a cycle
}Run this exact sequence and confirm the second call throws — if it doesn't, that's a regression worth filing, not something to work around.
Schema validation rejects malformed permission objects
try {
await rbac.createRole('bad-role', {
permissions: [{ resource: 'invoice', actions: ['approve'], extraField: 'nope' }],
});
} catch (err) {
console.log(err.name); // schema validation error — unknown field
}Verified against docs/PLAN.md §8's guardrail table — run these yourself before relying on any of the four in a security-sensitive path of your own app.