Safeguard
Supply Chain Security•

Keep PII Out of AI Agent Web Searches: Redaction, Proxies, Policy

Agents write search queries from their context, so names and emails leak. Redact before the call, search through your own proxy and log hashes, not text.

Safeguard Security Team
51 min read

To keep personal data out of an AI agent's web searches, stop the data before the model writes the query, not after. Redact names, emails and identifiers from the agent's context, replace hosted search with a search tool you run so you can inspect every query, route it through your own metasearch proxy, and log hashes rather than raw text.

Hosted search tools run on the provider's side, so the only place you can redact there is the prompt itself. That single fact shapes the whole design. This guide goes one level deeper than our overview of how agent web search leaks data to search engines: it traces the exact data path for each provider as their own documentation describes it, builds a two layer redaction step (patterns plus named entity recognition) with working Python, shows a Claude Code hook that rewrites search queries before they leave, stands up a self-hosted metasearch instance, sets out what Anthropic and OpenAI document about retention for tool calls, designs a query log that does not become its own PII store, and turns GDPR and NIST guidance into a tool policy you can actually enforce. Every provider fact below comes from that provider's own documentation, linked under Sources.

Key takeaways

  • A search query is model output. The model writes it from whatever is in its context, so any personal data in the prompt, the ticket or a tool result can end up in a query string.
  • With hosted search tools (Anthropic's web_search server tool, OpenAI's web_search in the Responses API), the provider runs the search. You cannot intercept the query in flight; you can only clean the context going in and audit the queries coming back in the response.
  • If you need a real inspection point, give the agent a client side search tool instead. Your code receives the query, redacts or blocks it, and sends it to a search backend you choose.
  • Patterns catch structured identifiers, named entity recognition catches many names and places, and an exact match dictionary catches your known customers. None of them catches paraphrase or quasi-identifiers.
  • A self-hosted metasearch proxy such as SearXNG hides your users' IP addresses and cookies from upstream engines. It does not hide the query, which its own docs say goes on to the external search services.
  • Provider retention settings matter but are not redaction: they cover the provider's copy, never the search engine's or the website's logs.
  • Log query hashes, rule hits and decisions, not the query text, and keep the log short lived. Otherwise the audit trail becomes the largest PII store in the system.
  • Write the policy down as configuration: which tools exist, which domains they may reach, which calls need a human, and what happens when the redactor fires.

Where does a search query go when an agent uses a web search tool?

The answer depends on who executes the search. There are three patterns, and they behave very differently.

Hosted search: Anthropic's web search server tool

Anthropic's documentation describes the flow in three steps: Claude decides when to search based on the prompt, "the API runs the searches and provides Claude with the results," and this can repeat several times in one request before Claude answers with citations. The search happens inside Anthropic's platform. Your application never sees the query before it runs; it sees it afterwards, in a server_tool_use block in the response whose input holds the query string.

The tool has had three versions. web_search_20250305 is basic search. web_search_20260209 adds dynamic filtering, where Claude writes and runs code that filters results before they reach the context window. web_search_20260318 adds a response_inclusion control. The controls you get on the request are:

  • max_uses, a hard cap on searches per request.
  • allowed_domains or blocked_domains (never both in one request; the API returns a 400 error). Entries are bare domains without a scheme, subdomains are included automatically, and wildcards are not allowed in the domain part.
  • user_location, an approximate location (city, region, country, timezone) used to localise results.

Organisation administrators can switch web search off entirely in the Claude Console or restrict the domains it can search. Anthropic's server tools page says request level allowed_domains must be a subset of the organisation level allowed list. The same page warns that Unicode lookalike domains can bypass filters and recommends ASCII only entries.

Note what is missing: there is no documented hook to rewrite the query before Anthropic runs it. That is not a criticism; it is what a server side tool is. It means your redaction must happen before the request is sent.

Hosted search: OpenAI's web search tool

OpenAI's guide describes web search in the Responses API, enabled with {"type": "web_search"} in the tools array. The model decides from the input prompt whether to search at all. The response contains a web_search_call output item whose action is search (which "will usually (but not always)" include the queries that were searched), open_page or find_in_page. The last two are documented for reasoning models.

Controls documented for the web_search tool:

  • filters with up to 100 allowed_domains or up to 100 blocked_domains, scheme omitted, subdomains included.
  • user_location with approximate country, city, region and timezone.
  • external_web_access: false, which runs the tool in cache only mode with no live fetching. The default is live access. The older web_search_preview variant ignores this setting.
  • search_context_size (low, medium, high), which controls how much result content reaches the model.

As with Anthropic, the query is generated and executed on the provider's side. You see it afterwards in the web_search_call item, which is useful for auditing but too late for redaction.

Client side search: a tool you run

