hyunsu blog

홈동향

Claude Code 계정 이메일이 User-Agent 로 샌 경로, 수정 전에 막는 법

목차 · 7
  1. 01계정 이메일은 7월부터 모델 컨텍스트에 있었다
  2. 02왜 하필 User-Agent 헤더였나
  3. 0311월 3일 검사는 셸 명령의 한 가지 모양만 막는다
  4. 04근본 수정은 이메일 빼기, 변경 기록에는 아직 없다
  5. 05한국 팀에서는 회사 계정과 자동 허용부터 본다
  6. 06지금 할 일: 기록을 찾고, 훅으로 막고, 다음 릴리스를 본다
  7. 07자주 묻는 질문

claude.ai 계정으로 로그인한 Claude Code 에 curl·wget 같은 셸 명령을 맡겨 왔다면, 계정 이메일이 외부 사이트의 User-Agent 헤더로 나갔는지 확인해야 한다. 11월 3일 릴리스부터 기본으로 켜진 내장 검사 cc-plugin-privacy 는 셸 도구 입력에 주소와 User-Agent 표현이 함께 있을 때만 막는다. 켜 둔 상태에서도 거부되지 않았다는 재현까지 나왔다. Anthropic 이 예고한 근본 수정(모델 컨텍스트에서 이메일 빼기)은 11월 8일 현재 변경 기록에 없으니, 그때까지는 지난 세션 기록을 검색하고 PreToolUse 훅으로 직접 막는 편이 낫다.

01 계정 이메일은 7월부터 모델 컨텍스트에 있었다

이번 논란은 Claude Code 저장소에 11월 5일 올라온 이슈 #107757 에서 커졌다. 신고자는 macOS 에서 전날 나온 릴리스를 쓰다가, 웹 검색 도중 자기 주소가 User-Agent 헤더에 실려 제3자 사이트로 갔다고 적었다. 하루 수십 통씩 오는 사기 메일도 이 때문이라고 했지만, 요청 헤더나 주소를 받은 사이트 같은 근거는 붙이지 않았다. 이 글은 주소가 밖으로 나간 경로와 막는 법만 다룬다. 스팸과의 인과나 법적 쟁점은 다루지 않는다.

뿌리는 더 오래됐다. 7월 25일에 열린 이슈 #81138 은 로그인 계정의 이메일이 세션마다 모델 컨텍스트에 실린다고 신고했다. 신고자는 배포 번들을 풀어 userEmail 필드를 찾았고, 문서 어디에도 이 값을 끄는 설정이 없다고 적었다. 같은 스레드에는 모델이 답변 중에 주소를 그대로 꺼낸 일도 올라왔다. noreply 주소로 맞춰 둔 git 설정을 무시하고 git -c user.email=<계정 주소> 로 커밋을 만든 일도 있었다.

왜 넣었는지는 Claude Code 팀원이 Hacker News 댓글에서 밝혔다. 계정 정보가 없으면 모델이 @author 태그 같은 자리에 이름을 지어내서, 로그인한 사용자의 주소를 컨텍스트에 줬다는 설명이다. #81138 의 8월 28일 댓글은 그 뒤 안내 문구에 제한이 붙었다고 보고했다. 주소를 사용자 식별에만 쓰고, 요청이 없으면 다른 서비스로 보내지 말라는 내용이다. 그런데 10월 20일에는 웹 하네스에서 모델이 User-Agent 에 주소를 넣었다는 댓글이 또 달렸다. 문구 하나로는 모델을 묶지 못한 셈이다.

02 왜 하필 User-Agent 헤더였나

스크립트에 연락처를 요구하는 사이트가 있어서다. Wikimedia 의 User-Agent 정책은 봇과 스크립트가 운영자에게 연락할 수단을 헤더에 넣으라고 한다. 이메일·웹사이트·위키 사용자 이름 가운데 하나면 되고, 비었거나 흔한 기본값이면 403 으로 실패한다고 적었다. HN 의 한 댓글은 SEC API 가 기본 curl 값에는 403 을, 이메일을 넣은 값에는 200 을 돌려줬다고 썼다가, 나중에 curl 이 아닌 값이면 대부분 받아 준다고 고쳤다.

팀원 설명을 따르면, 모델은 이런 사이트로 보낼 curl 명령을 직접 짜면서 컨텍스트에 있던 계정 주소를 연락처 자리에 넣었다. 요청이 오간 순서는 이렇다.

