AUTHORIZATION
Authorization in swamp serve is grant-based. Each grant binds a subject to an
effect (allow or deny) on a set of actions against a resource selector. The
server evaluates all matching grants at access-check time.
Default deny
If no grant matches a request, the request is denied. There is no implicit allow.
Subjects
A grant's --subject identifies who the grant applies to.
| Format | Description |
|---|---|
user:<id> |
A specific authenticated user |
group:<name> |
Members of a local group managed via swamp access group |
idp-group:<collective-slug> |
Users whose IdP token includes the group |
Local groups are resolved at policy reload time. IdP groups come from the OAuth
provider's userinfo response, using the field specified by --groups-field
(default: collectives).
IdP group refresh
The server periodically refreshes IdP group memberships by re-fetching each
authenticated user's groups from the OAuth provider. The refresh interval is
controlled by --group-refresh-interval (default: 4h). On each refresh cycle,
the server calls the provider's userinfo endpoint for every active session and
updates the policy snapshot with the current group memberships.
Transient refresh errors (network timeouts, provider unavailability) are
fail-open: the server retains the last known group memberships and retries on
the next cycle. A 401 Unauthorized response from the provider is treated as a
hard revocation — the user's session is invalidated and all idp-group: grants
for that user stop matching immediately.
See swamp serve — IdP group refresh and deprovisioning for the design rationale behind fail-open vs hard revocation.
Effects
Each grant has exactly one effect:
| Effect | Meaning |
|---|---|
allow |
Permits the specified actions |
deny |
Blocks the specified actions |
Deny wins
When both allow and deny grants match a request, deny wins. A deny grant always overrides an allow grant, regardless of specificity.
Actions
| Action | Description |
|---|---|
run |
Execute a model method or workflow |
read |
Read data outputs, model definitions, vaults |
write |
Write data, create models |
admin |
Manage access: grants, tokens, groups |
Actions are specified as comma-separated values in --allow or --deny.
Admin superuser
A grant of admin on access:* implies all actions on all resources. This is
the superuser grant — the holder can perform any operation. The --admins flag
materializes this grant for the listed principals on every server start.
Resource selectors
The --on flag specifies which resources the grant applies to.
| Selector | Matches |
|---|---|
workflow:@acme/* |
All workflows in the @acme namespace |
workflow:@acme/deploy |
A specific workflow |
model:hello |
A specific model |
model:* |
All models |
data:* |
All data outputs |
data:@acme/secrets-* |
Data outputs matching the prefix |
access:* |
The access control system itself |
The * wildcard matches any suffix.
CEL conditions
The --when flag adds a CEL expression that must evaluate to true for the
grant to apply. Conditions are evaluated against resource fields at access-check
time.
--when 'tags.env == "staging"'See the CEL Expressions reference for the full syntax, operators, and available functions.
Resource fields can be passed to swamp access check using the --field flag
for testing condition evaluation.
Condition cost limits
Grant conditions are subject to complexity limits. Conditions that exceed a limit are rejected at grant creation time, except the aggregate limit which is enforced at access-check time.
| Limit | Threshold | Enforcement |
|---|---|---|
| AST depth | ≤ 24 | Write-time |
| Comprehension nesting | ≤ 2 | Write-time |
| Per-condition cost budget | ≤ 500 | Write-time |
matches() pattern |
See below | Write-time |
| Aggregate per-decision | ≤ 100 | Access-check |
AST depth counts the maximum nesting depth of the parsed expression tree.
Comprehension nesting limits the levels of chained exists, all,
filter, and map macros.
Per-condition cost budget is an estimated cost derived from AST node count and comprehension weight.
matches() restrictions: the pattern argument must be a string literal, not
a variable. Patterns containing catastrophic backtracking constructs are
rejected.
Aggregate per-decision limit: if more than 100 --when conditions are
evaluated in a single access check, the request is denied.
Existing grants created before these limits were introduced are unaffected. Error messages identify which limit was exceeded and the actual value.
# Rejected: comprehension nesting exceeds limit (3 > 2)
--when 'tags.all(k, tags[k].all(v, v.all(c, c == "x")))'Restricted model types
The --restricted-model-types flag locks specific model types behind the admin
superuser grant. A restricted model type requires admin on access:* to
create or run — the same authorization path used for built-in access model types
(swamp/grant, swamp/group, swamp/server-token).
swamp serve --auth-mode token --admins 'user:admin-id' \
--restricted-model-types 'command/shell'Non-admin users who attempt to create or run a restricted type receive:
Access denied: <principal> does not have 'admin' on access:*Model types are normalized on parse. Denormalized variants (Command::Shell,
COMMAND.SHELL, command.shell) all resolve to the canonical form and are
caught. Non-restricted model types are unaffected — non-admin users can still
create and run them per their grants.
Multiple types can be restricted with a comma-separated list:
--restricted-model-types 'command/shell,custom/dangerous'Restricted commands
The --restricted-commands flag locks specific server commands behind admin
authority. A restricted command requires admin on access:<command> to
execute — non-admin users are denied even if they have grants covering the
command's usual resource type.
swamp serve --auth-mode token --admins 'user:admin-id' \
--restricted-commands 'datastore.namespace.list,extension.install'Non-admin users who attempt a restricted command receive:
Access denied: <principal> does not have 'admin' on access:<command>Multiple commands can be restricted with a comma-separated list:
--restricted-commands 'datastore.namespace.list,extension.install,vault.seal'Restricted model types vs restricted commands
Use --restricted-model-types to control which models can be created or run
— for example, locking down command/shell so only admins can execute shell
models. Use --restricted-commands to control which server operations are
available — for example, preventing non-admin users from listing datastore
namespaces or installing extensions. Both flags require --auth-mode token or
oauth.
Grant source types
Grants can originate from different sources:
| Source | Description |
|---|---|
method |
Created via swamp access grant create |
config |
Materialized from --admins at startup |
file:<filename> |
Loaded from a YAML file in the grants/ directory |
extension:* |
Contributed by an extension |
The reconciler (triggered by swamp access reload) only creates, updates, and
revokes file:* grants. Grants with source method, config, or extension:*
are never modified by the reconciler.
Policy snapshot
Grants and group memberships are compiled into a policy snapshot. The snapshot is rebuilt:
- On
swamp access reload - On every grant or group change when
--grant-reload autois active - On server start (including admin grant materialization from
--admins)
Changes to grants and groups do not take effect until the snapshot is rebuilt.
Related
- Access Commands — CLI reference for grant, group, and token commands
- CEL Expressions — full CEL syntax reference
- swamp serve Architecture — why deny-wins, why default-deny, why hard refusals