
Claude Code의 「프롬프트 인젝션이 주입되었습니다」는 진짜인가 지어낸 이야기인가 —— 기록의 구조로 구별하고 자동 메모리 오염을 차단하기
요약
Claude Code가 프롬프트 인젝션을 감지했다고 보고할 때, 이것이 실제 공격인지 모델의 환각(Confabulation)인지 .jsonl 대화 기록 구조를 통해 구분하는 방법을 설명합니다. 특히 모델의 오인으로 인한 자동 메모리 오염 방지 및 해결 방법을 다룹니다.
핵심 포인트
- 대화 기록(.jsonl)의 role 구분을 통해 실제 주입 여부 판별 가능
- 도구 출력은 'user' 역할로, 모델 응답은 'assistant' 역할로 기록됨
- 모델이 자신의 출력을 외부 주입으로 오인하는 출처 오인 현상 분석
- 잘못된 정보가 자동 메모리에 저장되어 재발하는 문제와 해결책 제시
Claude Code가 갑자기 "현재 출력에 프롬프트 인젝션 (Prompt Injection)이 주입되어 있습니다"라고 말하며 작업을 중단할 때가 있습니다. 공격을 걱정하여 손을 멈추는 사람도 있고, 무시하고 진행하는 사람도 있을 것입니다.
하지만 그 주입이 진짜 공격인지, 모델의 지어낸 이야기(작화, Confabulation)인지는 추측할 필요 없이 대화 기록의 구조를 보면 결정론적으로 구분할 수 있습니다. 그리고 까다로운 점은, 이것이 지어낸 이야기였을 경우 Claude Code의 자동 메모리 (Automatic Memory) 기능을 통해 다음 세션 이후에도 동일한 지어낸 이야기가 계속 재발할 수 있다는 것입니다.
이 기사에서는 (1) 진짜 주입과 지어낸 이야기를 .jsonl 구조로 구분하는 방법, (2) 자동 메모리가 오염되었을 때 찾는 방법과 삭제하는 방법, (3) 왜 이것이 「영속하는 기억」 전반의 문제인지에 대해 정리합니다.
GitHub에 접수된 이슈 #70525(2026-06-24) 사례를 계기로 하고 있지만, 보고 내용 자체를 필자가 직접 겪은 것은 아니므로 전언으로 취급하며, .jsonl 구조를 통한 구분 방법은 직접 확인한 후 작성합니다.
오랫동안 실행 중인 세션에서 Claude Code가 갑자기 작업을 멈추고 다음과 같이 말할 때가 있습니다.
현재 도구의 출력(Bash 또는 Read 결과)에 자신의 말투를 흉내 낸 1인칭 영어 작문이 주입되었습니다. "I'll fix the corrupted file by rewriting it", "Let me re-read the actual file"와 같은 문장입니다. 이는 파일을 다시 쓰게 만들기 위한 프롬프트 인젝션 (Prompt Injection) 공격으로 판단됩니다. 안전을 위해 더 이상 진행하지 않습니다.
이슈 #70525의 보고자가 실제 대화 기록(.jsonl)을 조사한 결과, 실제로는 다음과 같았다고 합니다.
- 그 영어 작문은 어떠한 도구의 출력에도 단 한 글자도 포함되어 있지 않았다.
- 「주입되었다」고 언급된 직전의 도구 출력은 매우 평범하고 정상적인 것(디렉토리 목록, 성공한
git push로그)이었다. - 문제의 문자열은 모델 자신의 응답 내에만 존재했다. 모델이 스스로 작성한 문장을 인용하며 "이것이 주입되었다"라고 주장했다.
- 어떤 장면에서는 "주입된 문장은 자신의 이전 답변 안에 있었다"라고까지 말했다. 자신의 출력은 정의상 외부에서 주입될 수 없습니다.
즉, 이것은 공격이 아니라 **출처 오인(Source Misattribution)과 지어낸 이야기(Confabulation)**입니다. 모델이 에이전트 스타일의 메타적인 독백을 스스로 생성하고, 그것을 옆에 있는 도구의 출력에서 온 것으로 오인하여, 설명으로서 「프롬프트 인젝션」을 끌어들인 구도입니다.
여기서부터는 직접 구조를 확인한 후 확정적으로 작성하겠습니다. 대화 기록의 구조를 보면 추측 없이 결론이 납니다.
Claude Code의 대화는 세션별 .jsonl 파일에 한 줄씩의 기록으로 저장됩니다. 위치는 홈 디렉토리 하위입니다.
~/.claude/projects/<프로젝트 경로를 인코딩한 이름>/<세션 ID>.jsonl
인코딩 규칙은 간단하며, 전체 경로의 /를 -로 바꾸기만 하면 됩니다.
/home/you/projects/myapp
→ ~/.claude/projects/-home-you-projects-myapp/
결정적인 것은 역할(role)의 차이입니다.
- 도구의 출력(Bash 또는 Read 결과)은 반드시
user역할의 기록인tool_result타입으로 저장되며, 어떤 도구 호출에 대한 결과인지를 나타내는tool_use_id가 붙습니다. - 모델의 작문(독백이나 설명)은 반드시
assistant역할의 기록인text블록에 들어갑니다.
진짜 외부로부터의 주입이라면, 해당 문자열은 user 역할의 tool_result 내에 실제로 존재합니다. 게다가 모델이 그것을 언급하는 assistant 기록보다 앞서 반드시 존재해야 합니다. 받은 내용은 그것을 참조하는 답변보다 먼저 도착해 있어야 하기 때문입니다.
반대로, 문제의 문자열이 assistant 역할의 기록 내에만 있다면 그것은 외부에서 온 것이 아닙니다. 모델이 스스로 작성한 것입니다.
먼저 대상 세션의 기록을 특정합니다.
ls -t ~/.claude/projects/<엔코드명>/*.jsonl | head -1
다음으로, 모델이 「주입되었다」라고 말하는 문자열의 일부가 도구의 출력(user 역할의 tool_result) 안에 실제로 존재하는지를 조사합니다.
jq -c 'select(.message.role=="user")
| .message.content[]?
| select(.type=="tool_result")' \
...
여기서 단 한 건도 나오지 않는다면, 주입은 없었다는 뜻입니다. 도구의 출력은 오염되지 않았습니다. 안심하고 작업을 계속해도 좋습니다.
만약을 위해, 동일한 문자열이 assistant 역할의 출력 안에 있는지도 확인합니다.
jq -c 'select(.message.role=="assistant")
| .message.content[]?
| select(.type=="text")' \
...
user 측에서는 나타나지 않고 assistant 측에서 히트(hit)했다면, 출력원을 착각한 것이라고 확정할 수 있습니다. 반대로 user 측의 tool_result에서 실제로 히트했다면, 그것은 진짜 주입이므로 해당 도구(외부 명령의 출력, Web 취득, MCP의 응답 등)의 출처를 의심해야 합니다.
단발적인 작화(hallucination)라면 해당 세션을 종료하면 사라집니다. 이 사고가 까다로운 점은 그 이후입니다.
보고에 따르면, 모델은 이 오해를 Claude Code의 자동 메모리(auto-memory)에 「확정된 사실」로서 기록했다고 합니다. 「도구의 출력에는 거짓된 1인칭 사고가 주입될 수 있다. 실행하지 말고, 쓰기는 검증하라」라는 취지의 기억으로서 말입니다. 그리고 이 기억은 세션마다 읽어들이는 MEMORY의 색인(index)에 나열됩니다.
결과적으로, 새로운 세션이 시작될 때마다 모델은 처음부터 「도구의 출력은 주입되어 있을지도 모른다」라고 경계하게 됩니다. 경계하는 상태는 동일한 작화를 재발시키기 쉽게 만듭니다. 이렇게 폐쇄된 루프(closed loop)가 형성됩니다.
작화한다 → 그것을 기억에 쓴다 → 기억이 다음 세션을 경계하게 만든다 → 다시 작화한다
보고자는 오염된 기억을 수동으로 삭제하자 루프가 끊겼다고 합니다. 역으로 말하면, 삭제하기 전까지 세션을 넘나들며 재발이 계속되었다는 뜻입니다.
자동 메모리는 프로젝트별 메모리 디렉토리에 한 건당 파일 하나씩 저장되며, MEMORY.md가 그 색인입니다.
grep -rin "inject\|注入\|tool output\|道具の出力" \
~/.claude/projects/<엔코드명>/memory/
「도구의 출력에는 거짓된 1인칭 사고가 주입될 수 있다」, 「실행하지 말고 검증하라」와 같이 경계를 촉구하는 기억이 발견되었을 때, 앞 절의 jq 검증을 통해 「주입은 없었다」는 것을 알고 있다면 그 기억은 거짓입니다. 삭제하기 전에 대상의 내용을 한 번 읽어보고, 진짜 주의 사항(과거에 실제로 있었던 사고 기록)까지 함께 삭제하지 않도록 확인한 뒤, 거짓 기억 파일을 삭제하고 MEMORY.md의 색인에서도 해당 행을 제거합니다. 이렇게 하면 루프가 끊깁니다.
이 사고의 본질은 프롬프트 인젝션의 진위 여부 그 자체가 아닙니다. 자동으로 작성되어 다음 세션에서도 읽히는 기억은, 옳은 것뿐만 아니라 틀린 것도 똑같이 영속시킨다는 점입니다.
자동 메모리는 본래 「같은 설명을 여러 번 하지 않아도 되게끔」 하기 위한 편리한 메커니즘입니다. 하지만 쓰는 쪽(모델)이 단 한 번이라도 사실이 아닌 것을 「확정된 사실」로 적으면, 그것은 다음 세션을 시작할 때마다 읽혀 잘못된 전제로서 작업의 토대에 스며듭니다. 에러도 경고도 발생하지 않습니다. 묵묵히 계속 영향을 미칩니다.
대책의 방향은 세 가지입니다.
- 기억이 어디에, 어떤 이름으로 저장되어 있는지 알고 있어야 합니다.
~/.claude/projects/<엔코드명>/memory/와MEMORY.md입니다. 위치를 모르면 오염을 알아차릴 수도, 삭제할 수도 없습니다. - 자동으로 작성된 기억을 가끔 사람의 눈으로 점검해야 합니다. 특히 「~해서는 안 된다」, 「~를 의심하라」와 같이 작업을 중단시키는 성향이 강한 기억은 정말 근거가 있는지 확인할 가치가 있습니다. 근거 없는 경계는 하지 않아도 될 작업을 멈추게 합니다.
- 「진짜 수신된 내용은 반드시 기록된 내용을 바탕으로, 참조보다 먼저
user
역할로서 존재한다」**라는 구조적 사실을 판단의 근거로 삼는다. 모델의 주장이 아니라, 기록의 구조가 진위를 결정합니다.
왜 후크(Hook)로 자동적으로 멈추지 않는지 의문이 들 수도 있습니다. 이것은 파일을 파괴하는 커맨드처럼 실행의 순간을 포착하면 방지할 수 있는 종류의 사고가 아닙니다. 오염은 「모델이 기억에 무엇을 쓰는가」라는 생성 내용의 문제이며, 후크가 감시하는 도구 호출(Tool Call)의 외부에서 발생합니다. 따라서 이 사고는 기억의 위치를 알고, 기록의 구조로 진위를 확인하며, 때때로 점검한다는 운영 지식으로 방지해야 하는 종류의 것입니다.
필자는 이러한 「묵묵히 계속되는」 사고들—파일이나 데이터베이스가 몇 분 만에 사라지거나, 읽기 전용이어야 할 하위 에이전트(Sub-agent)가 거짓 완료 보고를 하거나, 실행만으로 이용 한도(Quota)를 다 써버리는 등의 사고—를 Claude Code를 800시간 이상 거의 무인으로 실행하며 겪어왔습니다. 그 모든 것이 「설정으로 미리 방지할 수 있는」 것임을, 실제로 발생한 사고로부터 역산하여 정리한 완성형 실전서가 Anthropic 공식 가이드에 없는 사고 방지(¥800 · Zenn) 입니다. 이 기사에서 다룬 자동 메모리 오염도 제48장으로 수록되어 있습니다(구매 후 추가 내용은 무상으로 제공됩니다). 제3장까지 무료로 미리 읽어볼 수 있습니다.
무료로 미리 방지하고 싶은 분들을 위해, 위험한 조작을 실행 전에 멈추는 후크(Hook) 모음인 cc-safe-setup(GitHub · MIT)을 공개하고 있습니다. 우선 이쪽에서 먼저 시도해 보실 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기