sod
Separation-of-duties rules name roles that nobody may hold together, such as creating suppliers and approving payments to them.
Separation-of-duties rules name roles that nobody may hold together, such as creating suppliers and approving payments to them. Each role is fine on its own; the combination makes fraud or an unnoticed mistake possible. Because roles reach people through many paths over time (direct bindings, groups, access requests, packages, configuration), nobody sees such a combination forming. A rule makes Better IAM check for it on every operation that can grant a role, and report the conflicts that already exist. The guide is separation of duties.
How rules are checked
A person holds a rule's role when they have a direct binding to it, a binding through a group they are a live member of, an eligible (just-in-time) binding even before activating it, or a future-dated binding. Expired bindings, lapsed memberships, and deleted identities do not count. Role inheritance is not expanded: name the roles you actually bind.
A rule has one of two modes:
prevent(the default): every operation that can grant a role (bindings.createandbindings.update, group membership changes,identities.createMany, access-request approval, access-package assignment and approved package requests,config.apply, and member-invitation acceptance) compares the tenant's conflicts before and after it runs. If it would create a new one, it fails withSOD_CONFLICT(409) and its transaction rolls back.detect: nothing is refused; conflicts are only reported byviolationsand by the access analysis, as high-severityseparation-of-dutiesfindings.
Conflicts that already existed never block unrelated work, so you can add a rule to a tenant that is not clean yet and fix violations at your own pace. SCIM role mappings are not blocked; their conflicts appear in the reports.
Rule management is authorized on iam/sod/* (create, list, violations) and iam/sod/{ruleId} (update, delete), so
a compliance team can manage rules without other administrative rights.
| Method | What it does | Access |
|---|---|---|
create | Declares 2 to 20 roles that nobody may hold together, and reports how many people already hold two of them. | Credential |
delete | Removes a rule, so the combination is no longer checked or reported. | Credential |
list | Lists the tenant's rules, newest first. | Credential |
update | Changes a rule's name, description, roles, or mode. | Credential |
violations | Lists everyone who currently holds two or more roles of a rule, with names for review screens. | Credential |
create
Declares 2 to 20 roles that nobody may hold together, and reports how many people already hold two of them.
Used inSeparation of duties
- Permission:
iam:sod:manageoniam/sod/*. - Audited as:
iam:sod:manage. - Errors:
INVALID_INPUTwhennameorroleIdsis missing, fewer than 2 or more than 20 distinct roles are named, one of them is a protected Owner role, themodeis notpreventordetect, or the name (200 characters) or description (1000) is too long;NOT_FOUNDwhen a role is not in this tenant.
The result is the stored rule plus existingViolations, the number of conflicts that already exist. Creating a
rule never fails because of them. To measure a rule's impact before enforcing it, create it with mode: 'detect'
and switch to prevent with update later.
const rule = await iam.api.sod.create(credential, {
tenantId,
name: 'Supplier creation vs payment approval',
roleIds: [supplierAdmin.id, paymentApprover.id],
description: 'Finance controls policy, section 4.2',
});
// rule.existingViolations: people who already hold both rolesProp
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/sod/create" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"name": "<name>",
"roleIds": [
"<roleId>"
]
}'iam.api.sod.create(
credential: CredentialInput,
input: {
tenantId: string;
name: string;
roleIds: string[];
mode?: SodRule['mode'];
description?: string;
},
): Promise<{
existingViolations: number;
name: string;
description?: string;
roleIds: string[];
mode: 'prevent' | 'detect';
createdAt: number;
createdBy: string;
id: string;
tenantId: string;
uniqueKey?: string;
}>delete
Removes a rule, so the combination is no longer checked or reported.
- Permission:
iam:sod:manageoniam/sod/{ruleId}. - Audited as:
iam:sod:manage. - Errors:
NOT_FOUNDwhen the rule is not in this tenant.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/sod/delete" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"ruleId": "<ruleId>"
}'iam.api.sod.delete(
credential: CredentialInput,
input: { tenantId: string; ruleId: string },
): Promise<{ deleted: boolean }>list
Lists the tenant's rules, newest first.
- Permission:
iam:sod:readoniam/sod/*. - Audited as:
iam:sod:read.
Prop
Type
An array of SodRule.
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/sod/list" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.sod.list(
credential: CredentialInput,
input: { tenantId: string },
): Promise<SodRule[]>update
Changes a rule's name, description, roles, or mode.
- Permission:
iam:sod:manageoniam/sod/{ruleId}. - Audited as:
iam:sod:manage. - Errors:
NOT_FOUNDwhen the rule is not in this tenant;INVALID_INPUTunder the same rules ascreate.
Only the fields you pass change; roleIds replaces the whole list. Switching to prevent takes effect for the next
granting operation, and conflicts that exist at that moment still do not block unrelated work.
Prop
Type
A SodRule object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/sod/update" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"ruleId": "<ruleId>"
}'iam.api.sod.update(
credential: CredentialInput,
input: {
tenantId: string;
ruleId: string;
name?: string;
description?: string;
roleIds?: string[];
mode?: SodRule['mode'];
},
): Promise<SodRule>violations
Lists everyone who currently holds two or more roles of a rule, with names for review screens.
Used inSeparation of duties
- Permission:
iam:sod:readoniam/sod/*. - Audited as:
iam:sod:read.
Both prevent and detect rules are covered; pass ruleId to check one rule (an unknown id returns an empty
list). Each entry names the rule (ruleId, ruleName, mode), the person (identityId, and identityName, their
email or else their name), and the conflicting roles (roleIds, roleNames). Disabled identities are included,
because they can be re-enabled. To fix a violation, remove one of the conflicting grants: delete a binding with
bindings.delete, remove the person from the group that carries the role,
or revoke the access package that granted it.
const violations = await iam.api.sod.violations(credential, { tenantId, ruleId: rule.id });Prop
Type
An array of object.
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/sod/violations" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.sod.violations(
credential: CredentialInput,
input: { tenantId: string; ruleId?: string },
): Promise<{
identityName: string;
roleNames: string[];
ruleId: string;
ruleName: string;
mode: SodRule['mode'];
identityId: string;
roleIds: string[];
}[]>Better IAM is created by Sean Filimon
Last updated