새벽에 채팅창에 붙여넣은 값이 어디에 남는지 모르고 있었다. 퇴사하고 AI로 회사를 만드는 중이라 하루치 세션 로그를 작업일지로 바꾸는 파서를 만들었는데, 세션 7개에 도구 호출 842회짜리 하루를 넣고 돌린 첫 출력에 텔레그램 봇 토큰과 네이버 API Client ID·Secret, 검색광고 액세스 라이선스, CUSTOMER_ID가 그대로 찍혀 나왔다.

파서는 읽었을 뿐이다
값들은 클로드 코드의 세션 기록 파일(~/.claude/projects/*.jsonl)에 평문으로 저장돼 있었다. 파서는 그 원문을 충실히 읽었다. 위험한 건 화면에 뜬 출력이 아니라 그다음 계획이었다. 이 출력을 그대로 블로그 초안 생성기에 물릴 생각이었으니, 순서가 하루만 반대였으면 키가 발행됐다.
넓게 막는 쪽을 골랐다
정규식 7종을 만들었다. 발급 토큰 모양 넷, 키워드 뒤에 오는 값, 긴 해시, 인코딩된 문자열. 그리고 추출된 텍스트가 전부 함수 하나를 지나게 강제했다. 통로를 여러 개 두면 언젠가 한 곳이 마스킹을 건너뛰기 때문에, 내보내는 문자열은 예외 없이 그 함수를 통과해야 나가도록 묶었다. 실제로 로그에 있던 키 5개가 전부 막히는 걸 확인했고, 발견부터 검증까지 15분이 걸렸다.
지우면 안 되는 단어가 지워졌다
일곱 중 ‘키워드 뒤에 오는 값’ 규칙이 키워드 뒤 6자 이상을 전부 값으로 봤다. 하루치를 뽑아 크기를 재던 출력의 259번째 줄이 이렇게 나왔다.
authorization [비밀정보]
원문은 authorization denied다. 16시 38분에 맥의 권한 데이터베이스(com.apple.TCC/TCC.db)를 열려다 실패하면서 나온 줄인데, 접근이 거부됐다는 사실만 남고 거부됐다는 말이 사라졌다. 이 프로젝트에서 에러 메시지는 콘텐츠 소재 그 자체이고, 같은 에러로 검색해 들어올 사람이 붙여넣을 문자열도 정확히 그 두 단어다. 내가 만든 안전장치가 그걸 먼저 먹었다.
길이와 숫자로 가른다
기준을 바꿨다. 값이 10자 이상이거나 숫자를 포함할 때만 마스킹한다. denied는 6자에 숫자가 없어 통과하고, 실제 키 5개는 길거나 숫자가 섞여 있어 그대로 걸린다. 6과 10 사이에서 임의로 고른 값은 아니다. 지키려던 단어에서 나온 숫자다. 영어 에러 단어는 대개 짧고 숫자가 없고, 발급받은 키는 그 반대다.
고친 뒤 통과 판정은 두 줄로 했다. 뽑은 출력에 authorization denied가 원문 그대로 남아 있는지, 로그에 있던 키 5개가 전부 마스킹되는지. 한쪽만 세면 규칙이 계속 한 방향으로 틀어진다.
이 기준을 그대로 가져가면 10자 미만이고 숫자가 없는 비밀값은 통과한다. 그런 값을 쓰는 서비스를 다룬다면 길이 판정에 맡기지 말고, 토큰 모양을 직접 적은 정규식 쪽에서 잡아야 한다. 이 파서로 뽑은 로그에서 진짜 실패만 골라내는 규칙은 다음 편에서 다뤘다.
세션 기록을 글의 원자료로 쓸 생각이라면, 마스킹 규칙을 짜기 전에 지워지면 안 되는 문자열을 하나 정해두고 규칙을 돌릴 때마다 그게 살아남는지 같이 보세요. 마스킹이 붙기 전까지는 자동 발행을 연결하지 마세요.
규칙을 좁혀도 로그에 이미 평문으로 들어간 값은 되돌아오지 않아서, 텔레그램 봇 토큰과 네이버 API 시크릿은 재발급 목록에 올렸다. 화면 녹화는 아직 0바이트다. ffmpeg이 에러 없이 끝나는데 파일이 안 생기고, 원인은 VS Code에 화면 기록 권한이 없는 것인데 이 VS Code가 DMG(/Volumes/VS Code)에서 실행 중이라 권한을 줘도 유지가 안 될 수 있다. 내일 /Applications로 옮기고 다시 준다.