Nolixan

Approvals & separation of duties

Hold risky actions for a second person — the requester can never approve their own.

When a policy returns require approval, the action doesn't run. It's stored as a pending approval and the execute call returns 202 with an approval_id. A reviewer then approves or denies it — and separation of duties guarantees that reviewer isn't the person who requested it.

The flow

  1. An action hits a require_approval policy → a pending Approval is created. Its payload is stored encrypted (AES-256-GCM) and a hash of the arguments is kept for integrity.
  2. A reviewer lists pending approvals and decides:
    POST /api/v1/approvals/{approval_id}/approve
    POST /api/v1/approvals/{approval_id}/deny
  3. On approve, Nolixan decrypts the original payload and re-executes the action (the policy gate is not re-applied — the approval is the authorization). On deny, it's marked denied and never runs.
  4. Pending approvals expire after a TTL (24h by default); an expired approval can't be acted on.

Separation of duties

The user who triggered an action cannot approve or deny it. If the requester tries, the call returns 403:

You requested this action and cannot approve or deny it yourself. It needs a different approver.

Crucially, that blocked attempt is itself written to the audit log (outcome self_approval_blocked) — so the separation-of-duties enforcement is visible in the trail, not silent.

Every decision — approved, denied, self-approval-blocked, or expired — is recorded against the approval and the original trace.

On this page