Approval System¶
The approval system provides a unified interface for tool execution authorization across multiple channels. When sensitive tools are invoked, the system routes approval requests to the appropriate provider based on the session context.
Architecture¶
Tool Invocation ──► CompositeProvider ──► Channel Routing
│
┌───────────┼───────────┐
▼ ▼ ▼
Gateway Channel TTY Fallback
(WebSocket) (Telegram/ (Terminal)
Discord/
Slack)
Providers¶
CompositeProvider¶
The CompositeProvider is the central router. It evaluates registered providers in order and routes to the first one whose CanHandle() returns true for the given session key.
Routing rules:
- P2P sessions (p2p:... keys) use a dedicated P2P fallback — never the headless provider
- Non-P2P sessions fall back to the TTY provider when no other provider matches
- If no provider matches, the request is denied (fail-closed)
GatewayProvider¶
Routes approval requests to connected companion apps via WebSocket. Active when at least one companion is connected to the gateway.
TTYProvider¶
Prompts the terminal user via stdin/stderr. Supports three responses:
| Input | Behavior |
|---|---|
y / yes |
Approve this single invocation |
a / always |
Approve and grant persistent access for this tool in this session |
N / anything else |
Deny |
TTY approval is unavailable when stdin is not a terminal (e.g., Docker containers, background processes).
The approval banner, optional summary line, and [y/a/N] prompt are emitted through seam-aware TTY streams so tests can capture the full interaction without replacing process-global stdin or stderr.
If the terminal prompt reaches EOF before an approval answer is received, Lango treats that as a safe denial instead of surfacing a read error.
One-shot Approve vs Always Allow¶
Lango distinguishes between two approval scopes:
| Action | Scope |
|---|---|
Approve / yes |
Current request only |
Always Allow / always |
Session-wide persistent grant |
A normal one-shot approval now also acts as a turn-local replay grant for the current request. If the agent retries the same canonical approval action later in the same turn, Lango reuses the approval result instead of opening a second identical prompt.
Canonicalization can ignore approval-neutral params. For example, browser_search shares the same turn-local approval state across limit-only variants of the same query.
Likewise, if the same canonical action was denied in the current request, Lango blocks the duplicate retry immediately. Timeouts are retryable only within a bounded per-turn budget; once that budget is exhausted, later identical retries are blocked until the next user turn.
HeadlessProvider¶
Auto-approves all requests with WARN-level audit logging. Intended for headless environments (Docker, CI) where no interactive approval is possible.
Security: HeadlessProvider is never used for P2P sessions. Remote peers cannot trigger auto-approval.
Grant Store¶
The GrantStore tracks per-session, per-tool "always allow" grants in memory. When a user selects "always" on a TTY prompt, subsequent invocations of that tool in the same session are auto-approved.
Properties:
- In-memory only — grants are cleared on application restart
- Optional TTL — grants can expire automatically via SetTTL()
- Scoped — grants are per session key + tool name
- Revocable — individual grants or entire sessions can be revoked
Grant Lifecycle¶
| Method | Description |
|---|---|
Grant(sessionKey, toolName) |
Record an approval |
IsGranted(sessionKey, toolName) |
Check if a valid grant exists |
Revoke(sessionKey, toolName) |
Remove a single grant |
RevokeSession(sessionKey) |
Remove all grants for a session |
CleanExpired() |
Remove expired grants (when TTL is set) |
This store is separate from the request-scoped turn-local approval cache used to suppress duplicate prompts within a single agent turn.
Approval Request¶
Each approval request contains:
| Field | Type | Description |
|---|---|---|
ID |
string | Unique request identifier |
ToolName |
string | Name of the tool requiring approval |
SessionKey |
string | Session key (determines routing) |
Params |
map | Tool invocation parameters |
Summary |
string | Human-readable description of the action |
CreatedAt |
time | Request timestamp |
Approval Policies¶
The approval policy controls which tools require approval:
| Policy | Behavior |
|---|---|
dangerous |
Only tools marked as dangerous require approval |
all |
All tool invocations require approval |
configured |
Only tools explicitly listed in the policy require approval |
none |
No approval required (all tools auto-approved) |
Configure via security.interceptor.approvalPolicy:
{
"security": {
"interceptor": {
"enabled": true,
"approvalPolicy": "dangerous"
}
}
}
P2P Approval Pipeline¶
Inbound P2P tool invocations pass through a three-stage approval pipeline:
- Firewall ACL — Static allow/deny rules by peer DID and tool pattern
- Reputation Check — Peer trust score must exceed
minTrustScore - Owner Approval — Interactive approval via the composite provider
Small paid tool invocations can be auto-approved when the amount is below payment.limits.autoApproveBelow.
CLI Commands¶
lango approval status [--output table|json] # Show approval system configuration
Both table and JSON modes write through the Cobra command output stream so wrappers and test harnesses can capture approval status output directly. Unknown --output values fail fast before config loading begins.
Configuration¶
| Setting | Default | Description |
|---|---|---|
security.interceptor.enabled |
false |
Enable the security interceptor |
security.interceptor.approvalPolicy |
"dangerous" |
Approval policy for tool invocations |
security.interceptor.redactPII |
false |
Redact PII in tool inputs/outputs |