AID-H-018.001: Tool Parameter Constraint & Schema Validation
Pillars: app
Lifecycle: building, operation
Scope boundary
Owns deterministic schema, type, format, range, enum, cardinality, and dangerous-string validation for the parameters of one tool call before execution. It does not decide whether the caller is authorized or whether a valid value may flow to the destination.
AID-H-018.002: AID-H-018.001 determines whether parameters satisfy the tool's structural contract; AID-H-018.002 determines whether the verified subject and context are authorized to invoke the action. Well-formed input can still be unauthorized.
AID-H-018.005: AID-H-018.001 asks whether each parameter is syntactically valid; AID-H-018.005 asks whether the specific runtime value and its provenance may flow to that sink. A valid email address or URL can still be an unauthorized exfiltration destination.
AID-H-017.002: AID-H-018.001 validates calls to an exposed tool; AID-H-017.002 determines whether that tool should exist as a narrow, single-purpose capability and what backend authority it holds. Parameter validation cannot make an ambient-power tool least privilege.
AID-H-018.009: AID-H-018.001 determines whether a path value satisfies the tool's structural contract; AID-H-018.009 proves which filesystem object that value actually opens or affects. A syntactically valid path can still escape through links, mounts, aliases, or replacement races.
Implementation guidance
- AID-H-018.001-G001: Use Pydantic or JSON Schema to enforce tool parameter schemas at dispatch time.
AID-H-018.001-G001
Concept:
Tool parameter validation belongs in the dispatcher, not inside individual tools. The dispatcher should reject unknown tools, reject non-object payloads, validate against strict schemas, and only invoke the tool with the validated result.
Step 1: Define strict schemas for each tool
# File: agent/tool_schemas.py
from __future__ import annotations
import os
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, ValidationError, constr
SUBJECT_MIN_LENGTH = int(os.environ["VERIFIED_H018_EMAIL_SUBJECT_MIN_CHARACTERS"])
SUBJECT_MAX_LENGTH = int(os.environ["VERIFIED_H018_EMAIL_SUBJECT_MAX_CHARACTERS"])
if SUBJECT_MIN_LENGTH < 1 or SUBJECT_MAX_LENGTH < SUBJECT_MIN_LENGTH:
raise RuntimeError("verified email-subject bounds are invalid")
class GetStockPriceParams(BaseModel):
model_config = ConfigDict(extra="forbid", strict=True, str_strip_whitespace=True)
ticker: constr(pattern=r"^[A-Z]{1,5}$")
class SendEmailParams(BaseModel):
model_config = ConfigDict(extra="forbid", strict=True, str_strip_whitespace=True)
recipient: constr(pattern=r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
subject: constr(min_length=SUBJECT_MIN_LENGTH, max_length=SUBJECT_MAX_LENGTH)
priority: Literal["high", "normal", "low"] = "normal"
Step 2: Fail closed in the dispatcher
# File: agent/dispatcher.py
from __future__ import annotations
from collections.abc import Callable
import json
import logging
from typing import Any
from pydantic import ValidationError
from .handlers import get_stock_price, send_email
from .tool_schemas import GetStockPriceParams, SendEmailParams
class ToolDispatchRejected(Exception):
pass
ToolHandler = Callable[..., Any]
logger = logging.getLogger("agent.tool_dispatch")
TOOL_SCHEMAS = {
"get_stock_price": GetStockPriceParams,
"send_email": SendEmailParams,
}
TOOL_HANDLERS: dict[str, ToolHandler] = {
"get_stock_price": get_stock_price,
"send_email": send_email,
}
def log_security_event(event: str, **fields: Any) -> None:
logger.info(json.dumps({"event": event, **fields}, sort_keys=True, default=str))
def dispatch_tool(tool_name: str, raw_params: dict, request_id: str) -> Any:
if not isinstance(request_id, str) or not request_id:
raise ToolDispatchRejected("request_id is required")
schema = TOOL_SCHEMAS.get(tool_name)
handler = TOOL_HANDLERS.get(tool_name)
if schema is None or handler is None:
log_security_event("tool_param_validation_failed", request_id=request_id, tool_name=tool_name, reason="unknown_tool")
raise ToolDispatchRejected(f"unknown tool: {tool_name}")
if not isinstance(raw_params, dict):
log_security_event("tool_param_validation_failed", request_id=request_id, tool_name=tool_name, reason="arguments_not_object")
raise ToolDispatchRejected("tool arguments must be a JSON object")
try:
validated = schema.model_validate(raw_params)
except ValidationError as exc:
log_security_event(
"tool_param_validation_failed",
request_id=request_id,
tool_name=tool_name,
errors=exc.errors(),
)
raise ToolDispatchRejected(f"invalid parameters for {tool_name}") from exc
log_security_event(
"tool_param_validation_passed",
request_id=request_id,
tool_name=tool_name,
schema_name=schema.__name__,
)
return handler(**validated.model_dump())
Step 3: Return a structured deny result to the agent loop
# File: agent/tool_runtime.py
from .dispatcher import ToolDispatchRejected, dispatch_tool
def handle_tool_call(tool_name: str, raw_params: dict, request_id: str) -> dict:
try:
result = dispatch_tool(tool_name, raw_params, request_id)
return {"status": "ok", "result": result}
except ToolDispatchRejected as exc:
return {"status": "blocked", "reason": str(exc)}
Verification evidence: Unit-test unknown tools, non-object arguments, extra keys, malformed values, and handler non-invocation on every deny path. Retain structured allow/deny events with tool name, schema version, dispatcher release, and request correlation ID in the external audit pipeline; independently replay the negative corpus before promotion.
Action: Validate every LLM-produced tool call at dispatch time, forbid unknown keys with extra="forbid", and never pass raw model output directly into a tool implementation.
Keep the action safely bounded
Reconcile the complete population of every registered tool call and raw parameter object presented to the dispatcher; reject expired, revoked, or stale policy and asset bindings under the policy-defined freshness window.
Open-source tools
- Pydantic
- Ajv
- python-jsonschema
- Instructor
- Microsoft TypeChat
- Zod
Commercial / hosted tools
- Kong Gateway Enterprise (custom schema/plugin policy required at agent-tool dispatch)
- Kong Konnect (custom schema/plugin policy required at agent-tool dispatch)
- Apigee API Management (custom schema policy/extension required for agent-tool dispatch)
Threat mappings
MITRE ATLAS
- AML.T0053 AI Agent Tool Invocation
MAESTRO
- Agent Tool Misuse (L7)
- Input Validation Attacks (L3)
OWASP LLM Top 10 2026
- LLM01:2026 Prompt Injection (strict tool schemas and argument validation limit injected output from becoming unsafe tool parameters)
- LLM03:2026 Excessive Agency
- LLM10:2026 Improper Output Handling
OWASP ML Top 10 2023
- N/A
OWASP Top 10 for Agentic Applications 2026
- ASI02:2026 Tool Misuse and Exploitation
NIST Adversarial Machine Learning 2025
- NISTAML.039 Compromising connected resources (parameter constraints prevent reaching unintended resources)
Cisco Integrated AI Security and Safety Framework
- AISubtech-12.1.1 Parameter Manipulation
- AISubtech-4.3.1 Schema Inconsistencies
- AITech-12.1 Tool Exploitation
- AITech-12.2 Insecure Output Handling
- AITech-4.3 Protocol Manipulation
- AISubtech-11.1.2 Tool-Scoped Evasion (rejects tool-specific prohibited argument forms before dispatch)
Google Secure AI Framework 2.0 - Risks
- IIC: Insecure Integrated Component (typed parameters secure tool integrations)
Databricks AI Security Framework 3.0
- Agents - Core 13.2: Tool Misuse (parameter schema validation prevents tool misuse)