Claude Code Can Send Your Email to Websites. How to Check and Block It
Claude Code can send your email address to websites in the User-Agent header of curl calls it writes itself. If you're on the stable channel, you don't have Anthropic's stopgap at all: it needs 2.1.317, and stable was se
Claude Code can send your email address to websites in the User-Agent header of curl calls it writes itself. If you're on the stable channel, you don't have Anthropic's stopgap at all: it needs 2.1.317, and stable was serving 2.1.311 on November 6. If you're on latest, the block's own README says it ignores WebFetch, URLs, From: headers and scripts, and two users on 2.1.318 report it missing the one case it was built to catch.
Issue #107757 was still open on November 6, with more than 1,100 points on Hacker News. An older issue has been complaining about the email in the model's context since July 25. I'll start with how the leak happens, then check the logs for it and block it with a PreToolUse hook.
Who adds the email to the User-Agent header?
Mostly the model. The issue asks for the behavior to be "removed from the harness entirely", and some readers assumed Claude Code stamps every request. HN commenters pushed back, and then someone on the Claude Code team confirmed it: the account email went into the model's context because Claude kept hallucinating names for @author tags, and when Claude wrote its own curl call, it sometimes used that address as a contact.
Sites do ask for contact details. Wikimedia's policy wants "some way of contacting you, the operator" from scripted clients, and SEC EDGAR's sample header is Sample Company Name AdminContact@<sample company domain>.com. One HN commenter showed EDGAR returning 403 for default curl, then added that most non-curl agents get through, so your real address often isn't even needed.
Since 2.1.234, Claude has been told not to send the address to unrelated services unless you ask. It didn't hold: a commenter on #81138, open since July 25, posted Opus 5.5 in the web version admitting it did this anyway. So I'd skip the CLAUDE.md rule someone posted in #107757, which is one more instruction of the same kind.
Is the User-Agent leak causing your spam?
That's unproven. The reporter blames it for dozens of scam emails a day, and a few commenters describe similar spikes. Others use a unique address per service and report no spam at all, and one commenter argues that few sites harvest addresses from User-Agent headers, and Claude would have to have fetched one of them. Nobody in either thread has traced a spam message back to a User-Agent. The closest is a #81138 commenter who traced a spike to their address in commit messages, a different route that step 5 deals with. The leak itself does show up from the other side: a site operator on HN says they've seen it "for months, both as a Claude user, and in my website access logs".
I wouldn't lean on the spam argument anyway. Handing your address to a site without asking is the bug, whoever reads the access log, and any site can start asking.
The best number available is self-reported. whoami-guard's author (write-up) measured 8/60 runs leaking identity without their hook and 0/60 with it. That was against a mock site that asks for contact details, on one Windows machine with Claude Code 2.1.312 (before the built-in mod), and all 8 leaks came from Haiku, with Opus at 0/30. All 11 leaking requests came after a 403, so what it measures is how often the model gives in when a site asks.
What cc-plugin-privacy blocks and what it misses
A Claude Code contributor announced the mod as a way "to deterministically block this behavior". The README, which sits next to the code, calls it "a courtesy, not a boundary". The 2.1.317 changelog says it "asks before a web or shell tool call" sends your address, while the README says the refusal "raises no prompt" and that "WebFetch and WebSearch are not checked". I'd go with the README.
| Case | cc-plugin-privacy (README) | Hook below | whoami-guard (README) |
|---|---|---|---|
curl -A "bot (you@…)" |
Refused on paper | Blocked | Blocked |
Address in a URL, From: or curl -u
|
Passes | Blocked | Scanned |
| WebFetch | Passes | Blocked | Scanned |
| WebSearch | Passes | Blocked | Not in its sample matcher |
| MCP tools | Passes | Not covered | Scanned |
| Git email that differs from the account email | Passes | Blocked | Scanned if global or listed |
$(git config user.email) |
Passes | Literal form only | Not when a script reads it at run time |
| Script that hardcodes the address | Passes | Blocked at Write/Edit | Blocked |
| Script already on disk | Passes | Passes | Reads it for python fetch.py style runs |
I wrote "on paper" because of what people are reporting. A macOS user on 2.1.318 and a Windows 11 user on 2.1.318 both had the mod listed as loaded. On Windows, curl -s -m 3 -A "test <account email>" http://leak-test.invalid/ ran with no prompt and no denial. Neither test reached a real site. Two other 2.1.318 users say the mod doesn't show up in /plugins at all. The README does list ways it can be off, including a server-side switch and sessions where no hooks module loads, but nobody has confirmed the cause, and I saw no maintainer answer to those reports in the issue as of November 6.
A proper fix is on the way: Anthropic said on November 6 that the next release, due "early next week", removes the account email from the context. That should have been the first move, given #81138 had been asking for it since July. It still leaves git log and git config, and whoami-guard's author says their leak came from gitconfig.
How to check your logs and block it: five steps
1. Check your Claude Code version and channel
The mod needs 2.1.317 or later. Compare your version with what the stable channel serves:
claude --version
curl -s https://downloads.claude.ai/claude-code-releases/stable; echo
On November 6 that returned 2.1.311, and .../latest returned 2.1.318. Native installs default to latest. If you chose stable, set "autoUpdatesChannel": "latest" in ~/.claude/settings.json, then run claude update. Homebrew ignores that setting and claude update won't move it: the claude-code cask tracks stable, claude-code@latest tracks latest, and the two conflict. Run brew uninstall --cask claude-code, then brew install --cask claude-code@latest, and upgrade later with brew upgrade claude-code@latest. Check the changelog for the context removal too.
2. Confirm the privacy mod is on
In /plugins, under Installed, look for cc-plugin-privacy. Then ask Claude to run this no-network test from the issue, with your real address in place of the placeholder:
echo 'User-Agent: test <account email>' > /dev/null
The mod should refuse it. If it runs, you're relying on whatever you add next.
3. Grep your Claude Code logs for your email
ME=$(jq -r '.oauthAccount.emailAddress' ~/.claude.json)
grep -rhoiE ".{0,100}(user.?agent|curl .{0,60}-A|wget .{0,60}-U).{0,160}" \
~/.claude/projects --include='*.jsonl' | grep -iF "$ME"
If ME comes back null, you're not signed in with an account, so set it by hand. Repeat with your git config user.email. Hits include any place you merely discussed a header, so read them. Empty output isn't an all-clear: transcripts get deleted after cleanupPeriodDays, the window limits how far apart the two strings can be, and a script that reads the address when it runs won't match.
4. Add a PreToolUse hook that blocks your email
Save this as ~/.claude/hooks/block-my-email.sh, then chmod +x it. It needs jq, and Git Bash on Windows.
#!/usr/bin/env bash
set -euo pipefail
input=$(cat)
tool=$(jq -r '.tool_name' <<<"$input")
text=$(jq -r '.tool_input | tostring' <<<"$input" | tr '[:upper:]' '[:lower:]' | sed 's/%40/@/g')
net='user.?agent|curl|wget|invoke-(webrequest|restmethod)|requests\.|urllib|httpx|fetch\(|https?://'
lookup='git +config[^;|&]*user\.email|%ae|\.claude\.json'
deny() { echo "Blocked: $1. Use a generic client name, without my email." >&2; exit 2; }
if grep -Eq "$net" <<<"$text" && grep -Eq "$lookup" <<<"$text"; then
deny "address looked up for a network call"
fi
acct=$(jq -r '.oauthAccount.emailAddress // empty' ~/.claude.json 2>/dev/null || true)
git_mail=$(git config user.email || true)
global_mail=$(git config --global user.email || true)
old_mail="" # step 5: your previous git address
for addr in "$acct" "$git_mail" "$global_mail" "$old_mail"; do
[ -n "$addr" ] || continue
addr=$(tr '[:upper:]' '[:lower:]' <<<"$addr")
grep -Fq "$addr" <<<"$text" || continue
case "$tool" in
WebFetch|WebSearch) deny "address in a web tool call" ;;
*) grep -Eq "$net" <<<"$text" && deny "address near a network call" ;;
esac
done
exit 0
Register it in ~/.claude/settings.json. Hooks fail open by default, so a typo in the path would let everything through. onFailure closes that and requires v2.1.295 or later. It also means a missing jq blocks every matched tool call, so install that first.
{"hooks": {"PreToolUse": [{"matcher": "Bash|PowerShell|Monitor|WebFetch|WebSearch|Write|Edit",
"hooks": [{"type": "command", "command": "~/.claude/hooks/block-my-email.sh", "onFailure": "block", "timeout": 10}]}]}}
Test the script before you trust it. This feeds it a fake tool call and sends nothing, and it should print exit 2:
ME=$(jq -r '.oauthAccount.emailAddress' ~/.claude.json)
jq -n --arg c "curl -A \"bot ($ME)\" http://leak-test.invalid/" \
'{tool_name:"Bash",tool_input:{command:$c}}' \
| ~/.claude/hooks/block-my-email.sh; echo "exit $?"
It's string matching, so it will sometimes block a harmless call, like editing a README with your address next to a link. If you want encoded variants, name matching and a curl/wget shim for scripts, use whoami-guard (uv tool install whoami-guard), and read its known-gaps list first.
5. Switch to a no-reply git email
Get your address from GitHub's email settings: [email protected].
git config --global user.email "[email protected]"
Repos with their own user.email keep it, and earlier commits keep the old address, so git log can still expose it. Put the old address in old_mail in the hook, because git config now returns the no-reply one.
Should you keep the hook after the fix ships?
The email should never have been in the context by default. A mod with a word list is a fair stopgap and a weak privacy story for a tool that can run anything in your shell. Removing the address is the right call. I'd keep the hook after that release anyway, because your git identity is still one command away.
Have you grepped your own Claude Code logs for your email? What did you find, and do you think this should be opt-in or gone entirely?
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.