AID-H-018.001: Tool Parameter Constraint & Schema Validation

Version 1.20261015 · Actionable sub-technique

Define strict, machine-readable schemas for each agent tool's input parameters and enforce validation before execution, including types, formats, ranges, enums, cardinality, and bounded dangerous-string rules. Reject structurally invalid or prohibited parameter values before they reach authorization or execution; filesystem-object confinement for valid path values remains in AID-H-018.009.

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.
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

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-H-018.001: Tool Parameter Constraint & Schema Validation | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version