Skip to content

Security: MCP Tool Call Name Not Validated Against Allowlist Before Execution #1066

@BenjaminMichaelis

Description

@BenjaminMichaelis

Summary

When the AI agent decides to call an MCP tool, AIChatService passes functionCallItem.FunctionName directly to mcpClient.CallToolAsync() without first validating that the function name is in an allowed set. A prompt-injected payload that persuades the model to fabricate a tool call with an arbitrary name could cause unexpected behavior.

Affected Code

EssentialCSharp.Chat.Shared/Services/AIChatService.cs — both GetChatCompletionCore and ExecuteFunctionCallAsync:

// No allowlist check before calling
var toolResult = await mcpClient.CallToolAsync(
    functionCallItem.FunctionName,   // ← comes directly from model output
    arguments: arguments,
    cancellationToken: cancellationToken);

The list of tools is loaded from the MCP client at request time:

var mcpTools = await mcpClient.ListToolsAsync(cancellationToken: cancellationToken);

But this list is only used to advertise tools to the model — it is not re-checked when a tool call is received back from the model.

Risk

OWASP AI Agent Security — Risk: Tool Abuse & Privilege Escalation

If an indirect prompt injection convinces the model to emit a FunctionCallResponseItem with a crafted FunctionName, the application will attempt to call that tool name. While the MCP server would likely reject unknown tool names, the defense-in-depth principle dictates that the client should also enforce the allowlist. Future tool additions or MCP client changes could inadvertently expose new capabilities.

Recommended Mitigation

Cache the list of allowed tool names retrieved in CreateResponseOptionsAsync and validate each incoming FunctionName against it before calling:

// In CreateResponseOptionsAsync, return the allowed names alongside options
var mcpTools = await mcpClient.ListToolsAsync(cancellationToken: cancellationToken);
var allowedToolNames = mcpTools.Select(t => t.Name).ToHashSet(StringComparer.Ordinal);

// In ExecuteFunctionCallAsync / GetChatCompletionCore, before CallToolAsync:
if (!allowedToolNames.Contains(functionCallItem.FunctionName))
{
    // Log and reject — do not call the tool
    logger.LogWarning("Model requested disallowed tool: {ToolName}", functionCallItem.FunctionName);
    return ("Tool not permitted.", responseId);
}

References

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions