Neu: Freigabe von Agenten, Red Teaming und Modell-Scans. Mehr erfahren
MITRITY
Docs / Richtlinien / Approval Workflows

Approval Workflows

Not every governance decision should be automated. Some actions — privilege escalation, bulk data operations, financial transactions — require a human to review and approve before they proceed. MITRITY's approval workflow system lets you define hold policies that pause agent actions and route them to a human approval queue.

This guide covers the end-to-end approval workflow: defining hold policies, managing the approval queue, configuring timeouts, and setting up notifications.

How Hold Policies Work

When the gateway evaluates an agent action and the matching rule has hold: true, the following sequence occurs:

  1. Action paused — The gateway asks the control plane to open an approval, and keeps the agent's call open while it waits for the outcome. A caller of the admission API sets its own budget for the wait: with hold_timeout_seconds: 0 it is answered held at once, with the pending approval's ID.
  2. Approval created — The control plane creates an approval record with status pending and starts the timeout clock.
  3. Notifications sent — MITRITY announces the held action: in the dashboard's notifications, and on Slack when your organization has connected it. Email and webhook destinations receive it when you turn those channels on for it; see Notification Channels.
  4. Human reviews — An authorized user reviews the pending action in the dashboard or via API.
  5. Decision applied — The user approves or denies the action. The gateway receives the decision and either forwards the original request or returns an error to the agent.
  6. Timeout fallback — If no human responds before the timeout expires, the timeout_action determines the outcome (auto-deny or auto-allow).

If the gateway cannot reach the control plane to open or follow the approval, the policy's fail mode decides: open lets the action through, closed refuses it.

Agent ──action──► Gateway ──hold──► Control Plane ──notify──► Slack / Email
                     │                     │
                     │◄────decision─────────┤◄────approve/deny──── Human
                     │                     │
                     ▼                     ▼
              forward or block       log audit event

Defining a Hold Policy

A hold is set on a policy rule, not on the policy itself. A rule whose action is deny and whose hold is true pauses the actions it matches for a human decision instead of blocking them. On a rule whose action is allow, hold has no effect. The rule's hold_timeout_minutes and timeout_action say how long to wait, and what happens when nobody decides.

Rule Structure

{
  "tool_pattern": "regex:^mcp:iam\\.(create_role|assign_role|escalate_permissions)$",
  "action": "deny",
  "hold": true,
  "hold_timeout_minutes": 15,
  "timeout_action": "deny",
  "priority": 400,
  "enabled": true
}

To scope a hold to particular agents, assign the policy to those agents. An agent_id constraint does not do it: rule constraints match the arguments of an action, not the caller — see Writing Policies.

Hold-Specific Fields

FieldTypeRequiredDescription
holdbooleanNotrue pauses the matched action for a human decision. Takes effect on a deny rule only.
hold_timeout_minutesintegerNoMinutes to wait for a human decision before applying the timeout action. Left out, or zero, the wait is 30 minutes.
timeout_actionenumNoWhat happens when the timeout expires: "deny" (block the action) or "allow" (permit the action). Left out, it is "deny".

Adding a Hold Rule via API

Add the rule to an existing policy:

curl -X POST https://api.mitrity.com/api/v1/policies/pol_8f3k2m1n/rules \
  -H "Authorization: Bearer mk_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "tool_pattern": "mcp:finance.transfer_*",
    "action": "deny",
    "hold": true,
    "priority": 350,
    "hold_timeout_minutes": 30,
    "timeout_action": "deny"
  }'

Response (201 Created): the rule.

{
  "id": "rul_3k9m2x",
  "policy_id": "pol_8f3k2m1n",
  "tool_pattern": "mcp:finance.transfer_*",
  "action": "deny",
  "alert": false,
  "hold": true,
  "hold_timeout_minutes": 30,
  "timeout_action": "deny",
  "priority": 350,
  "enabled": true,
  "created_at": "2026-03-01T10:00:00Z"
}

A new policy can carry hold rules from the start in the rules array of POST /api/v1/policies.

