Skip to main content

CLI approval config

The three CLIs (sentinel-hook, sentinel-copilot-hook, sentinel-mcp) read approval config from a JSON file pointed to by the SENTINEL_APPROVAL_CONFIG environment variable. Source-editing isn't an option for CLI users, so the config file is the only public surface.

Enabling

export SENTINEL_APPROVAL_CONFIG=/etc/ai-sentinel/approvals.json
sentinel-hook user-prompt-submit < input.json

If the env var is unset (or empty), approvals are disabled — the CLI behaves exactly as it did pre-Stage-5.

File shape

{
"backend": "sqlite",
"databasePath": "/var/lib/ai-sentinel/approvals.db",
"defaultGrantMinutes": 15,
"tools": {
"delete_database": { "role": "DBA" },
"send_payment": { "role": "Treasury", "grantMinutes": 5, "requireJustification": true },
"deploy_*": { "role": "DeployApprover" }
}
}

Top-level fields

FieldTypeRequiredDefaultNotes
backendstringyesOne of none, in-memory, sqlite, entra-pim.
tenantIdstringfor entra-pim onlyEntra tenant GUID.
databasePathstringfor sqlite onlyAbsolute path to the .db file. WAL/SHM sidecars created next to it.
defaultGrantMinutesintno15Must be > 0. Per-tool grantMinutes overrides this.
defaultJustificationTemplatestringno"AI agent invocation: {tool}"Currently informational; per-tool override planned.
includeConversationContextboolnotrueForwarded to backend (currently informational).
toolsmapnoemptyPattern → binding. Keys are tool-name patterns (glob-style with *).

Per-tool fields

FieldTypeRequiredDefaultNotes
rolestringyesBackend-specific binding — DBA group, PIM role name, etc. Set on ApprovalSpec.BackendBinding.
grantMinutesint?nodefaultGrantMinutesOverride the top-level default.
requireJustificationbool?notrue

Environment-variable interpolation

Values containing ${VAR} are expanded against process environment variables at load time. Unset variables expand to empty string. Useful for committing sample configs without baking in tenant IDs or absolute paths:

{
"backend": "entra-pim",
"tenantId": "${SENTINEL_ENTRA_TENANT}",
"tools": { "delete_database": { "role": "DBA" } }
}

Escaping ($${VAR}) is not currently supported — there's been no real-world need.

Per-CLI behavior on RequireApproval

CLIWhen the guard returns RequireApproval
sentinel-hook (Claude Code)Deny-with-receipt — exits 2 with the request ID + approval URL on stderr. User approves out-of-band, then re-runs the prompt.
sentinel-copilot-hookSame: deny-with-receipt.
sentinel-mcp (proxy)Wait-and-block when SENTINEL_MCP_APPROVAL_WAIT_SEC=N is set (positive integer). Otherwise fail-fast — emits the receipt as a JSON-RPC error and the agent decides whether to retry.
# MCP: wait up to 5 minutes for approval before failing the tool call.
export SENTINEL_MCP_APPROVAL_WAIT_SEC=300
sentinel-mcp proxy --target /usr/local/bin/my-mcp-server

Validation

The config loader validates required fields at startup:

  • backend=sqlite without databasePath → fail.
  • backend=entra-pim without tenantId → fail.
  • Unknown backend value → fail.
  • Per-tool role empty → fail.

Validation failures print to stderr and exit 1. Successful load is silent.

See also