text
1. 로그인 → 계정 이메일이 컨텍스트에 실림
2. 사용자 → 모델: 사이트 자료 수집 요청
3. 사이트 → 모델: UA 에 연락처를 요구
4. 모델 → Bash: curl -A "bot (계정 주소)" …
5. Bash → 외부 서버: User-Agent 헤더로 전송

신고자는 웹 검색 중이었다고 적었지만, 팀 설명대로라면 헤더를 실은 것은 웹 도구가 아니라 모델이 짠 셸 명령이다. HN 에는 업무용 연락 주소를 따로 마련해 뒀는데도 모델이 계정 주소를 넣었다는 댓글도 있었다. 그 사람은 curl 요청을 직접 들여다보고서야 알았다고 했다. 명령을 하나하나 읽지 않고 돌리는 방식이면 알아채기 어렵다.

03 11월 3일 검사는 셸 명령의 한 가지 모양만 막는다

11월 3일 릴리스의 변경 기록에 관련 항목이 한 줄 있다. 웹·셸 도구 호출이 계정 이메일을 네트워크로 보내기 전에 묻는 검사를 넣었다는 내용이다. 저장소 기여자는 이슈에서 이것이 내장 mod(확장 기능) cc-plugin-privacy 라고 안내했다. 기본으로 켜져 있고 /plugins 에서 끌 수 있다. 이 mod 의 README는 동작을 더 좁고 구체적으로 적었다.

  • Bash·PowerShell·Monitor 도구의 입력만 본다.
  • 입력에 계정 주소와 User-Agent 를 가리키는 말(User-Agent·--user-agent·curl -A·wget -U·UA= 등)이 함께 있으면 거부한다.
  • 주소는 대소문자를 가리지 않고, 퍼센트 인코딩은 한 번까지 잡는다.
  • 권한 확인 창 없이 거부하며, 저장해 둔 허용 규칙이나 auto 모드 분류기로도 뒤집지 못한다.
  • 사용자 설정·--settings·관리 설정의 enabledPlugins 에서 "cc-plugin-privacy@builtin": false 로 끌 수 있지만, 저장소 설정으로는 끌 수 없다.

변경 기록과 README 는 서로 다른 말을 한다. 변경 기록과 팀원 댓글은 웹 도구 호출까지 보고, 보내기 전에 묻는다고 썼다. README 는 묻지 않고 거부하며 WebFetch·WebSearch·MCP 도구는 보지 않는다고 적었다. 검사 코드 곁에 놓인 설명은 README 쪽이니, 웹 도구까지 검사받는다고 기대하지 않는 편이 안전하다.

README 는 이 mod 를 스스로 "a courtesy, not a boundary" 라고 불렀다. 보안 경계가 아닌 보조 장치라는 뜻이고, 그래서 설계상 지나가는 모양도 숨기지 않았다. 아래 표는 README 의 설명을 호출 모양별로 옮긴 것이다.

호출 모양내장 검사
curl -A "bot (주소)"거부
-H "User-Agent: …" 와 본문의 주소거부
URL·본문·From: 헤더에만 주소통과
curl -u 에 주소통과
$(git config user.email) 같은 실행 중 값통과
주소가 든 스크립트 파일 실행통과
WebFetch·WebSearch·MCP 도구보지 않음

설계상 빈틈 말고, 켜 둔 상태에서 막지 못했다는 보고도 나왔다. iMasters 가 소개한 재현에서는 Windows 환경의 11월 4일 릴리스에 cc-plugin-privacy@builtin 이 로드된 것으로 나왔다. 그런데 Bash 도구로 돌린 echo 'User-Agent: test <주소>' 와 curl -A "test <주소>" 가 둘 다 그대로 실행됐다. agentspulse 의 정리에도 macOS 에서 검사가 들어간 두 릴리스로 같은 echo 시험을 했더니 거부가 없었다는 보고가 실렸다. 둘 다 개인의 재현이고 원인은 아직 밝혀지지 않았다. 그래도 최신으로 올렸다는 사실만으로 안심하기엔 근거가 모자란다.

README 는 검사가 꺼지는 길도 몇 가지 더 적었다. 세션마다 한 번 읽는 서버 쪽 스위치가 있고, 설치한 다른 플러그인의 훅이 이 거부를 뒤집을 수도 있다.

04 근본 수정은 이메일 빼기, 변경 기록에는 아직 없다

Claude Code 팀원은 같은 HN 댓글에서 사과하고, 모델에 주는 컨텍스트에서 계정 이메일을 빼겠다고 밝혔다. 다음 릴리스에 들어가며 다음 주 초에 나올 것이라고 했다. 지금 검사가 모델이 스스로 짜서 돌리는 스크립트 같은 경우를 다 막지 못한다는 점도 그 댓글에서 인정했다. 11월 8일에 연 변경 기록의 맨 위는 11월 6일 릴리스이고, 이메일을 뺐다는 항목은 없다.

