본문으로 건너뛰기
목록으로 돌아가기
기술
15 분

AI의 숨겨진 추론이 유출됐다는 논문, 어디까지 사실일까?

Threads의 Stolen Thoughts 논문 소개를 검증하고, 추론 복원의 한계·실제 로그 노출 범위·현재 재현 가능성을 구분합니다. 문맥 결합과 로그 공개 경계를 Node.js 실습으로 확인합니다.

박효열 (Hyoyoul Park)
#AI 보안#추론#API#암호화#프롬프트 인젝션

확인 기준: 2026-09-13. 출발점은 CHOI의 Threads 소개글이다. 공개 페이지에서 확인한 도입부의 주장을 검토했으며, 후속 댓글 전체를 확인한 요약은 아니다. 근거는 Panfilov 외 연구진의 Stealing Reasoning Traces from Proprietary LLM APIs, arXiv v1과 공식 API 문서다. 논문의 실험 결과와 이 글에서 직접 실행한 로컬 예제를 구분한다.

핵심 주장은 사실이다. 다만 범위와 시점이 중요하다

“주요 AI 회사의 숨겨진 추론이 API를 통해 유출됐고, 공개 로그에서 비밀정보가 발견됐다”는 주장은 실제 연구에 근거한다. 그러나 이를 암호가 깨졌다, 모든 내부 사고를 완벽하게 복원했다, 지금도 모든 모델에서 같은 공격이 된다고 읽으면 연구가 입증한 범위를 넘어선다.

논문은 2026년 8월 10일 공개됐다. 주된 실험 범위는 7월 초의 API와 모델이다. 연구진은 업체에 신고한 뒤 동일한 공격을 더 이상 성공시키지 못했다고 적었다. 이는 해당 공격 경로의 완화를 시사하지만, 업체별 패치의 내부 구현이나 모든 변형에 대한 안전성을 증명하지 않는다. 이 글도 현재 상용 API의 취약 여부를 독립적으로 재현한 보고서가 아니다. 논문 §5.1–5.2

원문의 주장과 검증 결과

주장 또는 읽히기 쉬운 해석판단근거와 제한
Anthropic·OpenAI·Google에서 추론 추출을 보였다연구 결과로 확인시험한 모델과 API 조합에서 성공했다. 모든 모델 조합의 호환성을 뜻하지 않는다.
세 회사에 같은 방식을 썼다원리는 같다암호화 블록의 재전송과 호환 모델을 이용했다. 세부 요청 형식과 추출 절차는 업체별로 달랐다.
추론 원문을 통째로 복원했다표현을 좁혀야 한다추출 토큰 수가 API의 추론 토큰 수에 가깝지만, 정답 원문이 없어 모든 토큰의 일치를 보장하지 못한다.
공개 로그에서 API 키와 비밀번호를 찾았다연구 결과로 확인연구진의 탐지·분류 결과다. 모든 값의 현재 유효성이나 실제 계정 침해를 입증한 것은 아니다.
지금도 두 번 호출하면 누구나 추출할 수 있다현재 일반화 불가신고 이후 동일 공격이 막혔다는 연구진의 설명이 있다.

이 표의 “확인”은 논문에 해당 실험과 결과가 있다는 뜻이다. 이 블로그가 세 회사의 비공개 서버 상태나 연구 원본 전체를 독립 감사했다는 뜻은 아니다. 논문 §2.3–2.5, §4.1, §5

숫자는 무엇을 센 것인가

연구진은 공개 에이전트 세션 6,708개에서 암호화된 추론 블록 315,320개를 복원했다고 보고했다. 초록의 개인정보 367건·자격증명 182건은 벤치마크 출처까지 포함한 집계다. 벤치마크를 제외한 사용자 세션에서는 서로 다른 API 키 62개, 비밀번호 33개, 액세스 토큰 24개, 개인키 7개를 보고했다. 프로젝트 페이지는 이 범위의 자격증명을 126개로 표시한다. 182개를 모두 실제로 작동하는 API 키라고 바꿔 쓰면 틀린다. 논문 §4.1, 연구진 프로젝트 페이지