In the third pattern you define an ordinary function tool, for example search_web(query). The model emits a tool call, your code receives the arguments, your code performs the search and returns results. This is the only pattern where you hold the query before it leaves your network. Everything in the redaction section below assumes this pattern, or a coding agent that lets you intercept tool input (Claude Code's hooks do, covered later).

MCP search servers

A search tool exposed through the Model Context Protocol adds one more party: the MCP server operator. If you call a third party MCP server, the query goes to that server, which then calls whatever search backend it uses. If you run the MCP server yourself, it behaves like a client side tool with a protocol in front of it. MCP gets its own section below.

PatternWho runs the searchWhere you can redactWhere you see the query
Anthropic web_search server toolAnthropicOnly in the prompt and context before the requestserver_tool_use block in the response
OpenAI web_search in the Responses APIOpenAIOnly in the prompt and context before the requestweb_search_call.action.queries in the response (usually present)
Client side function toolYour codeIn your tool handler, on the exact query stringYour handler, before it is sent
Third party MCP search serverThe MCP server operatorBefore the tool call, if your client supports interceptionYour client, and the MCP server's own logs
Self-hosted MCP search serverYouIn the server, on the exact query stringYour server

Which data in an agent's context tends to leak into queries?

Models write queries the way a person would: they take the specific nouns from the task and search for them. When the task text is a support ticket, an incident log or a CRM record, the specific nouns are often personal data. The table below lists the categories to plan for in support, IT operations and coding agents, with an example of each and how easy it is to detect automatically.

Data typeExample in contextWhy it lands in a queryDetectability
Full names"Priya Raman reports the export fails"The model searches for the person to "understand context," or includes the name in an error searchMedium (NER catches many, misses unusual names)
Email addresses"from j.doe@client.example"Pasted error messages and headers include themHigh (regex)
Phone numbers"+44 20 7946 0000"Searched to identify a caller or businessMedium (many formats)
Customer or ticket IDs"ACME-77812", "order 99120344"The model treats IDs as distinctive search termsLow unless you know your own ID formats
Internal hostnames and URLs"db-prod-eu-3.corp.internal"Error strings are searched verbatimHigh if you list your internal suffixes
Private IP addresses"10.20.4.17 refused connection"Same: verbatim error searchHigh (regex)
Code identifiers"PayrollExportV2Service", a private package nameThe model searches for the class or package to find docsLow (looks like any identifier)
Free text describing a person"the CFO of our largest Ohio client"Paraphrase, not an identifierVery low
Health, financial or legal details"patient with condition X at clinic Y"Searched to find guidanceLow to medium

The last two rows are why redaction is never complete on its own. NIST SP 800-122 draws the distinction that matters here. Information is linkable when it could be associated with other information about a person, and the publication names an "internet search engine" as an example of a readily obtainable secondary source that makes data linkable. A search query is, by construction, sent to exactly that kind of source. A query that contains a rare job title, a small town and a date can identify one person even though it contains no name.

Code identifiers deserve a separate note. A private package name or internal class name is rarely personal data, but it leaks your architecture and can tell an outsider which internal packages you use. Treat it with the same pipeline, with its own rule set.

How do you put a redaction step between the agent and the search tool?

Why hosted tools leave you one place to redact

With a hosted search tool, the provider writes and runs the query inside a single API call. So there are two honest options:

  1. Keep hosted search and redact the context before every request. This protects the query indirectly: if the name is not in the context, the model cannot search for it. It also protects everything else the model sees.
  2. Replace hosted search with a client side tool. You redact the query itself, can block or ask for approval, and choose the search backend.

Most teams with real PII in the loop end up doing both: context redaction for the whole request, plus a client side search tool so each query passes a final check.

Step 1: Define a client side search tool

For the Claude Messages API, a client tool is a name, a description and a JSON schema. The model calls it; your code runs it.

import anthropic

client = anthropic.Anthropic()

SEARCH_TOOL = {
    "name": "search_web",
    "description": (
        "Search the public web. Use short, generic queries about products, "
        "error messages or documentation. Never include names, email addresses, "
        "customer identifiers, ticket numbers or internal hostnames."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {"type": "string", "maxLength": 200}
        },
        "required": ["query"],
    },
}

For the OpenAI Responses API, the equivalent is a function tool with the same schema under parameters. The description is a hint to the model, not a control; OWASP's entry on sensitive information disclosure (LLM02:2025) notes that restrictions in prompts "may not always be honored and could be bypassed via prompt injection." The real control is the handler.

Step 2: The pattern layer

Regular expressions are fast, predictable and easy to test. They are the right tool for anything with a fixed shape.

import re

PATTERNS = {
    # Your own formats first, so generic rules do not split them.
    "TICKET": re.compile(r"\b(?:INC|REQ|ACME)-\d{4,8}\b"),
    "ORDER": re.compile(r"\border\s*#?\s*\d{6,}\b", re.I),
    "INTERNAL_HOST": re.compile(
        r"\b[a-z0-9-]+(?:\.[a-z0-9-]+)*\.(?:corp\.internal|svc\.cluster\.local|lan)\b", re.I
    ),
    "EMAIL": re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b"),
    "IPV4": re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"),
    "CARD": re.compile(r"\b(?:\d[ -]?){13,19}\b"),
    "US_SSN": re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
    "PHONE": re.compile(r"(?<!\w)(?:\+?\d{1,3}[\s.-]?)?(?:\(?\d{2,4}\)?[\s.-]?)\d{3,4}[\s.-]?\d{3,4}(?!\w)"),
}

def luhn_ok(digits: str) -> bool:
    nums = [int(d) for d in digits if d.isdigit()]
    if len(nums) < 13:
        return False
    total, parity = 0, len(nums) % 2
    for i, n in enumerate(nums):
        if i % 2 == parity:
            n *= 2
            if n > 9:
                n -= 9
        total += n
    return total % 10 == 0

def pattern_redact(text: str):
    hits = []
    for label, rx in PATTERNS.items():
        def _sub(m, label=label):
            if label == "CARD" and not luhn_ok(m.group(0)):
                return m.group(0)
            hits.append(label)
            return f"[{label}]"
        text = rx.sub(_sub, text)
    return text, hits

Three details make this layer useful rather than noisy. The Luhn check stops every long number from being treated as a card. The TICKET, ORDER and INTERNAL_HOST rules encode your own identifier formats, which no generic library knows. And the function returns the list of rule hits, which the logging section uses instead of the raw text.

Step 3: The named entity layer

Names, organisations and places have no fixed shape. A statistical named entity recogniser handles them. spaCy's documentation describes its entity recogniser as statistical and says plainly that because models "strongly depend on the examples they were trained on," results are not perfect every time. Plan for misses.

python -m pip install spacy
python -m spacy download en_core_web_sm
import spacy

nlp = spacy.load("en_core_web_sm")
ENTITY_LABELS = {"PERSON", "GPE", "LOC", "ORG", "FAC"}

def ner_redact(text: str, allow_terms: set[str]):
    doc = nlp(text)
    out, last, hits = [], 0, []
    for ent in doc.ents:
        if ent.label_ not in ENTITY_LABELS:
            continue
        if ent.text.lower() in allow_terms:
            continue  # product and project names you want searched
        out.append(text[last:ent.start_char])
        out.append(f"[{ent.label_}]")
        last = ent.end_char
        hits.append(ent.label_)
    out.append(text[last:])
    return "".join(out), hits

