Skip to content

Policy & Approvals

LatchGate uses OPA (Open Policy Agent) for deterministic, machine-enforced policy evaluation. The default policy is at policies/opa/latchgate.rego with ACL data in policies/data.json (generated by latchgate init, managed via latchgate policy).

Policy is default-deny: every action is denied unless all rules pass.

The default Rego policy evaluates rules from most restrictive first:

  1. Trust gate — reject untrusted action digests
  2. ACL check — principal must be authorized for this action
  3. Base scope — lease must carry tools:call
  4. Required scopes — lease must carry all action-specific scopes
  5. Budget (calls) — session must have remaining call budget
  6. Sink check — requested targets must be within ACL allowlist
  7. Database gate — blocked DB operations are denied (planned; dormant in v0.1)
  8. Risk gate — high/critical risk actions require human approval
  9. Database approval — DB writes on sensitive tables require approval (planned, dormant in v0.1)
  10. Allow — all checks pass, return approved sinks and budget

The database rules are present in the Rego policy and exercised by make test-opa for forward compatibility, but never fire in v0.1 because no action manifest sets a database_context input field — the database provider ships in the planned releases.

The kernel constructs a PolicyInput struct and sends it to OPA. It includes: principal, action_id, action_trust_verdict, risk_level, scopes, required_scopes, requested_sinks, budgets_before, and database operation details when applicable.

OPA returns one of three decisions:

  • allow: true — action proceeds to grant issuance and dispatch
  • allow: false — action denied with deny_reason
  • pending_approval: true — action held for human approval

When allowed, the policy also returns approved_sinks and budget decrements.

Budgets are per-session counters tracked atomically in Redis via Lua scripts.

  • Call budget — maximum number of action executions per session

Budgets are atomically debited before dispatch via a Redis Lua script. If an execution fails, the debit is rolled back. This ensures budgets are never over-consumed even under concurrent requests.

Budget values are set at lease issuance time via max_calls.

When policy returns pending_approval, the kernel creates an immutable pending approval record and returns HTTP 202 to the caller. The pending approval stores the exact approved execution plan — it is never re-derived from the current manifest or registry state.

  1. Agent submits action => policy returns pending_approval => kernel returns 202 with approval_id.
  2. Webhook firesapproval.pending notification arrives in Slack/Teams/PagerDuty within seconds (if webhooks are configured).
  3. Operator reviews the pending approval (via CLI latchgate approvals list/show, the TUI, or the API).
  4. Operator approves or denies.
  5. On approval, the kernel executes the action through the same hardened path (budget, grant, dispatch, verify, receipt). Webhooks fire: approval.granted + action.executed.
  6. On denial, approval.denied webhook fires. On expiry, approval.expired fires (background scanner, 30-second interval).

Without notifications, operators must poll for pending approvals. Configure outbound webhooks to push approval.pending events to Slack, Teams, PagerDuty, or any HTTPS endpoint. For terminal-first operators, latchgate tui provides a real-time dashboard with inline approve/deny.

The operator TUI is the primary way to handle approvals. Launch it and switch to the Approvals screen (2):

Terminal window
latchgate tui
  • / (or k/j) — move through pending approvals; the detail pane shows the full approved plan.
  • a — approve the selected (pending) approval.
  • d — deny the selected (pending) approval.

The screen updates in real time, so approval.pending events surface without polling.

CLI equivalent
Terminal window
latchgate approvals list # show pending approvals
latchgate approvals show <approval_id> # show details
latchgate approvals approve <approval_id> # approve
latchgate approvals deny <approval_id> # deny
GET /v1/approvals # list pending
GET /v1/approvals/{id} # show details
POST /v1/approvals/{id}/approve # approve
POST /v1/approvals/{id}/deny # deny

All approval endpoints require operator authentication (API key + DPoP proof).

The approval_hash covers: request_hash, action_digest, policy_version, approved targets, secrets, egress, budgets, and expiry. Approval consumption is atomic and one-shot — concurrent approvals or retries cannot dispatch the same side effect twice.

The approving operator’s identity (approved_by) is recorded in the grant and bound into the grant signature.

  • Lease revocation — invalidate a specific lease.
  • Approval invalidation — expire or deny a pending approval.
  • Global kill-switchlatchgate revoke advances the revocation epoch. All leases and grants from prior epochs are immediately invalid. See CLI Reference for details.

ACLs map principals to the actions they may invoke, stored in policies/data.json. Sinks are auto-derived from manifest declared_side_effects — a security invariant that is never set by hand, in either the TUI or the CLI.

Switch to the Setup screen, Principals sub-tab (63):

  • / (or k/j) — select a principal.
  • g — grant actions to the selected principal. Action IDs are validated against manifests as you type, so typos are caught immediately.
  • x — revoke actions from the selected principal. Sinks are recomputed from the remaining actions.
  • n — add a new principal entry.
CLI equivalent

The latchgate policy command manages policies/data.json without manual JSON editing.

Grant actions:

Terminal window
latchgate policy grant agent-ops http_fetch,github_read,http_post
✓ agent-ops: 3 action(s) granted
Actions http_fetch, github_read, http_post
Sinks http_read, http_write (auto-derived)

Action IDs are validated against manifests before writing. Unknown actions are rejected with a suggestion for typos.

Revoke actions (sinks are recomputed from the remaining actions; if all are revoked, the principal entry is removed):

Terminal window
latchgate policy revoke agent-ops http_post

View the ACL:

Terminal window
latchgate policy show # all principals
latchgate policy show agent-ops # single principal with risk breakdown
latchgate policy list # compact table

latchgate init --preset permissive creates a wildcard ACL ("*") granting all actions to any caller. This makes init => up work out of the box for evaluation. For production, use a restrictive preset — the ACL starts empty and you use policy grant to add specific principals.

For ACL changes (granting/revoking actions), use latchgate policy grant and latchgate policy revoke — see Managing ACLs via CLI above.

For custom Rego rules beyond ACL management, edit policies/opa/latchgate.rego directly, then reload OPA. Run policy tests with:

Terminal window
make test-opa

The kernel sends the same PolicyInput regardless of policy complexity — you can add custom rules without modifying LatchGate itself.

For action manifest format and built-in actions, see Actions & Manifests. For writing custom actions with policy hooks, see Custom Actions.