유명한 항공권 예약 예시의 여권·카드 정보는 합성 벤치마크 인물의 데이터다. 반대로 실제 사용자 세션에서 보고한 노출까지 모두 가짜라고 할 수도 없다. 표본 선정과 자동 분류의 한계가 있으므로 이 연구의 비율을 전체 AI 사용자의 피해율로 확장하지 않는다.

암호를 푼 것이 아니라, 복호화를 허용하는 경로를 이용했다

추론 모델은 최종 답변 외에 다음 호출에서 이어 쓸 상태를 만들 수 있다. 서버가 모든 상태를 보관하는 대신 클라이언트에게 암호화된 블록을 돌려주면, 앱은 이를 다음 요청에 다시 넣는다.

정상 경로와 논문이 관찰한 경로를 나누면 다음과 같다.

정상적인 대화 이어가기
모델 A → 암호화된 상태 → 앱의 비공개 저장소
                              ↓
                     같은 대화의 다음 요청
                              ↓
                     공급자가 검증·복호화

연구에서 관찰한 경계 실패
다른 세션의 블록 → 같은 업체의 호환 모델 B에 재전송
                              ↓
                     공급자가 검증·복호화
                              ↓
                     모델 B가 내용을 출력

블록은 변조하지 않아도 문제가 될 수 있다. 암호문과 인증 태그가 유효하다는 사실은 “공급자가 만든 바이트”라는 근거이지, “지금 요청한 사용자와 대화가 읽어도 된다”는 허가가 아니다. 논문의 핵심은 이 구분이 충분히 강제되지 않았다는 점이다. OpenAI 블록을 Google에 보내 복호화했다는 뜻도 아니다. 모델 간 이동은 동일 공급자 내부의 시험된 호환 범위를 말한다. 논문 §2

또한 블랙박스에서 여러 조합이 통과했다는 관찰만으로 업체 내부의 실제 키가 몇 개인지 확정할 수 없다. “전역 키 하나를 사용한다”는 설명은 연구진의 추론으로 읽어야 한다. 최초 문제 제기를 한 Matthew Green의 글도 암호화된 상태를 다른 문맥에 재사용하는 문제를 다룬다. Green의 원문

공식 문서가 확인해 주는 부분

현재 문서에서도 암호화된 상태를 클라이언트가 보관하고 다시 보내는 구조는 확인된다. 이것만으로 현재 추출 공격이 가능하다고 결론 낼 수는 없다.

API공식 문서에서 확인한 필드와 의미
OpenAI Responsesstateless 모드의 reasoning item에는 encrypted_content가 기본 포함된다. store: false나 ZDR에서 해당하며, 이후 호출에 전달할 수 있다.
Anthropic Messagesthinking 블록의 signature에 암호화된 전체 추론이 담긴다. 다시 전달할 때 블록을 그대로 보존하도록 안내한다.

OpenAI의 상태 보존 문서, Anthropic의 Thinking encryption

store: false는 내 앱의 디버그 로그까지 지운다는 뜻이 아니다. 서버의 보관 정책과 내가 저장·공개한 응답은 별개의 데이터 흐름이다. thinking 텍스트가 비어 있거나 화면에서 추론을 숨겼어도, 응답의 다른 필드까지 비어 있다고 가정해서는 안 된다.

구현 예시 1: 로컬에서 재전송 문제와 방어를 비교하기

실제 원리를 익히려면 암호화와 권한 검사를 분리한 작은 실험이 유용하다. 아래 예제는 Node.js 24 이상에서 실행하는 자체 제작 모형이다. 실제 공급자의 암호 형식이나 모델을 사용하지 않으며, LLM을 설득하는 단계도 구현하지 않는다. 마지막 평문 출력은 복호화 서비스의 정보 노출을 단순화한 것이다.

전체 실습 코드 열기. 파일을 reasoning-envelope-lab.mjs로 저장해 실행한다.

node reasoning-envelope-lab.mjs

취약한 모형은 AES-GCM으로 바이트 변조만 검사하고 사용자 문맥을 사용하지 않는다.