The allow_terms set is essential. Search queries are full of organisation names that are fine to send: the name of a database, a cloud service, a programming language, an open source project. Without an allow list, the redactor strips the very words that make the search useful and the agent learns to work around it.

Step 4: An exact match dictionary for known people and customers

Pattern and NER layers find things that look like personal data. They do not know which strings are your customers. If you have a customer list, a staff directory or a patient roster, build a dictionary of normalised names and identifiers and match it exactly. Store it as keyed hashes rather than plain text so the redactor itself does not become a copy of your CRM.

import hmac, hashlib, os, re

KEY = os.environ["REDACTOR_HMAC_KEY"].encode()

def h(term: str) -> str:
    return hmac.new(KEY, term.lower().encode(), hashlib.sha256).hexdigest()

# Built offline from your directory: set of h(name) for every customer and staff name.
KNOWN = set(open("/etc/redactor/known_terms.txt").read().split())

def dictionary_redact(text: str, max_ngram: int = 3):
    words = list(re.finditer(r"[\w'.-]+", text))
    spans, hits, i = [], [], 0
    while i < len(words):
        for n in range(min(max_ngram, len(words) - i), 0, -1):
            phrase = " ".join(w.group(0) for w in words[i:i + n])
            if h(phrase) in KNOWN:
                spans.append((words[i].start(), words[i + n - 1].end()))
                hits.append("KNOWN_PERSON")
                i += n
                break
        else:
            i += 1
    for start, end in reversed(spans):
        text = text[:start] + "[KNOWN_PERSON]" + text[end:]
    return text, hits

This is deliberately simple: it walks three, two and one word windows, joins each window with single spaces (build the dictionary with the same normalisation), and compares keyed hashes. It catches the customer whose surname is also a common noun, which NER often misses, and it never stores the plain names on the redaction host.

Step 5: Decide whether to rewrite, block or ask

Redaction changes the query. Sometimes the changed query is still useful ("export fails with error E1042 in [ORG] payroll connector"). Sometimes nothing useful is left ("[PERSON] [GPE]"). Make the decision explicit.

BLOCK_LABELS = {"US_SSN", "CARD", "KNOWN_PERSON"}

def check_query(query: str, allow_terms: set[str]):
    q1, h1 = pattern_redact(query)
    q2, h2 = dictionary_redact(q1)
    q3, h3 = ner_redact(q2, allow_terms)
    hits = h1 + h2 + h3
    residue = re.sub(r"\[[A-Z_]+\]", "", q3).strip()
    if BLOCK_LABELS.intersection(hits):
        return "block", q3, hits
    if len(residue.split()) < 2:
        return "block", q3, hits   # nothing worth searching for
    if hits:
        return "rewrite", q3, hits
    return "allow", query, hits

A block returns a tool result telling the model why, so it can rephrase: "Search blocked: the query contained a customer identifier. Search for the error or product behaviour instead." That message is part of the control. Models adapt well to a clear refusal and badly to a silent failure.

For higher risk agents, add a third outcome, ask, which pauses the agent and sends the redacted query to a human for approval. Our post on approval tiers for AI agent actions by reversibility covers how to choose which actions get which tier. A web search is not reversible: once sent, the query sits in someone else's logs.

Step 6: Redact the context too

The search check is the last line. The first line is the context you send. Run the same pattern_redact and dictionary_redact over the ticket text, the retrieved documents and the tool results before they go into the request, and keep a mapping on your side if you need to put real values back into the final answer for the user. Our pre-launch checklist for LLM PII and data leakage has the full list of places personal data enters a prompt.

What redaction misses

GapExampleMitigation
Quasi-identifiersRare job title plus town plus dateKeep personal context out of web enabled agents; split tasks
Paraphrase"our biggest client's finance head"Policy and task design, not regex
Unusual or non-Latin namesNames the NER model was not trained onExact match dictionary; multilingual models
Names that are common wordsA surname that is also a nounDictionary layer
Encoding tricksBase64, spaced out letters, homoglyphsNormalise Unicode (NFKC), decode obvious encodings, block on high entropy strings
Prompt injectionA web page tells the agent to search for the user's emailTreat tool results as untrusted; block on hits, do not just rewrite
Over-redaction"Java", "Jordan" (a product, a person, a country)Allow list; measure false positives
Data in fetched URLsQuery strings with emails in a fetch URLApply the same check to every outbound URL, not only searches

Claude Code: a PreToolUse hook that redacts WebSearch queries

Coding agents are a common source of leaks because they search for error messages verbatim. Claude Code exposes exactly the interception point you need. Its hooks documentation says a PreToolUse hook runs "after Claude creates tool parameters and before processing the tool call," that it can match the built-in WebSearch and WebFetch tools, and that it can return updatedInput to replace the tool's input, or a permissionDecision of allow, deny, ask or defer. The WebSearch tool's input has query, allowed_domains and blocked_domains fields.

Save this as .claude/hooks/redact_search.py in your project:

#!/usr/bin/env python3
import json, sys
sys.path.insert(0, "/opt/redactor")   # the module from the steps above
from redactor import check_query, ALLOW_TERMS

event = json.load(sys.stdin)
tool_input = event.get("tool_input", {})
query = tool_input.get("query", "")

decision, new_query, hits = check_query(query, ALLOW_TERMS)

if decision == "block":
    out = {"hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "deny",
        "permissionDecisionReason": (
            "Search blocked by the PII policy (" + ", ".join(sorted(set(hits))) +
            "). Search for the error text or product behaviour without identifiers."
        ),
    }}
elif decision == "rewrite":
    updated = dict(tool_input)
    updated["query"] = new_query
    out = {"hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "ask",
        "permissionDecisionReason": "Query was redacted: " + new_query,
        "updatedInput": updated,
    }}
else:
    out = {}

