Skip to content

Harness HostedWebSearchTool is declared but not interceptable — align with FileAccess/FileMemory, or expose a request-authorize + result-inspection hook #7318

Description

@joslat

Follow-up to #7266, with the interceptability question now settled empirically for all three Harness native capabilities. Summary up front: FileAccess and FileMemory are already fully interceptable by function-call middleware (thanks @westey-m for confirming), WebSearch is not, and the two behave oppositely with respect to the tool collection in a way worth fixing.

What I verified

Using a real IChatClient.AsHarnessAgent(...) over a scripted fake client (net10.0, Microsoft.Agents.AI 1.13.0 / Microsoft.Agents.AI.Harness 1.13.0-preview.260703.1), registering a function-invocation middleware via AIAgentBuilder.Use((agent, context, next, ct) => …):

Capability Runtime shape In static ChatOptions.Tools? Seen by function middleware by name?
FileAccess (file_access_*) ApprovalRequiredAIFunction (via FileAccessProvider, an AIContextProvider) No (injected into per-invocation AIContext.Tools) Yes — observed file_access_read, and not calling next() suppressed the real AgentFileStore.ReadAsync
FileMemory (file_memory_*) same No Yes (same mechanism)
WebSearch (web_search) HostedWebSearchTool (added directly to ChatOptions.Tools at build) Yes No — never surfaces as a local FunctionCallContent

So the file tools are invisible in the static tool list but interceptable; WebSearch is visible in the list (as a marker, .Name == "web_search") but not interceptable. Visibility and interceptability are inverted between them.

Why WebSearch can't be intercepted

HostedWebSearchTool is, by its own doc, "a marker … [that] does not itself implement web searches". The provider executes the search server-side within one model round-trip, so there is no local function invocation for any middleware to observe or authorize. A DelegatingChatClient can see the marker on the way out and the CitationAnnotation/UriContent on the way back, but there is no pre-execution hook on the query itself.

Why this matters (risk)

  1. Exfiltration. The model composes the search query; it leaves to an external search engine server-side with no local pre-execution authorization point. Data encoded into a query exfiltrates without any middleware getting a say — unlike a file_access_* or caller-registered tool call, which can be inspected and blocked pre-execution.
  2. Indirect prompt injection. Provider web search typically retrieves and summarizes page content, pulling attacker-controllable text into the model context. The retrieved content is not surfaced through an interceptable seam either.

For a guardrail/policy layer that inspects and authorizes tool calls pre-execution, WebSearch is the one native capability that is structurally invisible to it — and it happens to be the highest-egress-risk of the three.

Ask

Any one of these would close the gap, roughly in order of preference:

  1. Route the web-search invocation through the same interceptable path as FileAccess/FileMemory — i.e. surface the search as an intercept point (a local AIFunction-like seam or an AIContextProvider-mediated call) so function-call middleware can observe/authorize the query and see the results, exactly as it can for file_access_* today.
  2. Failing that, expose a pluggable web-search backend analogous to HarnessAgentOptions.FileAccessStore / FileMemoryStore (e.g. a WebSearchStore / IWebSearchProvider), so a caller can supply an implementation they control and mediate. This is the pattern that already makes the file capabilities inspectable at the store boundary.
  3. At minimum, a request-authorize + result-inspection callback on HarnessAgentOptions for hosted tools (invoked with the proposed query before dispatch and the results after), plus documentation that HostedWebSearchTool is provider-executed and cannot be intercepted at the function-call seam.

Note that (1)/(2) would also generalize to other Hosted*Tool capabilities (hosted MCP, code interpreter, hosted file search), which share the same "declared but not locally interceptable" property.

Happy to share the minimal repro probe if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageUsage: [Issues], Target: All issues that still need to be triaged

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions