Human-in-the-Loop Approvals (non-fakeable) + Break-Glass

High-risk tool calls can require a human to approve them first. The approval is a signed, single-use grant bound to the exact action — not a status flag or a cleartext token — so it cannot be forged by anyone with Redis/backup access, and the agent cannot self-assert it.

Design rationale: spec-hitl-breakglass.md.

Enable

  1. Provide a stable signing key (32-byte hex) on the data + admin planes:
    export SHIELD_APPROVAL_TOKEN_PRIVATE_KEY=$(python3 -c "import os;print(os.urandom(32).hex())")
    
  2. Mark the tools that need approval, per tenant, via the control-plane config (PUT /v1/tenant/me/agentic/config):
    { "approvals": { "enabled": true, "rules": [
        { "rule_id": "delete", "tool_names": ["delete_account"], "min_approvals": 1 }
    ]}}
    

Flow

1. agent -> POST /v1/shield/tool/check {tool_name, tool_params, session_id}
      -> 200 {action:"pending_confirmation", guardrail_results:[{details:{request_id}}]}

2. human -> POST /v1/tenant/me/agentic/approvals/{request_id}/approve
      (portal/gateway forwards the SSO-verified approver as X-Approver-Sub)
      -> 200 {status:"approved", approval_grant:"<signed token>"}

3. agent -> POST /v1/shield/tool/check {tool_name, tool_params, session_id, approval_grant}
      -> 200 {allowed:true}   # grant verified: signature + tool + params + session + single-use

The grant is bound to (tool, params_hash, session) — changing an argument after approval invalidates it (TOCTOU protection). It is single-use (nonce burned on first tool/check) and dies instantly if the agent instance is revoked.

Approver identity. The approver is taken from an authenticated principal: if your portal/gateway forwards an SSO-verified subject as X-Approver-Sub, the grant records method:"sso"; otherwise the asserted value is recorded as method:"asserted" so the audit reflects the trust level. Wire your IdP to set X-Approver-Sub for non-repudiable approvals.

Break-glass (emergency override)

Admin-only, mandatory reason, loud audit (breakglass_used) — mint a time-boxed grant that tool/check accepts like any approval but flagged breakglass:true:

curl -X POST https://<shield>/v1/shield/breakglass \
  -H "X-Admin-Key: $ADMIN_KEY" -H "X-Admin-Sub: oncall@acme.com" \
  -d '{"tenant_id":"acme","agent_id":"bot","agent_instance_id":"pod-1",
       "tool":"delete_account","resource":"cust/1","session_id":"s1",
       "tool_params":{"account_id":"1"},"reason":"SEV1 remediation","ttl_seconds":600}'
# -> {"status":"issued","breakglass":true,"approval_grant":"<token>"}

Emergency deny is the existing instance/user/jti revocation (POST /v1/shield/auth/revoke).

Migration note — deprecated cleartext confirmation

The older confirmation_token from sensitive_action_confirmation is an in-memory cleartext value and is not a security control — treat it as a UX “are-you-sure” only. For enforcement, use the signed approval_grant described here. Existing approval_request_id (status-flag) handling still works for backward compatibility but is weaker than a signed grant; prefer approval_grant.

Approvals raised by cross-app flow rules

A Cross-App Flow Control rule with "action": "require_approval" opens the same kind of approval request, for example when an agent that read Salesforce tries to mail someone outside the company. It is approved the same way and the agent presents the same signed approval_grant, on tool/check or cap/mint. The grant is bound to the call’s arguments, so approving one email does not approve a different one. A retry of a held call reuses the pending request instead of opening another.

Enforcement points

Approval is enforced at both gates:

  • Cooperative tool/check — pass approval_grant alongside the call; verified against (tool, params, session).
  • Non-bypassable cap/mint (L3) — for approval_required tools, the mint is refused unless a valid grant is presented, bound to (tool, resource, params, **this agent instance**, session). Flow mirrors tool/check:
    agent -> cap/mint {tool, resource, tool_params, session_id}   # no grant
          -> 403 {reason:"approval_required", request_id}
    human -> approve -> approval_grant
    agent -> cap/mint {..., approval_grant}  -> 200 {cap_token}    # grant verified + minted
    

    Because the tool executor lives outside the (untrusted) sandbox and only accepts a verified cap, an agent that never obtains an approved cap cannot perform the action — even if it ignores tool/check entirely.