← Back to Learn
best-practicesguardrailsagent-safety

Input validation for AI agent tools

Authensor

AI agents generate tool arguments dynamically. Unlike human-driven applications where inputs come from forms with known constraints, agent inputs are generated by a language model that can produce any string. Every tool must validate every argument.

Why agent inputs need extra validation

A language model can generate:

  • Paths with traversal sequences (../../etc/passwd)
  • SQL with injection payloads ('; DROP TABLE users;--)
  • URLs pointing to internal services (http://169.254.169.254/metadata)
  • Numbers outside expected ranges (amount: -1000)
  • Arguments of unexpected types (string where number is expected)

Your tools should handle all of these safely.

Type validation

Verify argument types before processing:

function validateArgs(args: unknown, schema: JSONSchema): ValidationResult {
  const result = validate(args, schema);
  if (!result.valid) {
    return { error: `Invalid arguments: ${result.errors.join(', ')}` };
  }
  return { valid: true };
}

Use the same JSON Schema definitions that describe the tool's parameters to validate incoming arguments.

Path validation

For tools that accept file paths:

function validatePath(inputPath: string, allowedBase: string): string {
  const resolved = path.resolve(inputPath);

  // Prevent path traversal
  if (!resolved.startsWith(allowedBase)) {
    throw new Error('Path outside allowed directory');
  }

  // Block sensitive files
  const blocked = ['.env', 'credentials', 'secrets', '/etc/shadow'];
  if (blocked.some(b => resolved.includes(b))) {
    throw new Error('Access to sensitive file blocked');
  }

  return resolved;
}

SQL validation

For tools that accept database queries:

function validateQuery(query: string): void {
  // Only allow SELECT statements
  const trimmed = query.trim().toUpperCase();
  if (!trimmed.startsWith('SELECT')) {
    throw new Error('Only SELECT queries are allowed');
  }

  // Block dangerous keywords
  const blocked = ['DROP', 'DELETE', 'INSERT', 'UPDATE', 'TRUNCATE', 'ALTER', 'GRANT'];
  for (const keyword of blocked) {
    if (trimmed.includes(keyword)) {
      throw new Error(`Query contains blocked keyword: ${keyword}`);
    }
  }
}

Better yet, use parameterized queries and only allow the agent to provide parameter values, not query structure.

URL validation

For tools that make HTTP requests:

function validateUrl(url: string, allowedDomains: string[]): void {
  const parsed = new URL(url);

  // Block internal IPs
  if (isInternalIP(parsed.hostname)) {
    throw new Error('Internal IP addresses blocked');
  }

  // Block non-HTTPS
  if (parsed.protocol !== 'https:') {
    throw new Error('Only HTTPS URLs allowed');
  }

  // Check domain allowlist
  if (!allowedDomains.some(d => parsed.hostname.endsWith(d))) {
    throw new Error('Domain not in allowlist');
  }
}

Numeric validation

For tools that accept amounts, counts, or other numbers:

function validateAmount(amount: number): void {
  if (typeof amount !== 'number' || isNaN(amount)) {
    throw new Error('Amount must be a number');
  }
  if (amount < 0) {
    throw new Error('Amount cannot be negative');
  }
  if (amount > MAX_AMOUNT) {
    throw new Error(`Amount exceeds maximum (${MAX_AMOUNT})`);
  }
}

Defense in depth

Input validation at the tool level is the last line of defense. It should complement, not replace, policy-level argument validation. The policy engine catches many issues before the tool is called. Tool-level validation catches anything the policy missed.

Keep learning

Explore more guides on AI agent safety, prompt injection, and building secure systems.

View All Guides