const blob = seal('DEMO_ONLY_NOT_A_CREDENTIAL', Buffer.alloc(0));

// 잘못된 예: 인증된 사용자가 바뀌어도 같은 암호문을 열어 준다.
function vulnerableResume(blob, authenticatedContext) {
  return open(blob, Buffer.alloc(0));
}

개선 모형은 AEAD의 추가 인증 데이터(AAD)에 사용자·세션·모델·목적·순서·이전 상태·만료 시각을 결합한다. AAD는 비밀값을 숨기는 기능이 아니라, 함께 인증할 문맥을 지정하는 기능이다.

function aad(c) {
  return Buffer.from(JSON.stringify([
    'reasoning-lab-v1', c.tenant, c.user, c.session, c.model,
    c.purpose, c.turn, c.parent, c.expiresAt,
  ]));
}

const blob = seal(marker, aad(aliceContext));
open(blob, aad(aliceContext)); // 성공
open(blob, aad(bobContext));   // 인증 실패

bobContext는 클라이언트가 자칭하는 JSON이 아니라 서버가 인증 정보와 대화 저장소에서 재구성한 기대 문맥이어야 한다. 요청자가 Alice의 문맥을 그대로 제출하게 해 놓고 그것을 신뢰하면 방어가 무너진다. 예제는 필드 순서를 고정하지만, 운영 구현에는 스키마·타입·길이 검사와 버전 관리도 필요하다. Node.js에서는 setAAD()와 setAuthTag()를 맞춰 사용하고, final()의 인증 성공 전에 평문을 외부로 내보내지 않는다. Node.js Crypto 문서

실행해서 확인한 결과

실습 조건관찰 결과
문맥 없는 암호문을 다른 사용자에게 재사용복호화 성공: 취약한 모형의 문제 재현
올바른 문맥으로 묶인 암호문복호화 성공
사용자·세션·모델 등을 바꾸거나 암호문 변조인증 실패
같은 유효 문맥에서 두 번 재사용둘 다 성공: AAD만으로 중복 사용은 막지 못함
만료 검사와 사용 완료 상태 추가만료·두 번째 소비 거부
공개용 지표만 새 객체로 생성원본 응답·중첩 비밀 필드 미포함

첨부 코드는 여섯 묶음의 assertion을 통과했다. 이것은 로컬 모형의 확인 결과이며 상용 API 취약점의 현재 재현 결과가 아니다.

특히 같은 문맥의 재전송을 놓치기 쉽다. 실제로 한 번만 허용해야 하는 동작에는 데이터베이스 트랜잭션이나 compare-and-swap으로 순서·소비 상태를 원자적으로 갱신해야 한다. 네트워크 재시도는 같은 idempotency key에 저장된 결과를 돌려주도록 설계한다. 예제의 메모리 Set은 단일 프로세스에서만 작동하며 재시작·분산 실행을 해결하지 않는다.

구현 예시 2: 블로그에 공유할 로그와 대화 재개용 상태 나누기

내가 AI 앱 개발자라면 공급자의 내부 암호 형식을 고칠 수는 없다. 대신 내 서비스의 저장·공개·가져오기 경계를 통제할 수 있다.

API 응답
  ├─ 비공개 대화 상태: 접근 제어 + 보존 기간 + 저장 암호화
  │                     → 인증된 원래 대화에서만 이어 사용
  └─ 공개 관측 데이터: 허용한 모델 분류·토큰 수만 새로 구성
                        → 블로그, 분석 자료, 공개 실행 기록

공개 경로에는 { ...response }처럼 원본을 복사한 뒤 알려진 비밀 필드 몇 개를 지우는 방식을 쓰지 않는다. 미래 SDK가 새로운 중첩 필드를 추가하면 그대로 새어 나갈 수 있다. 다음 코드는 첨부 실습의 지표 전용 exporter와 같은 방식이다.

