Skip to main content

Tool Allowlist Policy

The tool-allowlist policy category enforces a positive allowlist of tools an agent may invoke. The absence of a tool from the allowlist blocks it.

This differs from safety.blocked_tools (deny-only): with the allowlist, you don't have to enumerate every dangerous tool -- you only have to enumerate the safe ones. Critical for Codex and third-party agents whose tool surface is unbounded.

Rules

RuleTypeDefaultDescription
allowed_toolsstring[][]Positive allowlist. Empty = no allowlist (all tools allowed unless in blocked_tools)
blocked_toolsstring[][]Always-deny list. Wins over allowlist (deny overrides allow)
action_on_violationstringblockAction on violation: block or warn

How It Works

The tool-allowlist handler runs at mid_execution and after_workflow (as a fallback for batched tools-used updates). before_workflow is a no-op since no tools have been invoked yet.

Semantics:

  1. Empty allowed_tools -> no positive allowlist; only blocked_tools applies
  2. Non-empty allowed_tools -> ONLY those tools are permitted; any other tool is a violation
  3. blocked_tools always wins (deny overrides allow)

Context Attributes Read

AttributePhasePurpose
context.tools_usedmid, afterList of tool names invoked this run

Example Policy

Strict allowlist for Codex-style agents

{
"name": "Codex tool allowlist",
"category": "tool-allowlist",
"rules": {
"allowed_tools": ["read_file", "list_directory", "search_code"],
"blocked_tools": ["shell_exec", "write_file"],
"action_on_violation": "block"
},
"scope": {
"agents": ["codex-*"]
},
"enabled": true
}

Warn-first rollout

{
"rules": {
"allowed_tools": ["http_get", "summarize"],
"action_on_violation": "warn"
}
}

Roll out as warn first, watch the governance trace for what tools your agents actually use, then promote to block.

SDK Integration

import waxell_observe as waxell
waxell.init()

@waxell.observe(agent_name="codex-assistant", enforce_policy=True)
async def codex_agent(query: str) -> str:
return await run_agent(query)

The handler reads context.tools_used -- the runtime / observe SDK populates this as the agent invokes tools. Mid-execution checks fire after each tool batch.

Observability

FieldExample (BLOCK)
Categorytool-allowlist
Actionblock
ReasonTool 'shell_exec' is on the deny list or Tool 'fetch_url' is not on the allowlist
Metadata{"blocked_tool": "shell_exec", "allowed_tools": [...]}

Common Gotchas

  • Exact name match. "shell" does not block "shell_exec". Use the full tool name.
  • Empty allowlist = NO allowlist. Setting allowed_tools: [] does not block all tools -- it disables the allowlist and falls back to blocked_tools only. To block all tools, use a single never-matching name like ["__none__"].
  • blocked_tools overrides allowed_tools. Listing the same tool in both = blocked.
  • Differs from safety.blocked_tools. Safety's blocked_tools is checked via check_tool_allowed() (a standalone API). The allowlist handler checks context.tools_used automatically at mid_execution. Use both for layered defense.
  • Mid-execution fires only if the runtime calls the handler between steps. after_workflow is the fallback to catch batched updates.
  • First violation short-circuits the run. The handler returns BLOCK on the first non-allowed tool; subsequent tools aren't checked.

Next Steps