Skip to content
downpipes docs

Roles and capabilities reference: the six built-in roles

downpipes ships six built-in roles. Authorisation is decided by an explicit capability map, ROLE_CAPABILITIES, where each role holds a fixed set of named capabilities and a route allows a caller only when the caller’s role holds the capability that route requires. The map is the single authority the engine reads; nothing else decides a per-route allow.

This page is the authoritative table, written for an auditor who needs to know the exact power each role carries. It reproduces ROLE_CAPABILITIES as a role-by-capability matrix, and it states the three facts that a plain ranking would hide: two of the roles are a subset of owner rather than a step on a cumulative ladder, two capabilities are owner-reserved and can never be granted elsewhere, and authority keys on a stable subject so a recycled email never inherits a departed member’s role.

The six roles at a glance

Four of the roles are cumulative: a new member defaults to viewer, and each of operator, approver and owner adds power on top of the one below. The other two are narrow roles that sit off that line. They hold a precise subset of owner’s powers for one job, so they are neither weaker nor stronger than a cumulative role overall, only different.

Role Boundary
viewer Reads, plus restore dry-run and restore verify. The least-privilege resting role.
operator Data operations and operational config, plus raising a restore request. No restore apply or approve, no people, no keys.
approver Everything operator holds, plus restore apply and approve.
restore-operator Recovery only. The reads, drill, and the full restore lifecycle of request, apply and approve. No downpipe create, edit, delete or trigger, no config, no people, no keys.
access-admin People only. The reads, plus managing roles and the access policy. No data write, no restore apply, no keys.
owner Everything. The break-glass holder, and the only role that holds the two owner-reserved capabilities.

restore-operator and access-admin are not rungs on the ladder

The cumulative ordering runs viewer, operator, approver, owner. restore-operator and access-admin are deliberately off it: each is a subset of owner shaped for one duty, not a prefix of owner. A single numeric rank cannot express that, which is why per-route authority reads the capability map and not a rank comparison. An internal rank still exists, but only to fold role resolution and expiry cleanly, never to decide whether a request is allowed.

The role-by-capability matrix

This table is ROLE_CAPABILITIES verbatim. Rows are the capabilities a route can gate on; columns are the six roles; a cell reads yes when that role holds that capability and no when it does not. The seven capabilities every role holds (the viewer floor) are listed first, then the capabilities that differentiate the roles.

Capability viewer operator approver restore-operator access-admin owner
downpipe.read yes yes yes yes yes yes
audit.read yes yes yes yes yes yes
reports.read yes yes yes yes yes yes
posture.read yes yes yes yes yes yes
roles.read yes yes yes yes yes yes
restore.dryrun yes yes yes yes yes yes
restore.verify yes yes yes yes yes yes
downpipe.write no yes yes no no yes
downpipe.delete no yes yes no no yes
run.trigger no yes yes no no yes
drill.run no yes yes yes no yes
notify.config no yes yes no no yes
expiry.config no yes yes no no yes
scheduledtest.config no yes yes no no yes
restore.request no yes yes yes no yes
restore.apply no no yes yes no yes
restore.approve no no yes yes no yes
roles.write no no no no yes yes
access.policy no no no no yes yes
keys.ceremony no no no no no yes
posture.riskaccept no no no no no yes

A few readings worth drawing out from the cells above. restore.verify and restore.dryrun are both granted from the viewer floor up, because proving an archive can be recovered writes nothing back and surfaces no plaintext, so it is as safe as a read. restore.apply and restore.approve are held only by approver, restore-operator and owner. restore-operator carries the full restore lifecycle yet holds no downpipe.write, run.trigger, notify.config or roles.write, which is exactly the recovery-only subset. access-admin is the mirror image: it holds roles.write and access.policy and nothing that writes data or applies a restore: beyond the shared read-safe floor it holds no downpipe.write, restore.apply or restore.approve.

The two owner-reserved capabilities

Two capabilities are the owner’s alone. keys.ceremony gates the key ceremony, the highest-consequence cryptographic action. posture.riskaccept gates the owner’s deliberate grading of a security check: an attested pass, a compensating control, not applicable, or an accepted risk.

These two are owner-reserved in a strict sense. They appear in no other built-in role’s set, they are barred from any custom role, and no identity-provider group mapping can confer them. The bar holds even when the person composing a custom role is themselves an owner, because owner is never itself a custom role, so the break-glass owner stays the single named holder of both powers.

No path grants keys.ceremony or posture.riskaccept to a non-owner

A custom-role proposal that lists either capability is rejected at the write boundary, and the same bar is re-applied when a stored custom role is read, so even a tampered record could not confer them. Group mappings resolve to a role and inherit that role’s set, and since no grantable role holds these two, no group confers them either. If a caller can run the key ceremony or accept a posture risk, that caller is the owner.

Authority keys on a stable subject, not the email