function publicMetrics(raw) {
  const allowedModels = new Set(['demo-model-a', 'demo-model-b']);
  const count = (n) => Number.isSafeInteger(n) && n >= 0 ? n : 0;
  return {
    schema: 1,
    model: allowedModels.has(raw.model) ? raw.model : 'other',
    inputTokens: count(raw.usage?.input_tokens),
    outputTokens: count(raw.usage?.output_tokens),
  };
}

이 예제는 본문을 공개하지 않는 지표 exporter다. 대화 본문까지 공유하려면 별도로 검토한 사용자 메시지와 최종 답변만 새 문서에 넣고, 도구 결과·첨부파일·개인정보도 확인해야 한다. 눈에 보이는 답변에도 비밀정보가 있을 수 있다. 원본에서 signature라는 이름만 지웠다는 이유로 안전 판정을 내릴 수 없다.

반대로 대화 재개용 원본에서 필요한 블록을 임의로 삭제하면 API 동작이 깨질 수 있다. 공개용 exporter와 비공개 replay 저장소를 다른 함수와 스키마로 관리해야 하는 이유다.

구현 예시 3: 외부 실행 기록을 가져오는 에이전트

다른 사람이 올린 실행 기록을 이어서 돌리는 기능은 편리하지만, 암호화된 내부 상태는 사용자가 검토할 수 없다. 내 서비스에서는 다음 정책을 적용할 수 있다.

  1. 외부 파일을 SDK 요청 객체로 그대로 역직렬화하지 않는다.
  2. 가져오기 전용 스키마로 허용한 공개 텍스트만 읽고 새 대화를 시작한다.
  3. 가져온 텍스트는 참고 자료로 다룬다. 서명이나 과거 assistant 역할이 있어도 도구 실행 권한을 부여하지 않는다.
  4. 파일 쓰기·업로드·자금 이동처럼 효과가 남는 도구는 서버의 사용자 권한과 목적지 정책으로 다시 검사한다.

예를 들어 문서 요약 에이전트의 도구 계정에는 읽기 권한만 주고, 배포 에이전트의 승인 여부는 모델 문장과 분리된 서버 상태로 저장한다. 모델이 “이미 승인됐다”고 출력해도 서버의 승인 레코드가 없으면 실행하지 않는다. 이 설계는 숨겨진 블록뿐 아니라 평문 프롬프트 인젝션에도 필요한 경계다.

우리 시스템에 적용하는 순서

단계할 일완료 기준
저장 위치 파악DB, APM, CI artifact, 에러 로그, 공유 데이터셋에서 원본 API 응답의 흐름을 찾는다누가 읽고 얼마나 보관하는지 설명할 수 있음
공개 경로 축소원본 덤프를 중단하고 허용 필드만 새 객체로 만든다알 수 없는 중첩 필드를 넣어도 공개 산출물에 나오지 않음
비공개 재개 경로 보호사용자·대화 소유권을 확인하고 외부 opaque 블록 가져오기를 제한한다다른 계정과 다른 대화의 상태 접근이 거부됨
도구 권한 분리비밀값은 실행 서비스가 사용하고 모델에는 필요한 결과만 준다프롬프트와 원본 도구 결과에 장기 자격증명이 불필요함
노출 대응실제 비밀이 포함됐을 가능성이 있는 공개 원본을 비공개화하고 해당 자격증명을 폐기·교체한다기록을 삭제하는 것과 키를 무효화하는 것을 각각 확인

공급자가 직접 상태 암호화를 구현한다면 문맥 결합, 키 버전·회전, 만료·폐기, 서버 측 상태 보관도 검토할 수 있다. 앱이 공급자 블록을 다시 암호화하는 것은 내 저장소의 추가 보호일 뿐이다. 공급자 원본이 다른 경로로 유출되었을 때 그 공급자 API의 재사용 정책까지 바꾸지는 못한다. 서버에 상태를 두는 설계도 ID별 권한 검사가 없으면 해결책이 아니다.

이 연구가 남기는 실용적인 질문은 “AI가 무슨 생각을 했는가”에 그치지 않는다. 읽을 수 없는 응답 필드를 누가 다시 사용할 수 있고, 그때 어떤 권한으로 처리하는가를 시스템 설계에서 명시해야 한다.

출처와 재현 범위