Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ When using multiple providers (e.g., skills + file access), combine their rules
})
```

## ⚠️ Security: avoid tool-name collisions

Built-in auto-approval rules match tool calls **solely by tool name**. A rule cannot tell the
provider's own tool apart from any other registered tool that happens to share the same name. If a
different tool — especially one with a caller-configurable name, such as the Harness shell tool
(`HarnessAgentOptions.ShellToolName`) — is registered under a name that one of these rules approves
(e.g. `load_skill`, `read_skill_resource`, `run_skill_script`, or the `file_access_*` names), that
tool will be **silently auto-approved**, bypassing the human approval boundary.

When using auto-approval rules, ensure no other tool's name collides with the reserved names the
rules approve, and never assign a configurable tool name that matches one of them.

## Skills Included

### unit-converter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ It builds on Post 1's personal finance assistant and teaches it to work with *yo
saving and deleting still pause for approval. The `place_trade` tool is also wrapped in an
`ApprovalRequiredAIFunction` (see `TradingTools.cs`), so the harness surfaces an approval prompt
before any trade runs. The trade itself is simulated — no real order is placed.

> ⚠️ **Security — avoid tool-name collisions:** auto-approval rules such as
> `FileAccessProvider.ReadOnlyToolsAutoApprovalRule` match tool calls **solely by tool name**. Any
> other registered tool that shares one of the approved names (`file_access_read`, `file_access_ls`,
> `file_access_grep`) would be silently auto-approved, bypassing the human
> approval boundary. Ensure no other tool's name collides with the reserved names a rule approves.
- **Durable memory, two ways:**
- **File memory** (coarse-grained, explicit) — the agent reads/writes files such as
`watchlist.md`. File memory is on by default; its files live on disk under
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ You can ask the agent to:

E.g. try the following prompt `Please process the sales.csv file by first filtering it to only North region sales, and then calculating the sum of sales by person. I'd like to write the results of the processing to north_region_totals.csv`.

## ⚠️ Security: avoid tool-name collisions

This sample uses `FileAccessProvider.ReadOnlyToolsAutoApprovalRule` to auto-approve read-only file
access tools. Built-in auto-approval rules match tool calls **solely by tool name**, so any other
registered tool that shares one of the approved names (`file_access_read`, `file_access_ls`,
`file_access_grep`) would be **silently auto-approved**, bypassing the
human approval boundary. Ensure no other tool's name collides with the reserved names an
auto-approval rule approves.

## Sample Data