There is no numeric threshold constraint. A rule cannot say "hold transfers above $1000". Numeric constraints are integer upper bounds, and the rule matches only while the argument stays at or below the limit — the opposite of a threshold. resource_pattern is a tool-permission field, not a rule constraint, and glob matching has no > operator. See Writing Policies.

Common Hold Rule Examples

Privilege Escalation

Hold any action that modifies IAM roles or grants elevated permissions:

{
  "action": "deny",
  "hold": true,
  "tool_pattern": "regex:^mcp:(iam|rbac)\\.(create_role|assign_role|grant_permission|escalate)$",
  "priority": 500,
  "hold_timeout_minutes": 15,
  "timeout_action": "deny"
}

Bulk Data Operations

Hold bulk export, delete, or migration operations:

{
  "action": "deny",
  "hold": true,
  "tool_pattern": "regex:^mcp:bulk_(export|delete|migrate)_.*$",
  "priority": 450,
  "hold_timeout_minutes": 60,
  "timeout_action": "deny"
}

Infrastructure Changes

Hold infrastructure modifications for a human decision:

{
  "action": "deny",
  "hold": true,
  "tool_pattern": "mcp:infra.*",
  "priority": 400,
  "hold_timeout_minutes": 30,
  "timeout_action": "deny"
}

There is no time-window constraint, so a hold cannot be scoped to business hours. hold_timeout_minutes plus timeout_action is the closest control: outside working hours nobody approves, and the request resolves to the timeout action.

Database Schema Changes

Hold any DDL operation with a long timeout for DBA review:

{
  "action": "deny",
  "hold": true,
  "tool_pattern": "regex:^mcp:db\\.(alter_table|create_table|drop_table|create_index|drop_index)$",
  "priority": 480,
  "hold_timeout_minutes": 1440,
  "timeout_action": "deny"
}

The Approval Queue

Dashboard View

The approval queue is the Approvals page in the dashboard sidebar. It lists the approvals you can see, newest first, under the All, Pending and Resolved tabs. Each row shows:

  • Agent that triggered the action
  • Action the agent attempted
  • Risk score of the action
  • Timeout: for a pending approval, the time left before the timeout action applies; for an approved or denied one, when it was decided
  • Status: pending, approved, denied or timed out

Selecting an approval shows its details: the agent, the action, its risk score, when it was requested and, once someone has decided it, the decision, who made it, when, and the note left with it.

Filtering the Queue

The API lists approvals with these query parameters:

ParameterDescriptionExample
statusOnly approvals in this status: pending, approved, denied or timed_outstatus=pending
sort_bycreated_at (default), action_type, drift_score or statussort_by=drift_score
sort_orderdesc (default) or ascsort_order=asc
limitResults per page, at most 100 (default 50)limit=20
offsetNumber of approvals to skipoffset=50

Which approvals a user sees depends on their access scope, not on a filter; see Approval Permissions.

Approve or Deny via Dashboard

Owners and Managers decide approvals in the dashboard:

  1. Open Approvals in the sidebar.
  2. Select an approval to see its details.
  3. Choose Approve or Deny on a pending approval.
  4. Optionally add a note; it is kept with the decision.
  5. The decision is applied immediately.

After a decision:

  • Approved: the gateway lets the held action through.
  • Denied: the gateway refuses the held action.

When the governance gate enforces agent approval, the held action of an agent that is not approved is refused, whatever the decision.

Approve or Deny via API

List Pending Approvals

Retrieve the pending approvals you can see:

curl "https://api.mitrity.com/api/v1/approvals?status=pending" \
  -H "Authorization: Bearer mk_your-api-key"

Response:

{
  "approvals": [
    {
      "id": "apr_9x2k4m",
      "tenant_id": "tnt_4f9a",
      "event_id": "evt_7h2k9d",
      "agent_id": "agt_sales-bot",
      "agent_name": "sales-bot",
      "action_type": "bulk_export_contacts",
      "resource_pattern": "crm/contacts/*",
      "drift_score": 0.42,
      "status": "pending",
      "timeout_at": "2026-03-01T11:00:00Z",
      "timeout_action": "deny",
      "created_at": "2026-03-01T10:00:00Z"
    }
  ]
}

