Skip to content

Commit bb4a743

Browse files
authored
fix(dotnet): validate MCP invocation headers (#9539)
Co-authored-by: Copilot <[email protected]> Copilot-Session: b7854637-38f5-42cd-8e94-bd46f8c06f01
1 parent 54b558b commit bb4a743

3 files changed

Lines changed: 69 additions & 0 deletions

File tree

‎dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@ public async Task<McpServerToolResultContent> InvokeToolAsync(
104104
ThrowIfListToolsArgumentsSpecified(arguments);
105105
}
106106

107+
if (headers is not null)
108+
{
109+
foreach (KeyValuePair<string, string> header in headers)
110+
{
111+
HttpHeaderValidation.ValidateNoProhibitedCharacters(
112+
header.Key,
113+
nameof(headers),
114+
"HTTP header name contains invalid characters.");
115+
HttpHeaderValidation.ValidateNoProhibitedCharacters(
116+
header.Value,
117+
nameof(headers),
118+
"HTTP header value contains invalid characters.");
119+
}
120+
}
121+
107122
if (this._httpClientProvider is not null)
108123
{
109124
TaskCompletionSource<bool> invocationCompleted = new(TaskCreationOptions.RunContinuationsAsynchronously);

‎dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
<PropertyGroup>
99
<InjectSharedThrow>true</InjectSharedThrow>
10+
<InjectSharedHttpHeaderValidation>true</InjectSharedHttpHeaderValidation>
1011
<InjectSharedOriginPinning>true</InjectSharedOriginPinning>
1112
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
1213
<InjectTrimAttributesOnLegacy>true</InjectTrimAttributesOnLegacy>

‎dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,59 @@ public async Task DisposeAsync_WhenCalledMultipleTimes_ShouldHandleGracefullyAsy
9090

9191
#region HttpClientProvider Tests
9292

93+
[Theory]
94+
[InlineData("\r", false, false)]
95+
[InlineData("\n", false, false)]
96+
[InlineData("\0", false, false)]
97+
[InlineData("\r", true, false)]
98+
[InlineData("\n", true, false)]
99+
[InlineData("\0", true, false)]
100+
[InlineData("\r", false, true)]
101+
[InlineData("\n", false, true)]
102+
[InlineData("\0", false, true)]
103+
[InlineData("\r", true, true)]
104+
[InlineData("\n", true, true)]
105+
[InlineData("\0", true, true)]
106+
public async Task InvokeToolAsync_WithInvalidHeader_ShouldRejectBeforeClientCreationAsync(
107+
string invalidCharacter,
108+
bool invalidName,
109+
bool useProvider)
110+
{
111+
// Arrange
112+
bool clientCreationAttempted = false;
113+
Task<HttpClient?> ProviderAsync(string url, CancellationToken ct)
114+
{
115+
clientCreationAttempted = true;
116+
throw new InvalidOperationException("Client creation should not be reached.");
117+
}
118+
119+
HttpMessageHandler CreateHandler()
120+
{
121+
clientCreationAttempted = true;
122+
throw new InvalidOperationException("Client creation should not be reached.");
123+
}
124+
125+
await using DefaultMcpToolHandler handler = new(useProvider ? ProviderAsync : null, CreateHandler);
126+
Dictionary<string, string> headers = new()
127+
{
128+
[invalidName ? $"X-Test{invalidCharacter}" : "X-Test"] = invalidName ? "value" : $"value{invalidCharacter}"
129+
};
130+
131+
// Act
132+
async Task actAsync() => await handler.InvokeToolAsync(
133+
serverUrl: "https://example.com/mcp",
134+
serverLabel: null,
135+
toolName: "testTool",
136+
arguments: null,
137+
headers: headers,
138+
connectionName: null);
139+
140+
// Assert
141+
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(actAsync);
142+
Assert.Equal("headers", exception.ParamName);
143+
Assert.False(clientCreationAttempted);
144+
}
145+
93146
[Fact]
94147
public async Task InvokeToolAsync_WithHttpClientProvider_ShouldCallProviderAsync()
95148
{

0 commit comments

Comments
 (0)