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_searchserver tool, OpenAI'sweb_searchin 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_domainsorblocked_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:
filterswith up to 100allowed_domainsor up to 100blocked_domains, scheme omitted, subdomains included.user_locationwith 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 olderweb_search_previewvariant 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.
| Pattern | Who runs the search | Where you can redact | Where you see the query |
|---|---|---|---|
Anthropic web_search server tool | Anthropic | Only in the prompt and context before the request | server_tool_use block in the response |
OpenAI web_search in the Responses API | OpenAI | Only in the prompt and context before the request | web_search_call.action.queries in the response (usually present) |
| Client side function tool | Your code | In your tool handler, on the exact query string | Your handler, before it is sent |
| Third party MCP search server | The MCP server operator | Before the tool call, if your client supports interception | Your client, and the MCP server's own logs |
| Self-hosted MCP search server | You | In the server, on the exact query string | Your 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 type | Example in context | Why it lands in a query | Detectability |
|---|---|---|---|
| Full names | "Priya Raman reports the export fails" | The model searches for the person to "understand context," or includes the name in an error search | Medium (NER catches many, misses unusual names) |
| Email addresses | "from j.doe@client.example" | Pasted error messages and headers include them | High (regex) |
| Phone numbers | "+44 20 7946 0000" | Searched to identify a caller or business | Medium (many formats) |
| Customer or ticket IDs | "ACME-77812", "order 99120344" | The model treats IDs as distinctive search terms | Low unless you know your own ID formats |
| Internal hostnames and URLs | "db-prod-eu-3.corp.internal" | Error strings are searched verbatim | High if you list your internal suffixes |
| Private IP addresses | "10.20.4.17 refused connection" | Same: verbatim error search | High (regex) |
| Code identifiers | "PayrollExportV2Service", a private package name | The model searches for the class or package to find docs | Low (looks like any identifier) |
| Free text describing a person | "the CFO of our largest Ohio client" | Paraphrase, not an identifier | Very low |
| Health, financial or legal details | "patient with condition X at clinic Y" | Searched to find guidance | Low 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:
- 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.
- 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
| Gap | Example | Mitigation |
|---|---|---|
| Quasi-identifiers | Rare job title plus town plus date | Keep 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 names | Names the NER model was not trained on | Exact match dictionary; multilingual models |
| Names that are common words | A surname that is also a noun | Dictionary layer |
| Encoding tricks | Base64, spaced out letters, homoglyphs | Normalise Unicode (NFKC), decode obvious encodings, block on high entropy strings |
| Prompt injection | A web page tells the agent to search for the user's email | Treat 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 URLs | Query strings with emails in a fetch URL | Apply 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.
| Property | Agent calls a search API directly | Agent calls your SearXNG instance |
|---|---|---|
| Query text reaches upstream engines | Yes | Yes, unchanged |
| Your users' IP addresses reach upstream | Depends on the client | No, upstream sees the instance's egress IP |
| User cookies and browser fingerprints reach upstream | Possibly | No |
| Queries from many users mixed under one identity | No | Yes |
| A single point to redact, log and rate limit | No | Yes |
| You control which upstream engines are used | No | Yes, per engine in settings.yml |
| Results quality | Single engine | Merged; 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_20260209and 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
storeparameter on/v1/responsesand/v1/chat/completionsis always treated asfalse. - The Responses API keeps application state for 30 days by default, or when
storeistrue. - 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
| Question | Anthropic (its docs) | OpenAI (its docs) |
|---|---|---|
| Is API data used for training by default? | No, not without express permission | No, since 1 March 2023, unless you opt in |
| Default retention of prompts and outputs | Anthropic's standard commercial retention policy, set out in its privacy center; Covered Models require 30 days | Abuse monitoring logs up to 30 days; Responses application state 30 days by default |
| Zero retention option | ZDR, per organisation, by arrangement | ZDR or Modified Abuse Monitoring, prior approval, org or project level |
| Hosted web search under zero retention | Basic 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 search | allowed_domains or blocked_domains, org level list in Console | filters with up to 100 allowed or blocked domains |
| Live versus cached | No cache only switch documented for web search | external_web_access: false for cache only |
| MCP data | MCP connector not ZDR covered; standard retention | Data sent to an MCP server follows that server's retention |
| What retention cannot change | Websites may log fetched URLs and parameters | Data 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
EMAILhits 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:
- 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.
- 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.
- Separate access. The people who read dashboards do not need the debug sample.
- 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. - Audit hosted searches after the fact. For hosted tools you cannot block, read the
server_tool_useblocks (Anthropic) orweb_search_callitems (OpenAI) from each response, run the samecheck_queryover 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.
| Requirement | Source | What it means for agent search |
|---|---|---|
| Data minimisation | GDPR Art. 5(1)(c); EDPB Guidelines 4/2019 | Only data needed for the task in the context; nothing personal in queries by default |
| Protection by design | GDPR Art. 25(1) | Redaction and blocking built into the tool path, not left to the prompt |
| Protection by default | GDPR Art. 25(2) | Web tools off unless the agent's purpose needs them |
| Minimise use, collection and retention | NIST SP 800-122 | Short lived, hashed query logs; no raw query archives |
| De-identification | NIST SP 800-122 | Pattern, NER and dictionary redaction before egress |
| Linkability | NIST SP 800-122 | Treat quasi-identifiers in queries as personal data |
| Restrict data sources and access | OWASP LLM02:2025 | Domain 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:
- Purpose per agent. Data minimisation is measured against a purpose. "Answer product questions" justifies documentation search; it does not justify searching for people.
- Tool inventory with defaults. Web search, web fetch and MCP access off unless the purpose needs them. This is Article 25(2) as configuration.
- 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.
- What happens on a hit. Block, rewrite or ask, per agent. Customer facing agents should block; developer tools can rewrite with approval.
- 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.
- Logging and retention. What is recorded, for how long, who can read it.
- Ownership and review. Someone owns the rule set and reads the metrics.
| Agent type | Web search | Web fetch | On redaction hit | Approval |
|---|---|---|---|---|
| Customer facing support bot | Client side tool, docs domains only | Docs domains only, no query strings | Block | None needed if blocks are strict |
| Internal analyst agent with CRM access | Off, or separate agent without CRM | Off | Block | Every external call |
| Coding agent | Client side or hooked, redact internal hosts and package names | Allow list of docs and registries | Rewrite | On rewrite |
| Research agent with no personal data | Hosted search acceptable | Hosted fetch with max_uses | Audit after the fact | None |
| Agent processing health or financial records | Off | Off | Not applicable | Not 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 anmcp_serversarray (URL must start withhttps://) and an MCP toolset withdefault_configand per toolconfigs. Settingenabled: falseas 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_toolsto import only named tools andrequire_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 aPreToolUsematcher, so the same redaction hook can cover an MCP search tool.
| Question | Hosted search tool | Remote MCP search server | Self-hosted MCP search server |
|---|---|---|---|
| Who sees the query | Model provider, search backend | Model provider (if hosted client), MCP operator, its backend | Your server, your chosen backend |
| Where to redact | Context only | Client before the call (hook, approval, proxy) | In the server and the client |
| Retention you control | Provider settings | Provider settings plus the operator's terms | Yours |
| Tool list drift | Not applicable | Possible; pin allowed tools | You control it |
| Prompt injection via results | Yes | Yes, plus tool descriptions | Yes, 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
- 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.
- 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.
- Redact the context. Add
pattern_redactanddictionary_redacton every input path: user message, retrieved documents, tool results. Keep the mapping server side if you need to restore values in the final answer. - Move search client side. Replace the hosted search tool with
search_weband the handler above, or keep hosted search only for agents whose context never holds personal data. - 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. - Hook coding agents. Install the
PreToolUsehook forWebSearchandWebFetchin Claude Code, and add deny rules for network commands in Bash plus sandbox network settings, per Claude Code's permissions docs. - 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.
- Log hashes. Ship the HMAC records with a fixed retention, build a dashboard of blocks and rewrites per rule.
- 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"
- Audit hosted searches. For any agent that still uses hosted search, run
check_queryover the queries reported in each response and alert on hits. - 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 situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull 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
- Anthropic: Web search tool
- Anthropic: Web fetch tool
- Anthropic: Server tools (ZDR, allowed_callers and domain filtering)
- Anthropic: API and data retention
- Anthropic: MCP connector
- Claude Code: Hooks reference
- Claude Code: Configure permissions
- OpenAI: Web search
- OpenAI: Data controls in the OpenAI platform
- OpenAI: MCP and connectors
- Model Context Protocol specification, version 2025-11-25
- Model Context Protocol: Tools (2025-11-25)
- Model Context Protocol: Security best practices
- EDPB Guidelines 4/2019 on Article 25 Data Protection by Design and by Default, version 2.0
- NIST SP 800-122: Guide to Protecting the Confidentiality of PII
- NIST Privacy Framework
- OWASP Top 10 for LLM Applications 2025: LLM02 Sensitive Information Disclosure
- SearXNG documentation
- SearXNG: Search API
- SearXNG: Container installation
- SearXNG: server settings
- SearXNG: search settings
- SearXNG: outgoing settings
- spaCy: Linguistic features and named entity recognition
Last reviewed: 23 October 2026. Provider tool versions, retention terms and eligibility tables change often; check the linked documentation before relying on any setting.