print(json.dumps(out))
sys.exit(0)

Register it in .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "WebSearch|WebFetch",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/redact_search.py" }
        ]
      }
    ]
  }
}

For WebFetch, extend the script to read the url field and run the same check over the full URL, including its query string. The updatedInput object replaces the whole input, so copy every field you are not changing, as the script does. Returning ask on a rewrite means the developer sees the redacted query before it runs, which is a good default while you tune the rules. Once false positives are low, switch rewrites to allow.

Pair the hook with permission rules. Claude Code's permissions page says rules are evaluated "deny, then ask, then allow," and that WebFetch(domain:example.com) style rules match on the requested hostname. It also warns that allowing WebFetch alone does not prevent network access if Bash is allowed, because curl can reach any URL; the docs recommend pairing deny rules for network tools with the sandbox network settings.

How do you run searches through a self-hosted metasearch proxy?

A metasearch engine takes your query, sends it to several upstream search engines and merges the results. Running your own instance gives you one controlled egress point for every agent search. SearXNG is a widely used open source option with a documented JSON API.

What it changes, and what it does not

Be precise about the benefit. The SearXNG search API documentation describes the q parameter this way: "This string is passed to external search services." Your query still reaches upstream engines. What changes is who appears to be asking.

PropertyAgent calls a search API directlyAgent calls your SearXNG instance
Query text reaches upstream enginesYesYes, unchanged
Your users' IP addresses reach upstreamDepends on the clientNo, upstream sees the instance's egress IP
User cookies and browser fingerprints reach upstreamPossiblyNo
Queries from many users mixed under one identityNoYes
A single point to redact, log and rate limitNoYes
You control which upstream engines are usedNoYes, per engine in settings.yml
Results qualitySingle engineMerged; some engines may rate limit or block the instance

So the proxy is a place to enforce redaction and logging, plus a privacy improvement for user identity. It is not a substitute for redaction.

Step by step: run SearXNG for agents

The SearXNG container docs recommend the compose setup:

mkdir -p ./searxng/core-config/
cd ./searxng/
curl -fsSL \
  -O https://raw.githubusercontent.com/searxng/searxng/master/container/docker-compose.yml \
  -O https://raw.githubusercontent.com/searxng/searxng/master/container/.env.example
cp -i .env.example .env
# edit .env, then:
docker compose up -d
docker compose ps

The compose template runs a core service and a valkey service; the docs' example output shows the core published as 0.0.0.0:8080->8080/tcp, which means every interface on the host. For an agent backend, publish it only where the agents run: edit the ports entry of the core service in docker-compose.yml to "127.0.0.1:8080:8080" if the agents share the host, or put the service on an internal network with no published port. The bind_address setting in settings.yml applies when SearXNG runs directly with Python, according to the server settings page, so in a container the port mapping is the control that counts.

Then edit core-config/settings.yml. The settings below are the ones that matter for an agent backend, each documented in the SearXNG settings reference:

use_default_settings: true

server:
  secret_key: "generate-a-long-random-value"   # the docs ship a placeholder and say to change it
  limiter: true               # rate limiting; requires the valkey database
  public_instance: false
  image_proxy: false
  method: "POST"              # keeps the query out of URL access logs on the instance side

search:
  autocomplete: ""            # blank turns autocomplete off; otherwise partial queries go to a backend
  safe_search: 1
  formats:
    - html
    - json                    # the JSON API returns 403 unless the format is enabled here

outgoing:
  request_timeout: 3.0
  # Optional: send upstream traffic through your egress proxy
  # proxies:
  #   all://:
  #     - http://egress-proxy.internal:3128

Two settings deserve a sentence each. method: "POST" matters because a GET search puts the query in the URL, and URLs end up in reverse proxy and web server access logs on your own side. The SearXNG API accepts both methods on / and /search. And the documented example settings name an autocomplete backend rather than leaving autocomplete blank; leaving it on sends partial queries to that backend as a person types, which is irrelevant for an agent but a leak for any human using the same instance.

Restart and test the JSON API exactly as the docs show, against your instance:

docker compose restart
curl -s -X POST 'http://127.0.0.1:8080/search' \
  -d 'q=python requests timeout retry&format=json' | python3 -m json.tool | head -40

Wire the proxy into the client side tool

Now the handler for search_web sends redacted queries to the instance and returns a trimmed result list:

import requests

SEARX = "http://127.0.0.1:8080/search"

def run_search_tool(query: str) -> str:
    decision, q, hits = check_query(query, ALLOW_TERMS)
    log_decision(query, q, decision, hits)          # see the logging section
    if decision == "block":
        return ("Search blocked by the PII policy. Rephrase using the error "
                "text or product behaviour, without names or identifiers.")
    r = requests.post(SEARX, data={"q": q, "format": "json"}, timeout=10)
    r.raise_for_status()
    results = r.json().get("results", [])[:8]
    return "\n".join(f"- {x.get('title')} | {x.get('url')} | {x.get('content','')[:300]}"
                     for x in results)