An approved or denied approval also carries resolved_by, resolved_at and, when a note was left, resolution_note.

Approve an Action

curl -X POST https://api.mitrity.com/api/v1/approvals/apr_9x2k4m/approve \
  -H "Authorization: Bearer mk_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "note": "Approved: EMEA contact export requested by sales director for quarterly review"
  }'

The body is optional. Response: the approval, now with status approved:

{
  "id": "apr_9x2k4m",
  "tenant_id": "tnt_4f9a",
  "event_id": "evt_7h2k9d",
  "agent_id": "agt_sales-bot",
  "agent_name": "sales-bot",
  "action_type": "bulk_export_contacts",
  "resource_pattern": "crm/contacts/*",
  "drift_score": 0.42,
  "status": "approved",
  "timeout_at": "2026-03-01T11:00:00Z",
  "timeout_action": "deny",
  "resolved_by": "user_jsmith",
  "resolved_at": "2026-03-01T10:06:00Z",
  "resolution_note": "Approved: EMEA contact export requested by sales director for quarterly review",
  "created_at": "2026-03-01T10:00:00Z"
}

Deny an Action

curl -X POST https://api.mitrity.com/api/v1/approvals/apr_9x2k4m/deny \
  -H "Authorization: Bearer mk_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "note": "Denied: No business justification provided for bulk export of 15K contacts"
  }'

Response: the approval, now with status denied, in the same shape.

An approval that does not exist, is already decided, or belongs to an agent you cannot see answers 404 Not Found, and nothing changes. A Member or Viewer gets 403 Forbidden.

Count Pending Approvals

Get the number of pending approvals you can see (useful for badge counters):

curl https://api.mitrity.com/api/v1/approvals/count \
  -H "Authorization: Bearer mk_your-api-key"

Response:

{
  "count": 3
}

Timeout Behavior

When no human responds before the hold_timeout_minutes expires, the timeout_action determines the outcome.

{
  "timeout_action": "deny"
}

When the timeout expires, the action is blocked: the gateway refuses it, and the approval's status becomes timed_out.

Use auto-deny when:

  • The action is high-risk (privilege escalation, data deletion, financial transactions)
  • It is better to err on the side of caution
  • The agent can retry the action later after explicit approval

Auto-Allow

{
  "timeout_action": "allow"
}

When the timeout expires, the action is permitted: the gateway lets it through, and the approval's status becomes timed_out.

Use auto-allow when:

  • The action is low-to-medium risk and the hold is primarily for visibility
  • Blocking the agent on timeout would cause unacceptable downstream failures
  • You want human review when available, but default to permissive behavior

Timeout Recommendations

ScenarioRecommended TimeoutRecommended Action
Privilege escalation15 minutesdeny
Bulk data export60 minutesdeny
Infrastructure changes30 minutesdeny
Schema changes (DBA review)24 hours (1440 min)deny
Non-critical alerts30 minutesallow
After-hours monitoring60 minutesallow

Notification Channels

Three notices follow a hold, each a notification type you route in Settings > Notifications > Alert routing:

NoticeWhenChannels by default
Action held for approvalA hold opens an approvalThe dashboard's notifications and Slack
Held action resolvedSomeone approves or denies itThe dashboard's notifications
Held action timed outNobody decided in time and the timeout_action appliedThe dashboard's notifications, Slack and email

Watch the timed-out notice in particular: with timeout_action: "allow" it means an action went through without anyone looking.

Slack and email deliver only when the channel itself is set up (below). Webhook delivery is off for every notice until you turn it on under Alert routing.

Email Notifications

Emails go to the organization's recipient list: Settings > Notifications > Email Notifications, where you enable email and list the recipients. A held action is emailed when email is turned on for Action held for approval under Alert routing; a timed-out hold is emailed by default.

The email for a held action names the agent, the action type and the resource, and says how long remains before the timeout.

Slack Notifications

Slack messages go to the incoming webhook set in Settings > Notifications > Slack Integration. A held action posts a message like this:

Action Held for Approval
Agent: sales-bot                 Action: bulk_export_contacts
Resource: crm/contacts/*         Risk: Medium (0.42)
Timeout: 60 minutes | Approval ID: apr_9x2k4m
[Review in Dashboard]

The risk is read from the action's drift score: Low below 0.4, Medium below 0.7, and High from 0.7. Review in Dashboard opens the approval queue.

Webhook Notifications

Webhook notices go to the destinations set up in Settings > Integrations, the same destinations that receive SIEM forwarding. When the webhook channel is on for a notice, every enabled destination receives a POST for it. Syslog destinations are the exception: they are not HTTP endpoints and are skipped. The body is the notification envelope:

{
  "type": "approval.requested",
  "version": "1.0",
  "event_id": "5f0c2d1e-9a7b-4c3d-8e6f-1a2b3c4d5e6f",
  "tenant": { "id": "tnt_4f9a" },
  "severity": "warning",
  "title": "Action held for approval",
  "body": "sales-bot was held for approval: bulk_export_contacts on crm/contacts/*. It times out in 60 minutes.",
  "link_url": "/app/approvals",
  "generated_at": "2026-03-01T10:00:00Z",
  "payload": {
    "approval_id": "apr_9x2k4m",
    "agent_id": "agt_sales-bot",
    "agent_name": "sales-bot",
    "action_type": "bulk_export_contacts",
    "resource_pattern": "crm/contacts/*",
    "drift_score": 0.42,
    "timeout_minutes": 60,
    "dashboard_url": "https://mitrity.com/app/approvals",
    "dedup_key": "apr_9x2k4m"
  }
}

The other two notices arrive as approval.resolved and approval.timed_out. Each request carries the notice's type in the X-Mitrity-Event header and its ID in X-Mitrity-Event-ID, and sends the destination's Authorization header when one is set. A Splunk HEC destination receives the same envelope as the HEC event, with the sourcetype mitrity:notification.

Approval Permissions

Every role can view approvals. Owners and Managers approve and deny them:

RoleCan ApproveCan DenyCan View Queue
OwnerYesYesYes
ManagerYesYesYes
MemberNoNoYes
ViewerNoNoYes

Approvals follow agent grants. A user whose access scope is scoped sees, counts and decides only the approvals of the agents granted to them; their environment access does not change which approvals they see. An approval of any other agent answers 404 Not Found, and a decision on it changes nothing. An Owner, or a user whose access scope is all, sees every approval.

Audit Trail

Every decision stays on the approval. An approved or denied approval keeps its status, who decided (resolved_by), when (resolved_at) and, when one was left, the note (resolution_note); an approval nobody decided in time has the status timed_out. Decided approvals stay in the list, on the Resolved tab of the Approvals page and in GET /api/v1/approvals.

Best Practices

Start Narrow, Expand Gradually

Begin with hold policies on a small set of high-risk actions (IAM changes, bulk exports). As your team builds comfort with the approval workflow, expand to cover additional action types.

Set Reasonable Timeouts

Choose timeouts that balance security with operational needs. A 5-minute timeout on a database migration during off-hours will almost certainly auto-deny. Consider your team's response time when setting timeouts.

Always Provide Reasons

Encourage (or require) approvers to leave a note with every decision. The note stays on the approval and helps with post-incident analysis.

Use Environment Scoping

In multi-environment setups, consider different timeout and action configurations:

  • Production: Strict holds, short timeouts, auto-deny
  • Staging: Looser holds, longer timeouts, auto-allow
  • Development: Minimal holds, or monitor mode instead

Monitor Timeout Rates

If a large percentage of approvals are timing out, your timeouts may be too aggressive or the wrong people are being notified. Review timed-out approvals on the Resolved tab of the Approvals page.

  • Writing Policies — Policy structure, pattern matching, and priority system
  • Enforcement Modes — How Monitor, Alert, and Enforce modes interact with hold policies
  • RBAC — Role-based access control and approval permissions
  • API Overview — Full API reference for approval endpoints