The included `working/sales.csv` contains sales transactions from January to March 2025 with the following columns:
Expand Down
9 changes: 9 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.Harness/HarnessAgentOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,17 @@ public sealed class HarnessAgentOptions
/// Gets or sets the name of the shell execution tool exposed to the model.
/// </summary>
/// <remarks>
/// <para>
/// When <see langword="null"/> (the default), the shell executor's default tool name (<c>run_shell</c>) is used.
/// This property is ignored when <see cref="ShellExecutor"/> is <see langword="null"/>.
/// </para>
/// <para>
/// <b>Security warning:</b> auto-approval rules may match tool calls solely by name. Pay attention to
/// the tool names approved by auto-approval rules for other features. Setting this property to a
/// value that collides with a tool name that is approved by an auto-approval rule for another feature will cause
/// the shell tool to also be auto-approved, bypassing the human approval boundary. Choose a unique
/// name that no other registered tool uses.
/// </para>
/// </remarks>
public string? ShellToolName { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,14 @@ internal static string FormatMemoryBytes(long memoryBytes) =>
/// gating. Container configuration alone is not a sufficient signal
/// to safely auto-execute model-generated commands — the
/// approval/policy decision belongs to the agent author.
/// <para>
/// <b>Security warning:</b> auto-approval rules may match tool calls solely by name. Pay attention to
/// the tool names approved by auto-approval rules for other features. Setting <paramref name="name"/>
/// to a value that collides with a tool name that is approved by an auto-approval rule for another feature will
/// cause this shell tool to also be auto-approved even when <paramref name="requireApproval"/> is
/// <see langword="true"/>, bypassing the human approval boundary. Choose a unique name that no other
/// registered tool uses.
/// </para>
/// </remarks>
/// <param name="name">Function name surfaced to the model.</param>
/// <param name="description">Function description for the model.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,14 @@ private async Task<ShellResult> RunStatelessAsync(string command, CancellationTo
/// container where the tool itself is the boundary).
/// </param>
/// <returns>An <see cref="AIFunction"/> wrapping <see cref="RunAsync"/>.</returns>
/// <remarks>
/// <b>Security warning:</b> auto-approval rules may match tool calls solely by name. Pay attention to
/// the tool names approved by auto-approval rules for other features. Setting <paramref name="name"/>
/// to a value that collides with a tool name that is approved by an auto-approval rule for another feature will
/// cause this shell tool to also be auto-approved even when <paramref name="requireApproval"/> is
/// <see langword="true"/>, bypassing the human approval boundary. Choose a unique name that no other
/// registered tool uses.
/// </remarks>
public override AIFunction AsAIFunction(string name = "run_shell", string? description = null, bool requireApproval = true)
{
if (!requireApproval && !this._acknowledgeUnsafe)
Expand Down
7 changes: 7 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.Tools.Shell/ShellExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ public abstract class ShellExecutor : IAsyncDisposable
/// explicit user approval before executing.
/// </param>
/// <returns>An <see cref="AIFunction"/> wrapping <see cref="RunAsync"/>.</returns>
/// <remarks>
/// <b>Security warning:</b> auto-approval rules may match tool calls solely by name. Pay attention to
/// the tool names approved by auto-approval rules for other features. Setting <paramref name="name"/>
/// to a value that collides with a tool name that is approved by an auto-approval rule for another feature will
/// cause this shell tool to also be auto-approved, bypassing the human approval boundary. Choose a
/// unique name that no other registered tool uses.
/// </remarks>
public abstract AIFunction AsAIFunction(string name = "run_shell", string? description = null, bool requireApproval = true);

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,20 @@ public FileAccessProvider(AgentFileStore fileStore, FileAccessProviderOptions? o
/// The rule matches on the tool name, returning <see langword="true"/> for read-only file access tools
/// and <see langword="false"/> for all other tool calls so that subsequent rules continue to be evaluated.
/// </para>
/// <para>
/// This rule approves calls to exactly the following tool names:
/// <list type="bullet">
/// <item><description><see cref="ReadFileToolName"/> (<c>file_access_read</c>)</description></item>
/// <item><description><see cref="LsToolName"/> (<c>file_access_ls</c>)</description></item>
/// <item><description><see cref="GrepToolName"/> (<c>file_access_grep</c>)</description></item>
/// </list>
/// </para>
/// <para>
/// <b>Security note:</b> because matching is by tool name only, any other registered tool that
/// shares one of these names — for example a configurable-name tool such as the Harness shell
/// tool (<c>HarnessAgentOptions.ShellToolName</c>) that was assigned the same name — will also be auto-approved, bypassing the
/// human approval boundary. Ensure no other tool collides with these reserved names.
/// </para>
/// </remarks>
public static Func<FunctionCallContent, ValueTask<bool>> ReadOnlyToolsAutoApprovalRule { get; } =
functionCall => new ValueTask<bool>(s_readOnlyToolNames.Contains(functionCall.Name));
Expand All @@ -193,6 +207,24 @@ public FileAccessProvider(AgentFileStore fileStore, FileAccessProviderOptions? o
/// The rule matches on the tool name, returning <see langword="true"/> for any file access tool
/// and <see langword="false"/> for all other tool calls so that subsequent rules continue to be evaluated.
/// </para>
/// <para>
/// This rule approves calls to exactly the following tool names:
/// <list type="bullet">
/// <item><description><see cref="WriteToolName"/> (<c>file_access_write</c>)</description></item>
/// <item><description><see cref="ReadFileToolName"/> (<c>file_access_read</c>)</description></item>
/// <item><description><see cref="DeleteFileToolName"/> (<c>file_access_delete</c>)</description></item>
/// <item><description><see cref="LsToolName"/> (<c>file_access_ls</c>)</description></item>
/// <item><description><see cref="GrepToolName"/> (<c>file_access_grep</c>)</description></item>
/// <item><description><see cref="ReplaceToolName"/> (<c>file_access_replace</c>)</description></item>
/// <item><description><see cref="ReplaceLinesToolName"/> (<c>file_access_replace_lines</c>)</description></item>
/// </list>
/// </para>
/// <para>
/// <b>Security note:</b> because matching is by tool name only, any other registered tool that
/// shares one of these names — for example a configurable-name tool such as the Harness shell
/// tool (<c>HarnessAgentOptions.ShellToolName</c>) that was assigned the same name — will also be auto-approved, bypassing the
/// human approval boundary. Ensure no other tool collides with these reserved names.
/// </para>
/// </remarks>
public static Func<FunctionCallContent, ValueTask<bool>> AllToolsAutoApprovalRule { get; } =
functionCall => new ValueTask<bool>(s_allToolNames.Contains(functionCall.Name));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ public ToolApprovalAgent(AIAgent innerAgent, ToolApprovalAgentOptions? options =
/// });
/// </code>
/// </para>
/// <para>
/// <b>Security note:</b> this rule is name-agnostic and approves <b>every</b> tool call regardless
/// of the tool name (<see cref="FunctionCallContent.Name"/>) or arguments. Only use this rule in a fully trusted context.
/// </para>
/// </remarks>
public static Func<FunctionCallContent, ValueTask<bool>> AllToolsAutoApprovalRule { get; } =
_ => new ValueTask<bool>(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ public class ToolApprovalAgentOptions
/// prompting the user. Rules are evaluated in order; the first rule returning <see langword="true"/>
/// causes the function call to be auto-approved.
/// </para>
/// <para>
/// <b>Security warning:</b> auto-approval rules may match tool calls solely by name. A rule provided for
/// one feature (for example a provider's read-only rule) may auto-approve <b>any</b> registered tool
/// whose name matches, not just the tool the rule was designed for. When adding rules here, ensure
/// no unrelated tools you register collide with a name approved by any rule in this list,
/// otherwise that tool will be auto-approved without a human prompt, bypassing the approval boundary.
/// </para>
/// </remarks>
public IEnumerable<Func<FunctionCallContent, ValueTask<bool>>>? AutoApprovalRules { get; set; }
}
27 changes: 27 additions & 0 deletions dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ public sealed partial class AgentSkillsProvider : AIContextProvider, IDisposable
/// The rule matches on the tool name, returning <see langword="true"/> for read-only skill tools
/// and <see langword="false"/> for all other tool calls so that subsequent rules continue to be evaluated.
/// </para>
/// <para>
/// This rule approves calls to exactly the following tool names:
/// <list type="bullet">
/// <item><description><see cref="LoadSkillToolName"/> (<c>load_skill</c>)</description></item>
/// <item><description><see cref="ReadSkillResourceToolName"/> (<c>read_skill_resource</c>)</description></item>
/// </list>
/// </para>
/// <para>
/// <b>Security note:</b> because matching is by tool name only, any other registered tool that
/// shares one of these names — for example a configurable-name tool such as the Harness shell
/// tool (<c>HarnessAgentOptions.ShellToolName</c>) that was assigned the same name — will also be auto-approved, bypassing the
/// human approval boundary. Ensure no other tool collides with these reserved names.
/// </para>
/// </remarks>
public static Func<FunctionCallContent, ValueTask<bool>> ReadOnlyToolsAutoApprovalRule { get; } =
functionCall => new ValueTask<bool>(s_readOnlyToolNames.Contains(functionCall.Name));
Expand All @@ -110,6 +123,20 @@ public sealed partial class AgentSkillsProvider : AIContextProvider, IDisposable
/// The rule matches on the tool name, returning <see langword="true"/> for any skill tool
/// and <see langword="false"/> for all other tool calls so that subsequent rules continue to be evaluated.
/// </para>
/// <para>
/// This rule approves calls to exactly the following tool names:
/// <list type="bullet">
/// <item><description><see cref="LoadSkillToolName"/> (<c>load_skill</c>)</description></item>
/// <item><description><see cref="ReadSkillResourceToolName"/> (<c>read_skill_resource</c>)</description></item>
/// <item><description><see cref="RunSkillScriptToolName"/> (<c>run_skill_script</c>)</description></item>
/// </list>
/// </para>
/// <para>
/// <b>Security note:</b> because matching is by tool name only, any other registered tool that
/// shares one of these names — for example a configurable-name tool such as the Harness shell
/// tool (<c>HarnessAgentOptions.ShellToolName</c>) that was assigned the same name — will also be auto-approved, bypassing the
/// human approval boundary. Ensure no other tool collides with these reserved names.
/// </para>
/// </remarks>
public static Func<FunctionCallContent, ValueTask<bool>> AllToolsAutoApprovalRule { get; } =
functionCall => new ValueTask<bool>(s_allToolNames.Contains(functionCall.Name));
Expand Down
Loading