A role is stored against a caller’s stable, immutable subject, under the key role:sub:<subject>. The subject is the principal the engine derives from the sign-in: a Cloudflare Access caller’s issuer and subject, a passkey caller’s credential principal, or a native identity-provider caller’s connection-scoped subject. The role lookup, the dual-control maker-is-not-checker comparison, and the last-owner count all key on that subject.

The email is retained for display and for the audit trail, and it is never the comparison axis for authority. Two consequences follow directly. A recycled or reassigned email can never inherit a departed member’s role, because the new person authenticates to a different subject and so reads a different (or empty) role entry. And the same person signing in with a different email case is still recognised as the same subject, so they cannot pose as a distinct second party in dual control.

An invitation is the one place an email is used as a key, and only until the invitee first authenticates. An owner or access-admin invites by email under role:pending:<email>, because they do not yet know the subject. On the invitee’s first verified request the engine binds that pending grant to their subject, writes the subject-keyed role entry recording the email, and deletes the pending row. From that point the authority is the subject’s.

The bare token is the only subjectless caller

A request authenticates by one of five methods. Four of them (Cloudflare Access, passkey, and the engine’s own native OIDC and SAML sessions) carry both a verified email and a stable subject, and all four resolve their role from the same subject-keyed table. The fifth is the bare ADMIN_TOKEN bearer fallback. It carries no email and no subject, so it has no attributable identity.

Because it cannot be attributed to a stable identity, the bare token resolves to the all-or-nothing owner break-glass. It does not read the role table; it is owner or it is nothing. This is a deliberate design point, not an accident of ranking: a shared secret should not masquerade as an identified person.

The same lack of a subject is what bars the bare token from four-eyes change control. Dual control needs two attributable, stable identities to compare as maker and checker, and a subjectless caller is neither. A caller on the bare token can therefore neither propose nor approve a dual-control change. For the mechanics of that gate, see dual control for restores.

How the engine reads the matrix at a request

Every per-route decision runs through one primitive, can(role, capability), which is a single lookup into ROLE_CAPABILITIES followed by a set membership test. The router’s gate is that lookup plus the shaping of a 403. The console mirrors the same map, so a client affordance is shown only when the server would allow the action, and the two sides cannot drift.

When a route denies a caller, the engine returns a structured forbidden body naming the capability the route required and the role the caller resolved to, which is distinct from the plaintext response for a failed sign-in. That lets the console tell a capability gate apart from an authentication failure and say which capability was missing.

A caller whose effective authority is a named custom role is pinned to the viewer floor on the role field, and their resolved capability set carries the real authority. The same can-style membership test runs over that set, so a custom-role caller is gated exactly like a built-in role, and the owner-reserved bar still applies to the set. Custom roles are additive on top of these six built-ins and never replace them.

Time-boxed role grants

A role grant can carry an optional expiry. When you add or edit a member you may set an expiry date and time, and the grant self-revokes at that moment: after it the engine treats the grant as viewer, so the member keeps read access to the console but loses the elevated capabilities the grant carried. This is how you hand out a temporary elevation, a contractor’s access for a fixed engagement or a break-glass Operator role for a single incident, without having to remember to remove it by hand.

The expiry moment is the date and time you pick, entered in your own local timezone. Once it passes, the engine reads that member’s email grant as viewer, so a member with no other source of authority keeps read access and loses the rest. A member who is also covered by a group-to-role mapping is a deliberate exception: because the expired email grant reads as viewer, the group mapping can still lift them, so after expiry they resolve to their group-mapped role when it is higher than viewer, not automatically to viewer. A shorter window is the stronger choice. The grant is a just-in-time elevation, so the member holds the elevated capabilities for the least time the task needs, and the posture it reverts to is least privilege.

The expiry binds to the grant, not to the person. Granting a fresh role replaces it, and clearing the expiry makes the grant standing again. Setting or changing a grant, the expiry included, is a first-class audited event, so who elevated whom and until when is on the tamper-evident trail.

An Owner grant is not exempt from this, and that is worth pausing on. You can put an expiry on an Owner, and the last-Owner guard will not stop you, because that guard only blocks a change that demotes the sole Owner at the moment you make it; a time-boxed Owner grant is still Owner when you save it, so the guard passes and never looks ahead to the expiry. The engine also checks only that the expiry is a well-formed date and time, not that it is in the future, so a moment in the past is accepted and lands a grant that is already expired, taking effect immediately as viewer. Pick a future moment.

Do not time-box your only standing Owner

If you put an expiry on the sole standing Owner, the grant lapses to viewer at that moment and the account is left with no standing Owner. The last-Owner guard does not catch this, because it only checks the change you make now, not a future expiry. Keep a second standing Owner, or leave the only Owner’s grant without an expiry.

  • Custom roles explains how an account composes a named capability bundle on top of these six, and the guardrails that keep a custom role from exceeding its creator.
  • Group-to-role mapping covers how an identity-provider group resolves to one of these roles, and why a group can never confer the owner-reserved capabilities.

Dual control for restores defines the request, approve and apply capabilities in the matrix above and the maker-is-not-checker rule that gates a restore apply.

Last updated .