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.
Refresh does not apply to SAML federation
The refresh cycle above describes what the server does with the groups Swamp returns. Whether those groups are themselves current depends on how the operative's identity provider is federated:
| Federation | When group memberships are re-read from the IdP |
|---|---|
| OIDC | On each refresh cycle, live from the IdP |
| SAML | Only when the operative next completes an SSO sign-in |
A SAML assertion is delivered once, at sign-in, and carries no token Swamp could
use to ask the IdP anything afterwards. So for a SAML-federated collective the
effective bound on a group change taking effect — including a removal — is the
operative's next SSO sign-in, not --group-refresh-interval. Plan
deprovisioning around session invalidation rather than around the refresh cycle.
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 |
approve |
Approve or reject a suspended workflow approval gate |
admin |
Manage access: grants, tokens, groups |
Actions are specified as comma-separated values in --allow or --deny.
Run implies approve
A grant of run on a workflow implicitly permits approve on the same
resource. A user who can run a workflow can also approve its gates — there is no
separation of duties by default. To enforce separation, grant approve without
run to the approver role, or deny approve on runners who should not
self-approve. See the
Separation of duties
scenario for worked examples.
Approve-only grants
A grant of approve alone — without run — permits the holder to approve or
reject workflow gates but not to start runs or resume them. This is the building
block for approval-only roles:
# Approvers can approve gates on deploy workflows but cannot start them
swamp access grant create --subject group:release-managers \
--allow approve --on 'workflow:@acme/deploy-*' \
--server wss://swamp.example.comTo prevent a runner from approving their own gates, add a deny grant:
# Runners can start the workflow but cannot approve its gates
swamp access grant create --subject group:developers \
--allow run --on 'workflow:@acme/deploy-*' \
--server wss://swamp.example.com
swamp access grant create --subject group:developers \
--deny approve --on 'workflow:@acme/deploy-*' \
--server wss://swamp.example.comThe deny-wins rule ensures the deny on approve overrides the implicit approve
from run.
Methods
A grant can optionally scope its actions to specific model method names using
the methods field. When present, the grant matches only when the requested
method is in the list.
| Behavior | Description |
|---|---|
methods absent |
Grant applies to all methods on the matched resource |
methods present |
Grant applies only to the listed methods |
swamp access grant create --subject user:alice --allow run \
--on 'model:hello' --methods read,listThis grant allows alice to run the read and list methods on model hello,
but not create, delete, or any other method.
Method scoping also works in file-based grants:
# grants/reader.yaml
- subject: "group:readers"
allow: [run]
on: "model:*"
methods: [read, list]The methodName field is available in CEL conditions for further refinement:
--when 'methodName == "deploy"'See the CEL Expressions reference for the full list of fields available in grant conditions.
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 admin-only model
types (swamp/grant, swamp/group, swamp/server-token,
swamp/enrollment-token, swamp/worker).
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