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")))'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