ADR 0003: Deny-by-Default Tool Policy¶
Status¶
Accepted
Date¶
2024-12-26
Context¶
AI agents in FerrumDeck can execute arbitrary tools via MCP (Model Context Protocol). This creates significant security risks:
- Data exfiltration: Agent could read sensitive files and send to external endpoints
- System compromise: Agent could execute destructive commands
- Privilege escalation: Agent could modify security configurations
- Resource abuse: Agent could consume excessive compute/network resources
We need a security model that: - Prevents unauthorized tool access by default - Allows operators to explicitly grant tool permissions - Supports approval workflows for high-risk operations - Provides audit trail of all tool executions
Decision¶
We implement a Deny-by-Default tool policy with three-tier classification:
Policy Structure¶
tool_allowlist:
allowed: # Automatic execution permitted
- read_file
- list_directory
- git_status
approval_required: # Requires human approval
- write_file
- git_commit
- create_pr
denied: # Always blocked
- delete_repository
- sudo
- shell_exec
Classification Criteria¶
| Tier | Risk Level | Approval | Examples |
|---|---|---|---|
| allowed | read | None | File reads, git status, API GETs |
| approval_required | write | Human or automated | File writes, commits, API POSTs |
| denied | destructive | Never | Deletions, admin operations |
Default Policy¶
Enforcement Points¶
- Gateway (Pre-queue): Check policy before dispatching to worker
- Worker (Pre-execution): Validate policy via control plane API
- MCP Router: Enforce schema validation per tool
Approval Flow¶
- Worker encounters
approval_requiredtool - Worker reports PENDING status with approval request
- Gateway creates approval record with expiry
- Human/system approves via API
- Gateway re-queues step
- Worker executes approved tool
Consequences¶
Positive¶
- Secure by default: Unknown tools cannot execute
- Explicit permissions: Operators consciously grant access
- Audit trail: All policy decisions logged
- Flexible: Per-agent, per-tenant policy customization
- Compliance: Meets principle of least privilege
Negative¶
- Initial friction: Operators must configure allowlists
- Approval latency: High-risk tools require human wait time
- Maintenance: Allowlists need updates as tools change
Mitigations¶
- Provide sensible default policies per agent type
- Implement automated approval for trusted scenarios
- Version policies alongside agent definitions
Implementation Details¶
Policy Engine (Rust)¶
pub enum PolicyDecision {
Allow,
RequireApproval { reason: String },
Deny { reason: String },
}
pub fn evaluate_tool_call(
tool_name: &str,
tool_input: &Value,
policy: &Policy,
) -> PolicyDecision;
Budget Enforcement¶
Policy engine also enforces budgets:
- max_input_tokens: Total input tokens per run
- max_output_tokens: Total output tokens per run
- max_tool_calls: Maximum tool invocations per run
- max_wall_time_secs: Maximum run duration
- max_cost_cents: Maximum cost per run
Alternatives Considered¶
1. Allow-by-Default¶
- All tools allowed unless explicitly denied
- Rejected: Too risky, easy to miss dangerous tools
2. Capability-Based¶
- Tools request capabilities (read, write, network)
- Rejected: Too coarse-grained for our use case
3. Sandboxing Only¶
- Run all tools in isolated sandbox
- Rejected: Not all tools can be sandboxed, doesn't prevent data exfiltration