RBAC Architecture & Specs

A deep dive into the Dual-Approach database strategy, deterministic string validation, and the 3-Stage execution pipeline of the Aeion Access Control Engine.

Security & Execution Metrics

RBAC Processing Overhead

<1ms

Permission Syntax

Regex Validated `^(?:\*|[a-z][a-z0-9-]*):(?:\*|[a-z][a-z0-9_-]*)$`

Enforcement Layers

4 (UI, Pre-Query, Post-Read, Pre-Write)

Role Hierarchy

Nested inheritance (cycle-safe, depth-limited)

1. The Dual-Approach Database Strategy

Aeion OS balances the flexibility needed for content against the strict constraints required for enterprise security. - Flexible Collections (JSONB): Standard modules (Commerce, Content) use the Collection System, storing dynamic fields in a flexible data JSONB column. - Fixed RBAC Admin APIs: Security tables — roles, members, teams — completely bypass the Collection System. They utilize fixed, typed PostgreSQL columns, enforced by database-level constraints (NOT NULL, primary keys, indexed lookups on every foreign-key-style column) — deliberately without cross-database REFERENCES constraints, since a tenant member row references a user record that may live in a different database — and are accessed via hyper-fast, direct Drizzle Admin APIs (/api/admin/roles). This deliberate architectural choice guarantees maximum authentication performance and ensures a malformed JSON payload can never compromise the security hierarchy.

2. Dynamic Row-Level Security (RLS)

Instead of forcing developers to write imperative authorization checks in their route handlers, Aeion leverages a declarative AST structure. - The `createScopedReadAccess` Helper: Developers define an access function on the schema. If the user is an admin, it returns true (granting full access). If the user is standard staff, it returns a strongly-typed Filter object (e.g., { managerId: { equals: user.id } }). - AST to Drizzle Translation: The database access layer intercepts every find() call, takes the Filter object returned by the RBAC engine, translates it safely into a parameterized Drizzle WHERE clause, and executes the query natively at the database level — no in-app post-filtering, no risk of an unfiltered row slipping through.

Frequently Asked Questions

Security tables — roles, members, teams — run on fixed, strictly-typed PostgreSQL columns with hard database constraints (NOT NULL, primary keys, indexed foreign-key-style lookups) and are read through direct Drizzle Admin APIs like `/api/admin/roles`. That keeps permission checks on a hyper-fast path and guarantees a malformed JSON payload can never corrupt the security hierarchy — the flexibility that content collections need would be a liability here.

Developers define a declarative access function on the schema. It returns `true` for admins or a strongly-typed `Filter` object for standard staff (e.g., `{ managerId: { equals: user.id } }`). The database access layer intercepts every `find()`, translates that Filter into a parameterized Drizzle `WHERE` clause, and runs it natively at the database level — no in-app post-filtering, so an unfiltered row can never slip through.

RBAC processing overhead is under 1ms. Enforcement spans four layers — the client-side Visual Airlock, then the three server stages (Pre-Query filter injection, Post-Read field redaction, and Pre-Write guard) — and the query itself does the filtering work at the database, so total request time stays dominated by the database and network rather than the security checks.

A module can declare custom sub-scopes in the same `<module>:<action>` format — for example `finance:payroll` or `hr:compensation` — to gate a single high-risk feature independently of a broader `finance:manage` grant. These custom strings pass through the identical regex validation and wildcard resolution as every other permission, so a role holding `finance:*` still covers them with no extra wiring.

The Visual Airlock. At application boot the OS cross-references the user's permissions against every registered view's declared requirements and physically purges anything they lack from the client's navigation tree — not hidden with CSS, genuinely never mounted. A restricted UI returns a hard 404 rather than a grayed-out button an attacker could poke at.

See the Access Control Engine in Context

Composes with the rest of the OS