Skip to main content

Tool Argument Schema Policy

The tool-argument-schema policy enforces OWASP LLM06a (excessive agency, sub-control "argument-level scoping"). It validates tool-call arguments against JSON schemas before invocation. This is the companion control to Tool Allowlist — where tool-allowlist answers "which tools can this agent call?", this handler answers "with what arguments?".

The classic exfiltration shape: delete_user is on the allowlist, but delete_user(role="admin") from a customer-facing surface is not. This handler blocks the second case even though the tool name is permitted.

Uses the jsonschema library when installed; falls back to a minimal hand-rolled validator covering type, required, enum, properties, and additionalProperties=false.

Rules

RuleTypeDefaultDescription
schemasobject{}Map of tool_name → JSON Schema. Each call to the tool must validate against the schema.
require_schema_for_all_toolsbooleanfalseStrict mode — any tool call without a declared schema is rejected
action_on_violationstring"block""block" or "warn"

How It Works

PhaseBehavior
before_workflowValidates any tool calls already queued at workflow start (rare — workflow restart with queued actions)
mid_executionPrimary enforcement — every queued tool call between LLM turns
after_workflow(no-op — argument validation is a pre-call concern)

Per pending action, the handler:

  1. Skips non-tool-call entries (kind must be "tool_call", "tool", or absent).
  2. Looks up schemas[tool_name].
  3. If no schema and require_schema_for_all_tools=true → BLOCK with missing_schema.
  4. If schema present, validates args (or arguments) against it → BLOCK with schema_violation on failure.

Context Attributes Read

AttributePhasePurpose
context.pending_actionsbefore, midList of {"kind": "tool_call", "tool": "...", "args": {...}}

Example Policy

Atlassian-style policy — cap fund transfers at $10k, restrict delete_user to non-admin targets, and require schemas for everything else:

{
"schemas": {
"transfer_funds": {
"type": "object",
"required": ["amount", "recipient"],
"properties": {
"amount": {"type": "number", "maximum": 10000, "minimum": 0},
"recipient": {"type": "string", "pattern": "^acct_[a-z0-9]+$"},
"memo": {"type": "string", "maxLength": 200}
},
"additionalProperties": false
},
"delete_user": {
"type": "object",
"required": ["id"],
"properties": {
"id": {"type": "string"},
"role": {"type": "string", "enum": ["user"]}
},
"additionalProperties": false
},
"send_email": {
"type": "object",
"required": ["to", "subject", "body"],
"properties": {
"to": {"type": "string", "format": "email"},
"subject": {"type": "string", "maxLength": 200},
"body": {"type": "string", "maxLength": 5000}
}
}
},
"require_schema_for_all_tools": true,
"action_on_violation": "block"
}

SDK Integration

import waxell_observe as waxell

waxell.init()

@waxell.observe(agent_name="finance-bot", enforce_policy=True)
async def process(intent: str) -> str:
# When the agent queues a tool call, the runtime adds
# {"kind": "tool_call", "tool": "transfer_funds", "args": {...}}
# to context.pending_actions. mid_execution validates each entry
# against schemas["transfer_funds"] before the call is dispatched.
return await run_finance_workflow(intent)

Observability

FieldExample
Categorytool-argument-schema
Actionblock
Reason"Tool 'transfer_funds' arguments failed schema validation: 25000 is greater than the maximum of 10000 (at amount)"
Metadata{"phase": "mid", "tool": "transfer_funds", "signal": "schema_violation", "error": "25000 is greater than the maximum of 10000 (at amount)", "owasp": "LLM06"}

Missing schema in strict mode:

FieldExample
Reason"Tool 'list_pages' has no declared argument schema and require_schema_for_all_tools is true."
Metadata{"signal": "missing_schema", "tool": "list_pages", "owasp": "LLM06"}

Common Gotchas

  • Argument key is args OR arguments. The handler reads action_obj.get("args") or action_obj.get("arguments") or {}. Both shapes work — the rest of the runtime tends to use args.
  • Tool name is tool OR name. Same fallback — most pending-action records use tool.
  • Invalid policy schemas fail-open. If your JSON Schema is itself malformed (jsonschema.SchemaError), the handler logs a warning and returns ALLOW — so a bad schema never blocks legitimate work. Validate your schemas separately.
  • The minimal fallback validator is limited. Without jsonschema installed, only type, required, enum, properties, and additionalProperties=false are checked. No $ref, oneOf, anyOf, pattern, format, minimum/maximum, etc. Production deploys should always have jsonschema installed.
  • require_schema_for_all_tools=true blocks ANY undeclared tool. Combine carefully with Tool Allowlist — if the allowlist permits a tool but you forget to add its schema, this handler will still reject it.
  • Error messages are truncated to 200 characters. Verbose jsonschema path/cause output would dominate the metadata payload; the policy keeps the human-readable message and the dotted path.
  • Empty schemas + require_schema_for_all_tools=false is effectively a no-op. The policy must have either schemas configured or strict mode enabled to do anything.

Next Steps