Finally, make the proxy unavoidable. Block direct outbound HTTPS from agent hosts at the firewall, allow only the SearXNG host (and your model provider's API endpoint), and route SearXNG's own upstream traffic through the outgoing.proxies setting if your network requires an egress proxy. An agent that can open arbitrary sockets can bypass every control in this guide.

Which provider settings control retention and training for tool calls?

Retention controls decide how long the provider keeps what the agent sent and received. They do not stop data reaching the search engine or the websites the agent visits. Both providers say so in their own way.

Anthropic

From Anthropic's API and data retention page and the tool pages it links:

  • Retained data "is never used for model training without your express permission."
  • Under a zero data retention (ZDR) arrangement, Anthropic does not store customer prompts or responses at rest once the API response has been returned. ZDR is arranged per organisation through Anthropic's sales team.
  • The basic web search (web_search_20250305) and web fetch (web_fetch_20250910) tools are ZDR eligible. The _20260209 and later versions with dynamic filtering are not ZDR eligible by default, because dynamic filtering runs on code execution. Setting "allowed_callers": ["direct"] on the tool disables dynamic filtering so it can be used under ZDR.
  • Web search is listed as HIPAA eligible; web fetch is not. Dynamic filtering for either tool is eligible for neither.
  • For web fetch, the feature table notes that website publishers may keep request data such as fetched URLs and request metadata under their own policies, and the server tools page warns that publishers "might retain any parameters passed to the URL."
  • The MCP connector is outside ZDR arrangements; data exchanged with MCP servers, including tool definitions and results, follows Anthropic's standard retention policy.
  • Even with ZDR or HIPAA arrangements, Anthropic may retain data where required by law or where flagged by its automated trust and safety systems, in which case inputs and outputs may be kept for up to 2 years.
  • Some models (the page lists Claude Fable 5.1, Claude Mythos 5.1, Claude Fable 5 and Claude Mythos 5 as "Covered Models") require 30 day retention and are not available under ZDR unless Anthropic expressly authorises it.

The web fetch tool also has guardrails that matter for exfiltration. Anthropic's page says Claude "can only fetch URLs that have previously appeared in the conversation," cannot fetch URLs that appear only in its own output, and cannot fetch a URL that appears to contain a credential unless that credential appears in the system prompt or a user message. It recommends max_uses and the url_sources parameter where exfiltration is a concern, and it advises against enabling web fetch where Claude handles untrusted input and sensitive data together.

OpenAI

From OpenAI's "Data controls in the OpenAI platform" page and its web search and MCP guides:

  • Since 1 March 2023, data sent to the OpenAI API has not been used to train or improve OpenAI models unless the customer explicitly opts in.
  • Abuse monitoring logs can contain customer content such as prompts and responses, and are kept for up to 30 days by default, unless longer retention is legally required or reasonably necessary.
  • Zero Data Retention and Modified Abuse Monitoring exclude customer content from those logs. Both require prior approval from OpenAI and can be set at organisation or project level. Under ZDR, the store parameter on /v1/responses and /v1/chat/completions is always treated as false.
  • The Responses API keeps application state for 30 days by default, or when store is true.
  • Web search with live internet access "is not HIPAA eligible and is not covered by a BAA." Web search in cache only mode (external_web_access: false) can be covered by a BAA when used with an API key from a ZDR enabled project in a ZDR organisation.
  • MCP servers used with the remote MCP tool are third party services, and data sent to them follows their own retention policies.

Side by side

QuestionAnthropic (its docs)OpenAI (its docs)
Is API data used for training by default?No, not without express permissionNo, since 1 March 2023, unless you opt in
Default retention of prompts and outputsAnthropic's standard commercial retention policy, set out in its privacy center; Covered Models require 30 daysAbuse monitoring logs up to 30 days; Responses application state 30 days by default
Zero retention optionZDR, per organisation, by arrangementZDR or Modified Abuse Monitoring, prior approval, org or project level
Hosted web search under zero retentionBasic version eligible; dynamic filtering not, unless allowed_callers: ["direct"]Endpoint level ZDR applies; cache only web search can be BAA covered under ZDR
Domain controls on hosted searchallowed_domains or blocked_domains, org level list in Consolefilters with up to 100 allowed or blocked domains
Live versus cachedNo cache only switch documented for web searchexternal_web_access: false for cache only
MCP dataMCP connector not ZDR covered; standard retentionData sent to an MCP server follows that server's retention
What retention cannot changeWebsites may log fetched URLs and parametersData sent to third party services follows their policies

The row that matters most is the last one. No provider setting reaches into the search engine's logs or a website's access logs. Only keeping the data out of the query does.

How do you log and review outbound agent queries without creating a new PII store?

You need a record of what your agents searched for, both to tune the redactor and to answer "did we send that customer's name anywhere?" The naive design logs the raw query. That log then contains every piece of personal data the redactor missed, in one searchable place, kept for as long as nobody remembers to delete it.

A better record keeps enough to answer the questions without keeping the text:

import hmac, hashlib, json, os, time, uuid

LOG_KEY = os.environ["QUERY_LOG_HMAC_KEY"].encode()   # rotate; store in your secrets manager

def log_decision(original: str, sent: str, decision: str, hits: list[str],
                 agent: str = "support-agent", tenant: str = "default"):
    record = {
        "id": str(uuid.uuid4()),
        "ts": int(time.time()),
        "agent": agent,
        "tenant": tenant,
        "decision": decision,                 # allow / rewrite / block / ask
        "rules": sorted(set(hits)),           # e.g. ["EMAIL", "PERSON"]
        "orig_hmac": hmac.new(LOG_KEY, original.encode(), hashlib.sha256).hexdigest(),
        "sent_hmac": hmac.new(LOG_KEY, sent.encode(), hashlib.sha256).hexdigest(),
        "orig_len": len(original),
        "sent_len": len(sent),
    }
    print(json.dumps(record), flush=True)     # ship to your log pipeline with a short TTL

What this lets you do:

  • Count blocks and rewrites per agent, per rule, per day. A jump in EMAIL hits after a prompt change tells you something changed.
  • Answer "did this exact string go out?" by computing its keyed hash and searching for sent_hmac. You can confirm or rule out a specific value without storing any values.
  • Tune false positives by sampling. Keep a separate, opt in debug mode that records redacted text (never the original) for a small sample, on a separate store with tighter access and a retention of days, not months.

Design rules that keep the log honest:

  1. Key the hashes. A plain SHA-256 of a short string such as an email can be reversed by guessing. HMAC with a secret key cannot be reversed by someone who has only the log.
  2. Set a retention period up front and enforce it in the log store, not in a runbook. Thirty days is usually enough for tuning; a longer period needs a reason you can write down.
  3. Separate access. The people who read dashboards do not need the debug sample.
  4. Remember the provider's copy. OpenAI's MCP guide notes that with store=true, data sent to MCP servers is already logged through the API for 30 days unless Zero Data Retention is on. Your logging plan should account for copies you do not hold.
  5. Audit hosted searches after the fact. For hosted tools you cannot block, read the server_tool_use blocks (Anthropic) or web_search_call items (OpenAI) from each response, run the same check_query over the logged queries, and alert when a hosted search contained something the redactor would have blocked. That tells you whether context redaction is working.

Our post on PII in URLs and page titles sent to analytics covers the same trap in web analytics: the tool meant to measure the system quietly becomes its biggest store of personal data.

What do GDPR data minimisation and NIST privacy guidance require here?

GDPR

Article 5(1)(c) sets the data minimisation principle: personal data must be adequate, relevant and limited to what is necessary for the purpose. Article 25 adds data protection by design and by default. The European Data Protection Board's Guidelines 4/2019 on Article 25 (version 2.0, adopted 20 October 2020) explain what that means in practice:

  • Under Article 25(1), the controller must put in place appropriate technical and organisational measures designed to implement the data protection principles and build safeguards into the processing. The guidelines list pseudonymisation among the example measures.
  • Under Article 25(2), by default only the personal data necessary for each specific purpose are processed.
  • On minimisation, the guidelines say the controller "has to predetermine which features and parameters of processing systems and their supporting functions are permissible."
  • Where the controller uses third party or off the shelf software, the guidelines expect it to assess that software as part of its own obligations.

Read against an agent with a web search tool, that gives three concrete obligations. First, sending a customer's name to a search engine is a disclosure to a third party that needs a purpose and a legal basis; "the model decided to" is neither. Second, the redaction step and the default off search tool are the kind of predetermined technical measures the guidelines describe. Third, the provider, the MCP server operator and the search backend are all parts of the processing chain you must account for, which is where the retention facts above feed into your records of processing and data processing agreements. This is general information, not legal advice; your data protection officer should confirm how it applies to your processing.

NIST

NIST SP 800-122, the guide to protecting the confidentiality of PII (published April 2010), tells organisations to "minimize the use, collection, and retention of PII" to what their business purpose strictly needs. It describes de-identification as removing enough PII that the remaining information does not identify an individual, and it uses search engines as its example of a source that makes data linkable. Those three ideas map straight onto this design: minimise what goes into the context, de-identify what goes into the query, and assume anything sent to a search engine can be linked.

The NIST Privacy Framework is a voluntary tool for managing privacy risk, organised around functions for identifying, governing, controlling, communicating about and protecting data processing. NIST's site currently shows version 1.0 alongside an initial public draft of version 1.1. For this topic, the useful habit it encourages is an inventory of data flows: list each agent, each tool it can call, what data can reach that tool, and who receives it. The table in the first section of this guide is the start of that inventory.

RequirementSourceWhat it means for agent search
Data minimisationGDPR Art. 5(1)(c); EDPB Guidelines 4/2019Only data needed for the task in the context; nothing personal in queries by default
Protection by designGDPR Art. 25(1)Redaction and blocking built into the tool path, not left to the prompt
Protection by defaultGDPR Art. 25(2)Web tools off unless the agent's purpose needs them
Minimise use, collection and retentionNIST SP 800-122Short lived, hashed query logs; no raw query archives
De-identificationNIST SP 800-122Pattern, NER and dictionary redaction before egress
LinkabilityNIST SP 800-122Treat quasi-identifiers in queries as personal data
Restrict data sources and accessOWASP LLM02:2025Domain allow lists, least privilege tool sets

What should an agent tool policy say?

A policy that lives only in a wiki does not stop a query. Write it as configuration your tool handler, proxy and coding agents read, and keep the human readable version in the same repository.

# agent-tool-policy.yaml
version: 3
defaults:
  web_search: deny
  web_fetch: deny
  mcp_servers: deny
  on_redaction_hit: block        # block | rewrite | ask

agents:
  support-agent:
    purpose: "Answer product questions using public docs"
    tools:
      search_web:
        backend: "http://127.0.0.1:8080/search"
        allowed_domains: ["docs.example.com", "status.example.com"]
        max_calls_per_task: 5
        on_redaction_hit: block
      web_fetch:
        allowed_domains: ["docs.example.com"]
        block_query_strings: true
    context:
      redact_before_model: [EMAIL, PHONE, TICKET, ORDER, KNOWN_PERSON, PERSON]

  coding-agent:
    purpose: "Fix build and dependency issues"
    tools:
      search_web:
        on_redaction_hit: rewrite
        require_approval: on_rewrite
        extra_rules: [INTERNAL_HOST, PRIVATE_PACKAGE]
      mcp:
        allowed_servers: ["https://mcp.internal.example/"]
        allowed_tools: ["search_docs", "get_advisory"]
        require_approval: always

logging:
  store: hmac_only
  retention_days: 30
  debug_sample: { enabled: false, rate: 0.01, store: redacted_text, retention_days: 7 }

review:
  owner: "privacy-engineering"
  cadence: monthly
  metrics: [blocks_per_rule, rewrites_per_agent, hosted_search_audit_hits]

The sections every policy needs:

  1. Purpose per agent. Data minimisation is measured against a purpose. "Answer product questions" justifies documentation search; it does not justify searching for people.
  2. Tool inventory with defaults. Web search, web fetch and MCP access off unless the purpose needs them. This is Article 25(2) as configuration.
  3. Domain allow lists. Use them wherever the agent's job has a known set of sources. Both providers' hosted tools support allow lists; Anthropic recommends ASCII only entries because of lookalike domains.
  4. What happens on a hit. Block, rewrite or ask, per agent. Customer facing agents should block; developer tools can rewrite with approval.
  5. Approval for external calls. Name which tool calls need a human. MCP's tools specification recommends a human in the loop who can deny tool invocations.
  6. Logging and retention. What is recorded, for how long, who can read it.
  7. Ownership and review. Someone owns the rule set and reads the metrics.
Agent typeWeb searchWeb fetchOn redaction hitApproval
Customer facing support botClient side tool, docs domains onlyDocs domains only, no query stringsBlockNone needed if blocks are strict
Internal analyst agent with CRM accessOff, or separate agent without CRMOffBlockEvery external call
Coding agentClient side or hooked, redact internal hosts and package namesAllow list of docs and registriesRewriteOn rewrite
Research agent with no personal dataHosted search acceptableHosted fetch with max_usesAudit after the factNone
Agent processing health or financial recordsOffOffNot applicableNot applicable; split the task

The last row is a design choice worth stating plainly: the strongest control is not giving one agent both the sensitive records and the open web. Split the task. One agent reads the records and produces a generic question; a second agent with no access to the records searches the web for that question.

How do MCP servers change the data path?

MCP standardises how a client (the agent host) connects to servers that expose tools, resources and prompts. For data leaving through search, it changes three things.

There is another recipient. A search tool on a third party MCP server sends your query to that operator first. OpenAI's MCP guide describes all MCP servers as third party services under their own terms, and says remote servers "have not been verified by OpenAI." Anthropic's MCP connector page says data exchanged with MCP servers follows Anthropic's standard retention and is not covered by ZDR. So with a hosted model calling a remote MCP server, the query can sit with the model provider and the MCP operator, plus whatever backend the operator uses.

The spec puts consent on the host. The MCP specification (version 2025-11-25) sets out principles that hosts "must obtain explicit user consent before exposing user data to servers," must not pass resource data elsewhere without consent, and must get explicit consent before invoking any tool. Its tools section tells clients to treat tool annotations as untrusted unless the server is trusted. In practice, that means your agent host is where the redaction step and the approval prompt belong.

Tool lists can change under you. Servers can add tools and send a list changed notification. A server that only offered search_docs yesterday might offer search_people today. Pin the tools you allow.

The provider documented controls:

  • Anthropic's MCP connector (beta, header mcp-client-2025-11-20) takes an mcp_servers array (URL must start with https://) and an MCP toolset with default_config and per tool configs. Setting enabled: false as the default and enabling named tools gives an allow list. You can also pin the tool list yourself so the API does not ask the server for its tools.
  • OpenAI's remote MCP tool accepts allowed_tools to import only named tools and require_approval. The guide says that by default OpenAI requests approval before data is shared with a connector or remote MCP server, and it recommends keeping approvals for sensitive actions and logging the data sent to MCP servers.
  • Claude Code permission rules match MCP tools by name, such as mcp__server__tool, and its hooks can match MCP tool names with a PreToolUse matcher, so the same redaction hook can cover an MCP search tool.
QuestionHosted search toolRemote MCP search serverSelf-hosted MCP search server
Who sees the queryModel provider, search backendModel provider (if hosted client), MCP operator, its backendYour server, your chosen backend
Where to redactContext onlyClient before the call (hook, approval, proxy)In the server and the client
Retention you controlProvider settingsProvider settings plus the operator's termsYours
Tool list driftNot applicablePossible; pin allowed toolsYou control it
Prompt injection via resultsYesYes, plus tool descriptionsYes, from web results

If you need MCP for search, the cleanest pattern is a small MCP server you run, which wraps the same run_search_tool function from the proxy section. Then the protocol is just a transport and every control stays in your code.

Step by step: rolling this out for one agent

  1. Inventory. For the agent, list every tool, what data can reach its context, and every external recipient (model provider, search backend, MCP operators, websites). Write it down in the format of the first table.
  2. Decide the purpose and the default. If the purpose does not need web access, turn it off. For Anthropic hosted search, an administrator can disable web search for the organisation in the Console; for a single agent, simply do not include the tool.
  3. Redact the context. Add pattern_redact and dictionary_redact on every input path: user message, retrieved documents, tool results. Keep the mapping server side if you need to restore values in the final answer.
  4. Move search client side. Replace the hosted search tool with search_web and the handler above, or keep hosted search only for agents whose context never holds personal data.
  5. Stand up SearXNG. Publish it only on localhost or an internal network, enable the JSON format, set method: "POST", turn autocomplete off, enable the limiter, and block direct egress from the agent host.
  6. Hook coding agents. Install the PreToolUse hook for WebSearch and WebFetch in Claude Code, and add deny rules for network commands in Bash plus sandbox network settings, per Claude Code's permissions docs.
  7. Lock MCP down. Allow named servers and named tools only, require approval for anything that sends data out, and prefer a self-hosted search server.
  8. Log hashes. Ship the HMAC records with a fixed retention, build a dashboard of blocks and rewrites per rule.
  9. Test with a corpus. Keep a file of synthetic test cases (fake names, fake emails, your ID formats, lookalike Unicode, base64) and run it in CI:
python -m pytest tests/test_redactor.py -q
# tests/test_redactor.py
from redactor import check_query, ALLOW_TERMS

def test_email_blocked_or_rewritten():
    d, q, hits = check_query("error from jane.roe@example.org export", ALLOW_TERMS)
    assert "EMAIL" in hits and "jane.roe" not in q

def test_ticket_id_removed():
    d, q, hits = check_query("ACME-778812 timeout on export", ALLOW_TERMS)
    assert "ACME-778812" not in q

def test_product_names_survive():
    # assumes "postgresql" is in ALLOW_TERMS
    d, q, hits = check_query("PostgreSQL connection reset by peer", ALLOW_TERMS)
    assert d == "allow"
  1. Audit hosted searches. For any agent that still uses hosted search, run check_query over the queries reported in each response and alert on hits.
  2. Review monthly. Read the metrics, review false positives from the debug sample if enabled, update the dictionary from your directory, and record changes to the policy file in version control.

Common mistakes

  • Relying on the system prompt. An instruction never to search for customer names is a request, not a control. OWASP's LLM02 entry warns that prompt restrictions can be ignored or bypassed through prompt injection.
  • Treating a metasearch proxy as anonymisation. It hides who is asking. The query text still goes upstream unchanged.
  • Assuming zero data retention covers the search engine. Provider retention settings cover the provider. Websites and search backends keep their own logs, and Anthropic's docs say so explicitly for fetched URLs.
  • Turning on newer hosted tool versions without checking eligibility. On Anthropic, the dynamic filtering versions of web search and web fetch are not ZDR eligible by default; set allowed_callers: ["direct"] if your organisation depends on ZDR.
  • Logging raw queries "for debugging." The debug log becomes the most complete PII store in the system. Hash with a key, sample redacted text only, and expire it.
  • No allow list for product terms. Over-aggressive NER strips the useful words, the agent's answers get worse, and someone turns the redactor off.
  • Checking only search, not fetch. A fetch URL with ?email= in the query string leaks the same data to the site's access logs.
  • Letting the agent's Bash or code tool reach the internet. Domain rules on WebFetch do nothing if the agent can run curl. Claude Code's own docs make this point.
  • Trusting MCP tool descriptions and annotations. The MCP spec says to treat annotations as untrusted unless the server is trusted, and tool lists can change.
  • Giving one agent both the sensitive records and the web. Split the task so the web enabled agent never sees the records.

FAQ

Do AI agents send my prompt to the search engine?

Not the whole prompt, but the model writes the search query from the prompt and its context, so specific words from your prompt often appear in the query. With hosted tools (Anthropic's and OpenAI's web search), the provider runs that query against its search backend. You can see the queries afterwards in the API response.

Can I redact the query when using Anthropic's or OpenAI's built-in web search?

Not in flight. The search runs on the provider's side within the API call. You can redact the context you send, restrict domains, cap the number of searches, and audit the queries in the response. To inspect each query before it is sent, use a client side search tool that your code executes.

Does zero data retention stop PII leaking through web search?

No. Zero data retention controls what the model provider stores. It does not control what the search engine or the websites the agent visits log. Anthropic's documentation notes that publishers might retain parameters passed in fetched URLs even in a ZDR eligible configuration.

Is SearXNG private enough to send customer data through?

No. SearXNG hides your users' IP addresses and cookies from upstream engines and gives you one place to log and enforce policy, but its own API docs say the query string goes on to the external search services. Redact first, then proxy.

How do I stop Claude Code from searching for internal hostnames?

Add a PreToolUse hook matching WebSearch and WebFetch that runs your redaction rules and returns deny, or updatedInput with the cleaned query. Combine it with permission rules for WebFetch domains and deny rules for network commands, as Claude Code's permissions documentation recommends.

Do GDPR rules apply to what an AI agent searches for?

If the query contains personal data, sending it to a search engine is processing and disclosure to a third party. Article 5's data minimisation principle and Article 25's data protection by design and by default apply. The EDPB guidelines on Article 25 describe the kind of technical measures, such as pseudonymisation, that controllers are expected to build in. Confirm specifics with your data protection officer.

Are MCP servers safe to use for web search?

They can be, with the same controls as any third party processor. A remote MCP server is another recipient of your queries with its own retention terms. Allow named servers and tools only, require approval for calls that send data out, and prefer a search server you run yourself.

Should I log every search query my agents make?

Log every decision, not every query. Record a keyed hash of the query, the rules that fired and the outcome, with a fixed retention period. That lets you prove whether a specific value was sent without storing the values.

Where Safeguard fits, honestly

Safeguard is a software supply chain security platform, and most of this guide is privacy engineering that you build in your own agent code. Two Safeguard capabilities are relevant, and both are early access today. The AI Gateway (early access) is a runtime guardrail proxy for model traffic that includes PII egress checks, with a monitor mode as the default and an enforce mode you switch on. MCP Guardrail (early access) generates guardrail proxy URLs for MCP servers so agent tool traffic passes through a policy point. Neither replaces the context redaction, client side search tool and logging design above; they are places to enforce the same policy for teams that want it managed. On the generally available side, Safeguard's policies and gates, and the Safeguard CLI's permission modes, which ask before each command an agent wants to run, support the approval and policy-as-configuration habits this guide recommends. The libraries in your redaction pipeline (an NER library, an HTTP client, a metasearch engine) are open source dependencies too, which is where the next section applies.

Redaction proxies and agent hosts are ordinary software with open source dependencies, and a vulnerable HTTP client or parser in the egress path is a privacy problem as much as a security one. Keeping those components patched is the same supply chain work as any other service, even though it is a step removed from the PII question itself.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

The first demo shows how Claude connects to the Safeguard MCP server with OAuth and how you choose which tools Claude is allowed to use. That tool by tool choice is the same allow list discipline this guide recommends for any MCP server an agent can reach, including search servers.

Click through it at your own pace, or open it full screen.

The second demo is a real Safeguard CLI session: the assistant reads a lodash finding and its advisory, asks before each command, edits package.json, runs npm install and npm test only with approval, and pushes nothing. It then shows /permissions, /model and /scan .. The ask before acting pattern is the same one this guide applies to outbound searches, and /scan . checks the dependencies of a redaction service like the one built above.

Click through it at your own pace, or open it full screen.

The third demo is a real Android session that sends work to a computer running the CLI and approves the command on the phone with Allow once, Allow for turn or Deny. It shows what human approval for an agent's external action looks like when the reviewer is away from the desk.

Click through it at your own pace, or open it full screen.

To try it on your own repositories, download Safeguard and follow the CLI documentation.

Quick check

Why can you not redact the query itself when an agent uses Anthropic's or OpenAI's hosted web search tool?

Because the provider writes and runs the search inside the API call. Your code only sees the query afterwards, in the response. To redact the query itself you need a client side search tool, or a coding agent hook such as Claude Code's PreToolUse, that receives the query before it is sent.

What does a self-hosted metasearch proxy hide from upstream search engines, and what does it not hide?

It hides your users' IP addresses, cookies and individual identities, because upstream engines see only the proxy. It does not hide the query text, which is passed to the upstream engines unchanged, so redaction still has to happen first.

Sources

Last reviewed: 23 October 2026. Provider tool versions, retention terms and eligibility tables change often; check the linked documentation before relying on any setting.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.