여기까지를 날짜 순으로 놓으면 이렇다.

날짜일어난 일
7월 25일#81138: 컨텍스트의 계정 이메일 신고
8월 28일사용 제한 문구가 붙었다는 댓글
10월 26일HN 댓글 시점의 stable 채널 릴리스
11월 3일cc-plugin-privacy 검사 추가
11월 5일#107757 신고, 기여자가 검사 안내
11월 6일최신 릴리스, 이메일 제거 항목 없음
다음 주 초(예정)컨텍스트에서 이메일 제거

발표가 나와도 내 환경에 들어오기까지는 시차가 있다. 공식 설치 문서에 따르면 네이티브 설치의 기본 채널은 latest 다. stable 은 보통 일주일쯤 묵은 버전을 주고, 큰 회귀가 있는 릴리스는 건너뛴다. Homebrew 의 claude-code cask 와 apt·dnf·apk 의 stable 저장소도 이 채널을 따른다. 팀원 댓글 시점의 stable 은 10월 26일 릴리스로, 이메일 검사가 들어가기 전 버전이었다. 이메일 제거가 latest 에 나와도, 문서 설명대로라면 stable 쪽은 일주일쯤 뒤에 받는다.

05 한국 팀에서는 회사 계정과 자동 허용부터 본다

해당하는지는 인증 방식·채널·권한 설정·작업 종류로 갈린다. 아래는 이번에 연 출처로 가린 조건이다.

조건뜻
claude.ai 계정(OAuth) 로그인계정 주소가 컨텍스트에 있다
API 키·Bedrock·Vertex 인증주소가 빠진다는 사용자 보고
Team·Enterprise 회사 계정나가는 주소가 회사 도메인이다
stable 채널·Homebrew 기본 cask내장 검사조차 없을 수 있다
curl 허용 규칙·auto 모드사람 확인 없이 실행된다
크롤링·공개 데이터 수집 작업연락처 요구 사이트를 만나기 쉽다

이미 외부 서버로 간 주소는 되돌릴 방법이 없다. 그래서 할 일은 이미 나갔는지 찾는 것과 다음 호출을 막는 것 두 가지로 좁혀진다. 이메일은 비밀번호나 토큰처럼 그 자체로 계정을 여는 값은 아니다. 다만 회사 계정이라면 어느 사이트로 갔는지 보안 담당과 나눌 만하다.

작은 팀이 할 일은 훅 파일 하나와 설정 몇 줄이고, 되돌릴 때도 설정에서 훅 항목을 지우면 끝난다. 여러 명이 쓰는 팀이라면 관리 설정의 requiredMinimumVersion 으로 검사 이전 버전의 실행을 막을 수 있다. 누가 관리 설정으로 이 검사를 꺼 두지 않았는지도 함께 본다. 반대로 claude.ai 로그인 없이 API 키·Bedrock·Vertex 로만 쓰고 네트워크 명령을 허용한 적이 없다면 서두를 일은 적다. 그래도 사용자 보고에 기댄 판단이니, 아래 3번의 기록 검색은 한 번 돌려 볼 만하다.

06 지금 할 일: 기록을 찾고, 훅으로 막고, 다음 릴리스를 본다

  1. 버전과 채널을 확인한다. claude --version 의 번호를 변경 기록에서 account email 검사가 처음 실린 11월 3일 릴리스와 견주고, 낮으면 올린다.
bash
claude --version
claude doctor    # 설치 방식·자동 업데이트 상태
claude update    # 네이티브·npm 설치

Homebrew 의 claude-code cask 는 stable 채널이라 brew upgrade claude-code 를 해도 stable 버전에 머물고, 자동 업데이트도 되지 않는다. 검사가 든 버전을 바로 받으려면 claude-code@latest cask 로 옮겨야 한다. 옮길지는 아래 훅을 단 뒤에 정해도 늦지 않다.

  1. 내장 검사가 실제로 거부하는지 시험한다. /plugin 의 Installed 탭이나 claude plugin list 에서 cc-plugin-privacy@builtin 을 찾는다. 그다음 세션에서 아래 명령을 Bash 도구로 돌려 달라고 한다(자리 표시를 본인 주소로 바꾼다). 어디에도 보내지 않는 명령이고, README 대로라면 거부돼야 한다.
