5.0 The RS2 security model
This section is the map for all of Part 5. Read it first: it names the three tiers of authority, the one invariant they enforce, and where each later section fits.
The invariant
Authority originates only from configuration. Authored content can exercise authority that configuration already granted — it can never mint new authority.
Configuration is the operator-/installer-controlled surface: a mount's
access, a pipeline spec's access, capability grants, elevation roles,
operatorRoles, and quotas. Content is what authors write: pipeline logic,
custom code, data. A request, a pipeline, or a code bundle can only ever do what
configuration has already permitted — it can never grant itself more. Every rule
below is a consequence of holding this line.
Three tiers
RS2 separates who may act (roles) from who may define the rules (the operator and installer tiers). These are orthogonal: a role is meaningful only where config references it; operator/installer status is about changing config.
| Tier | Scope | Controls | Rooted in |
|---|---|---|---|
| Installer | the server / host | tenancy & domains, per-tenant quotas, which capabilities a tenant may use at all | the deployment (config file / process), never any tenant's API |
| Operator | one tenant | authorization config: mount & spec access, capability grants, elevation roles, role assignment |
a role listed in the tenant's operatorRoles (bootstrapped by the installer) |
| Roles | one application | nothing on their own — they gain meaning only when an access spec references them (A, U, editor, …) |
records in the users dataset (5.2) |
Installer — the cohabitation envelope
The installer sets the envelope every tenant lives inside: its resource quotas (concurrency, body size, wall-clock, outbound-call budget) and which capabilities exist for it (outbound HTTP, the JS engine, named stores). These come from server config, not from any tenant's config — so an operator cannot raise their own quotas or grant a capability the installer withheld. Want isolation between teams? Give them separate tenants; that boundary is the installer's, not something a tenant operator can cross.
Operator — the only authority over authorization
An operator is any principal holding a role named in the tenant's
operatorRoles (a space-separated list). Operators alone may change anything
that defines authorization:
- a mount's
access(via the/servicesconfig API or the on-disk config), - a pipeline spec's inline
accessfield (5.4, Part 7), - capability and elevation grants on a mount,
- assignment of operator roles to users.
Because roles have no inherent ordering, RS2 does not try to let non-operators delegate scoped slices of authority — that is a notorious source of privilege-escalation bugs. The line is bright: only operators define authority.
Bootstrap / root of trust. operatorRoles and the first operator credential
cannot come from the API (you would need to already be an operator). The
installer seeds them in the on-disk tenant config; operators then self-manage via
/services. Secure default: no operatorRoles ⇒ no API operator — the
authorization config is editable only from the file until an installer seeds one.
How requests are authorized
Every request is gated at its mount by the action its HTTP method maps to
(5.4): read (GET/HEAD/OPTIONS), write (PUT/PATCH), delete (DELETE),
invoke (POST and other non-idempotent verbs). The mount's access object names
the roles each action requires.
- No ambient trust. A call is authorized by the principal it carries. An
internal call (a pipeline step, a granted capability, a guest
fetch) is not trusted just for being internal — it propagates the originating principal and is checked exactly like an external call. A logged-in user's pipeline can never reach past that user's own access. - System calls. Runtime-originated calls the operator configured (a
scheduler tick on a mount's
schedule) are trusted — they cannot be forged from the wire and exist only by operator config.
Elevation — the one way to cross a boundary, bounded by config
A pipeline can deliberately reach a mount its caller could not, via the
gateway pattern: the pipeline mount is configured (by an operator) with an
elevate role, and a step marked elevate adds that role to the call's
principal — keeping the caller's identity (audit, path-scoped grants) intact. An
anonymous caller is given a synthetic principal carrying just that role (the
login gateway). The authority is the mount config, never the spec flag: without a
configured role the flag is inert, and an elevate role may not be an
operator role. Elevation is transitive (the added role rides along nested hops),
so keep an elevate-mount's authoring surface operator-locked.
What each section covers
- 5.1 the
authservice — turning credentials into tokens. - 5.2 users, password hashes, and roles — where roles live.
- 5.3 tokens, cookies, and bearer headers — how identity travels.
- 5.4 role specs on mounts — the
read/write/delete/invokevocabulary, path-scoped grants, and inline per-spec pipeline access. - 5.5 CORS and the CSRF guard — the external-only concerns.