Policy attachments
A policy attachment is the join row between a policy and a principal (a user, group, role, or service account). Attach to grant; detach to revoke.
A principal's effective permissions are the union of every policy attached, directly or via group membership. There is no "deny by absence" — only explicit Deny statements override Allows.
All endpoints require a bearer admin JWT — see Authentication.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/iam/policy-attachments |
Attach a policy to a principal |
GET |
/v1/iam/policy-attachments |
List attachments, filterable |
DELETE |
/v1/iam/policy-attachments/:id |
Detach |
Attach a policy
POST /v1/iam/policy-attachments
Request body
| Field | Type | Required | Description |
|---|---|---|---|
policyId |
string (pol_…) |
yes | The policy to attach. Custom or system. |
principalType |
user | group | role | service_account |
yes | Kind of target. |
principalId |
string | yes | The usr_…, grp_…, rol_…, or svc_…. Must belong to the active workspace (system policies are workspace-agnostic). |
Response — 201 Created. The attachment row.
{
"data": {
"id": "pat_01KPG…",
"policyId": "pol_system_huudis_readonly",
"principalType": "group",
"principalId": "grp_01KPG…"
}
}
Errors
| Status | error.code |
When |
|---|---|---|
409 |
ALREADY_ATTACHED |
The exact (policy, principalType, principalId) tuple already exists. Detach first if you want to replace. |
400 |
VALIDATION_ERROR |
Invalid principalType, or the principal doesn't exist in this workspace. |
List attachments
GET /v1/iam/policy-attachments
Returns attachments, filterable via query params.
Query parameters
| Param | Type | Description |
|---|---|---|
policyId |
string | Only attachments of this policy. |
principalType |
enum | Only attachments to this kind of principal. |
principalId |
string | Only attachments of this principal. |
Combine them. For example, ?principalType=user&principalId=usr_01KPG… returns every policy attached directly to that user (not policies inherited via group membership — for the effective view, call POST /v1/authz/check).
Response
{
"data": [
{
"id": "pat_01KPG…",
"policyId": "pol_system_huudis_readonly",
"principalType": "group",
"principalId": "grp_01KPG…",
"policy": {
"id": "pol_system_huudis_readonly",
"name": "HuudisReadOnly",
"scope": "system",
"description": "All read-only actions in Huudis.",
"document": { … }
}
}
]
}
The expanded policy field is included so the dashboard can render policy names and descriptions without a second query.
Detach a policy
DELETE /v1/iam/policy-attachments/:id
Removes the attachment. The principal loses that policy's statements from their effective set immediately — subsequent authz/check calls will deny anything that was only allowed by this policy.
204 No Content.
The attachment object
| Field | Type | Description |
|---|---|---|
id |
string (pat_…) |
Stable attachment ID. |
policyId |
string (pol_…) |
The attached policy. |
principalType |
enum | user, group, role, service_account. |
principalId |
string | The bound principal. |
policy |
object | Expanded policy (list response only). |
Effective permissions
When you call POST /v1/authz/check, Huudis assembles the effective policy set for the principal:
- Direct attachments where
principalTypematches the calling principal's kind. - For users only: every policy attached to a group the user belongs to.
- For assumed-role sessions: every policy attached to the role — the underlying principal's own policies are excluded, by design.
The set of statements is concatenated and evaluated. Deny always wins over Allow; default is Deny.
Inspecting "why did this fail?"
The authz-check response carries a matchedSid field naming the statement that triggered the decision (when the policy author set a Sid). Pair with GET /v1/iam/policy-attachments?principalId=… and the policy document to reconstruct exactly which statement applied:
huudis iam policy-attachments list --principal-id usr_01KPG…
huudis iam policies get pol_01KPG…
For repeated debugging sessions, the audit log records each authz_check invocation with the resolved decision and matchedSid in metadata.
Cross-attachment patterns
- One policy, many groups. Common.
HuudisReadOnlyattached to every "read-only" group. - Per-environment guardrails. A
Denypolicy attached to a group "Prod-only" that forbids destructive actions whenforjio:WorkspaceSlugis notproduction. - MFA-required group. Attach an explicit-deny policy that triggers when
forjio:MfaPresentisfalse. Members of the group can do nothing until they enrol.
Events
No events for attachment CRUD. Audit-log entries (iam.policy_attached, iam.policy_detached) cover both operations with the full policy ID and principal in metadata.
The reserved (not-yet-emitted) huudis.iam.policy_attached.v1 webhook event is in the catalog for future use; see webhook event catalog.