bash
echo 'User-Agent: test 본인-계정-이메일' > /dev/null
  1. 지난 세션 기록에서 주소가 나간 흔적을 찾는다. 세션 기록은 ~/.claude/projects 아래 JSONL 파일로, 평문 그대로 남는다.
bash
EMAIL="본인-계정-이메일"
grep -rliF "$EMAIL" ~/.claude/projects \
  | xargs grep -liE 'user-agent|curl -A|wget -U' /dev/null

끝의 /dev/null 은 첫 grep 이 아무 파일도 찾지 못했을 때 두 번째 grep 이 입력을 기다리며 멈추지 않게 붙였다. 이 검색은 주소와 User-Agent 관련 문자열이 한 파일에 같이 있는 기록만 추린다. 후보를 좁힐 뿐이니, 나온 파일을 열어 주소가 든 줄이 실제 curl·wget 호출인지 확인한다.

기록이 남는 기간에도 한계가 있다. 데이터 사용 문서에 따르면 로컬 기록은 기본 30일 동안만 남고, 기간은 cleanupPeriodDays 로 바꾼다. 검색에서 아무것도 안 나와도, 주소가 컨텍스트에 실려 있던 7월 무렵부터의 호출을 모두 확인한 것은 아니다.

  1. PreToolUse 훅으로 모든 도구 입력에서 주소를 막는다. 스크립트를 ~/.claude/hooks/ 에 두고 사용자 설정 ~/.claude/settings.json 에 건다.
bash
#!/usr/bin/env bash
# ~/.claude/hooks/block-account-email.sh
EMAIL="본인-계정-이메일"
ENCODED="${EMAIL/@/%40}"
if grep -qiF -e "$EMAIL" -e "$ENCODED"; then
  echo "Blocked: account email in tool input. Retry without it." >&2
  exit 2
fi
exit 0
json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          { "type": "command", "command": "bash ~/.claude/hooks/block-account-email.sh" }
        ]
      }
    ]
  }
}

훅 문서에 따르면 PreToolUse 훅은 도구 이름과 입력을 JSON 으로 stdin 에 받는다. 종료 코드 2 를 내면 호출이 막히고, stderr 가 모델에게 이유로 간다. matcher 를 "*" 로 두었으니 내장 검사가 보지 않는 WebFetch·MCP 도구와, 주소를 파일에 쓰는 Write 도 걸린다. 대가도 있다. package.json 의 author 처럼 주소를 일부러 적어야 하는 작업까지 막히고, $(git config user.email) 처럼 실행 중에 계산되는 값은 여전히 못 잡는다. 기존 설정에 hooks 가 있으면 PreToolUse 배열에 항목만 더하면 된다.

이 훅은 내 컴퓨터에서 도는 세션에만 걸린다. 같은 문서는 클라우드 세션이 로컬 ~/.claude/settings.json 을 읽지 않는다고 적었다.

  1. 모델이 쓸 연락처를 따로 정해 둔다. Wikimedia 정책은 웹사이트 주소도 연락처로 받으니, 프로젝트 URL 을 주는 쪽이 낫다. 프로젝트의 CLAUDE.md 에 아래 두 줄을 넣는다. 다만 이런 안내는 모델이 따를 때만 듣는다. 앞서 본 안내 문구처럼 깨질 수 있으니 훅을 대신하지는 못한다.
markdown
- User-Agent 에 연락처가 필요하면 https://example.org/our-project 를 쓴다.
- 계정 이메일과 git 설정의 이메일은 어떤 요청에도 넣지 않는다.
  1. 다음 릴리스 변경 기록을 지켜본다. 이메일을 컨텍스트에서 뺐다는 항목이 실리는지, #107757 에 팀 답변이 붙는지를 본다. 이메일이 빠진 뒤에도 훅은 남겨 두는 편이 낫다. 모델이 git log 나 설정 파일을 읽으면 주소는 다른 길로 컨텍스트에 들어온다.

07 자주 묻는 질문

클론한 저장소가 내장 검사를 꺼 버릴 수 있나

아니다. README 기준으로 저장소의 .claude/settings.json 에 적은 enabledPlugins 값은 이 검사에 통하지 않는다. 다만 내가 설치한 다른 플러그인의 훅은 거부를 뒤집을 수 있으니, 낯선 플러그인을 들일 때 함께 본다.

API 키로 바꾸면 주소가 안 들어가나

예, 사용자 보고로는 그렇다. #81138 의 8월 28일 댓글은 주소 줄이 claude.ai 계정(OAuth) 로그인일 때만 붙고, API 키·Bedrock·Vertex 인증에서는 빠진다고 했다. 공식 문서로 확인한 내용은 아니다.