AID-H-017.002: Least-Privilege Tool Architecture
Pillars: app
Lifecycle: building
Scope boundary
Owns architectural minimization of the effective agent tool surface through allowlisted, strongly typed, single-purpose capabilities with bounded parameters, projections, credentials, cardinality, destinations, and side effects.
AID-H-018.004: AID-H-017.002 designs the smallest safe tool primitives and removes ambient-power capabilities; AID-H-018.004 dynamically selects the allowed subset and budget for a particular authenticated request or session. A narrow registry is the architectural ceiling, while dynamic scope is the current runtime grant.
AID-H-018.001: AID-H-017.002 determines which granular tools should exist and what backend privilege they hold; AID-H-018.001 validates the parameters supplied to one exposed tool. A perfectly validated generic shell remains overprivileged by architecture.
Implementation guidance
- AID-H-017.002-G001: Design granular, single-purpose tools instead of overly-permissive ones.
AID-H-017.002-G001
Concept:
Do not expose a generic execute_sql capability. Publish narrow methods with typed parameters, explicit result projection, bounded cardinality, and a database identity that cannot write or read unrelated tables.
Step 1: Implement narrow database tools
# File: agent_tools/customer_reads.py
from __future__ import annotations
import os
from dataclasses import dataclass
from psycopg import Connection
from psycopg.rows import dict_row
def _required_positive_int(name: str) -> int:
value = int(os.environ[name])
if value <= 0:
raise RuntimeError(f"{name} must be a positive integer")
return value
ORDER_QUERY_DEFAULT_LIMIT = _required_positive_int("VERIFIED_H017_ORDER_QUERY_DEFAULT_LIMIT")
ORDER_QUERY_MAX_LIMIT = _required_positive_int("VERIFIED_H017_ORDER_QUERY_MAX_LIMIT")
if ORDER_QUERY_DEFAULT_LIMIT > ORDER_QUERY_MAX_LIMIT:
raise RuntimeError("verified default order limit exceeds maximum")
@dataclass(frozen=True)
class CustomerReadTools:
connection: Connection
def get_user_by_id(self, user_id: int) -> dict | None:
if isinstance(user_id, bool) or not isinstance(user_id, int) or user_id <= 0:
raise ValueError("user_id must be a positive integer")
with self.connection.cursor(row_factory=dict_row) as cursor:
cursor.execute(
"SELECT user_id, status, plan FROM agent_safe.customer_directory WHERE user_id = %s",
(user_id,),
)
return cursor.fetchone()
def get_user_orders(self, user_id: int, limit: int = ORDER_QUERY_DEFAULT_LIMIT) -> list[dict]:
if isinstance(user_id, bool) or not isinstance(user_id, int) or user_id <= 0:
raise ValueError("user_id must be a positive integer")
if isinstance(limit, bool) or not isinstance(limit, int) or not 1 <= limit <= ORDER_QUERY_MAX_LIMIT:
raise ValueError("limit must be inside the verified order-query policy")
with self.connection.cursor(row_factory=dict_row) as cursor:
cursor.execute(
"SELECT order_id, created_at, state FROM agent_safe.order_summary WHERE user_id = %s ORDER BY created_at DESC LIMIT %s",
(user_id, limit),
)
return list(cursor.fetchall())
Step 2: Enforce a read-only database role
CREATE ROLE agent_customer_reader LOGIN;
ALTER ROLE agent_customer_reader SET default_transaction_read_only = on;
GRANT USAGE ON SCHEMA agent_safe TO agent_customer_reader;
GRANT SELECT ON agent_safe.customer_directory, agent_safe.order_summary TO agent_customer_reader;
REVOKE CREATE ON SCHEMA public FROM agent_customer_reader;
Privilege boundary: default_transaction_read_only is defense in depth and is session-overridable; the real write barrier is ownership plus explicit table/schema privileges, which must be read back and tested independently. Do not grant this role ownership, SUPERUSER, BYPASSRLS, or any write privilege.
Verification evidence: Export the published tool schema, database grants, and a negative test showing the role cannot insert, update, delete, access non-approved tables, or submit arbitrary SQL. Re-run the permission readback on every role or schema change and fail the release if a generic query parameter or write privilege appears.
Action: Register only the two typed methods above with the agent runtime; keep the connection and SQL text server-owned.
Keep the action safely bounded
Reconcile the complete population of every registered tool operation, route, query, and permission in the agent's assigned tool set; reject expired, revoked, or stale policy and asset bindings under the policy-defined freshness window.
Open-source tools
- Pydantic
- python-jsonschema
- FastAPI
- Flask
- Microsoft Semantic Kernel
- Model Context Protocol Python SDK (typed tool registration only; external authorization required)
- Model Context Protocol TypeScript SDK (typed tool registration only; external authorization required)
Commercial / hosted tools
- Kong Gateway Enterprise
- Kong Konnect
- Google Cloud Apigee API Management
- AWS API Gateway
Threat mappings
MITRE ATLAS
- AML.T0050 Command and Scripting Interpreter
- AML.T0053 AI Agent Tool Invocation
- AML.T0098 AI Agent Tool Credential Harvesting (least privilege limits credential exposure via tools)
- AML.T0086 Exfiltration via AI Agent Tool Invocation (least-privilege tools limit data exfiltration surface)
- AML.T0101 Data Destruction via AI Agent Tool Invocation (granular tools prevent destructive operations)
- AML.T0108 AI Agent
- AML.T0072 Cyber Communication Channel (denying unrestricted HTTP and shell tools removes the agent's ability to open arbitrary outbound channels)
- AML.T0089 Enterprise Environment Discovery (without generic shell or broad filesystem tools an injected agent cannot enumerate host processes, configuration, or users)
- AML.T0075 Enterprise Resource Discovery (no generic shell, SQL, or broad filesystem tools to enumerate accounts, files, or services)
- AML.T0133 Discover AI Agent Runtime Capabilities (a narrow published tool schema and backend grants limit the capabilities revealed through runtime probing)
MAESTRO
- Agent Tool Misuse (L7)
OWASP LLM Top 10 2026
- LLM01:2026 Prompt Injection (least-privilege single-purpose tool architecture bounds the actions available after a successful injection)
- LLM03:2026 Excessive Agency
- LLM08:2026 Hidden Context Exposure
OWASP ML Top 10 2023
- N/A
OWASP Top 10 for Agentic Applications 2026
- ASI02:2026 Tool Misuse and Exploitation
- ASI03:2026 Identity and Privilege Abuse
- ASI05:2026 Unexpected Code Execution (RCE)
NIST Adversarial Machine Learning 2025
- NISTAML.039 Compromising connected resources
Cisco Integrated AI Security and Safety Framework
- AISubtech-12.1.1 Parameter Manipulation
- AISubtech-12.1.3 Unsafe System / Browser / File Execution
- AISubtech-14.1.2 Insufficient Access Controls
- AISubtech-2.1.2 Capability and Permission Overreach
- AISubtech-8.2.3 Data Exfiltration via Agent Tooling
- AISubtech-9.1.1 Code Execution
- AISubtech-9.1.2 Unauthorized or Unsolicited System Access
- AISubtech-9.1.3 Unauthorized or Unsolicited Network Access
- AITech-12.1 Tool Exploitation
- AITech-2.1 Excessive Agency
- AITech-8.2 Data Exfiltration / Exposure
- AITech-9.1 Model or Agentic System Manipulation
Google Secure AI Framework 2.0 - Risks
- RA: Rogue Actions (least-privilege tools limit blast radius of rogue actions)
Databricks AI Security Framework 3.0
- Agents - Core 13.2: Tool Misuse
- Agents - Core 13.3: Privilege Compromise (least-privilege tools limit privilege escalation)
- Agents - Core 13.11: Unexpected RCE and Code Attacks (granular tools prevent code injection)
- Model Serving - Inference requests 9.13: Excessive agency