Tool calling turns a model output into an action. That one transition is where an AI assistant becomes a security principal, but many systems protect it with only a JSON schema. The arguments are valid, therefore the action runs. Valid syntax is not valid authority.
The agent should propose an action. A deterministic layer should decide whether that action is allowed, full stop.

Treat model output as untrusted input
Prompt injection, misunderstood context, and plain model mistakes all arrive through the same channel: tool name plus arguments. Validate types and lengths, reject unknown fields, normalize identifiers, and never allow the model to choose a credential or authorization scope.
async function executeTool(call: ToolCall, context: AgentContext) {
const input = TOOL_SCHEMAS[call.name].parse(call.arguments);
const decision = await policy.evaluate({
subject: context.agentId,
delegatedBy: context.userId,
tenant: context.tenantId,
action: call.name,
resource: input.resourceId,
});
if (!decision.allowed) throw new Error("tool call denied");
return tools[call.name](input, decision.credential);
}The policy decision needs the real resource, not just the tool name. Permission to read_file inside one project is not permission to read every path the tool can reach. Resolve aliases and indirect references before the final authorization check, otherwise the model can ask for an allowed-looking object that redirects to a protected one.
Separate planning from execution
Run planning without production credentials when possible. The agent can assemble a proposed sequence, then an execution service evaluates each step with current context. This reduces the chance that a malicious document can directly drive a privileged connector.
High-impact tools need stronger controls. Sending money, deleting records, publishing externally, or changing identity policy may require human confirmation tied to the exact normalized action. A generic "allow this agent" click at session start is not meaningful approval for a different action twenty minutes later.
Issue short-lived credentials for the approved tool and audience. RFC 8693 token exchange is useful when a user delegates to an agent and the agent needs a narrower downstream token. Do not hand every tool the agent's original broad token.
Make retries safe and visible
Agents retry. Networks retry also. Destructive tools need idempotency keys and clear side-effect semantics so a timeout does not send the same payment twice. Audit the proposed call, policy decision, credential identity, normalized input, tool result, and retry chain.
Avoid placing secrets or full sensitive outputs into model-visible logs. The audit record should explain what happened without becoming a second data leak.
The takeaway is that tool calling is an authorization workflow. Schemas make calls well formed. Policy makes them permitted. Scoped credentials and audit make them containable when the agent gets the plan wrong.
Akash Devdhar is a Senior Software Engineer specializing in enterprise identity, authentication, authorization, and AI infrastructure. He writes about building secure AI systems using OAuth, OIDC, RBAC, and modern identity architectures.