코딩 에이전트는 압축 파일을 여는 그 순간 이미 안전하지 않다
Step-Zero Attack: Agent Safety Before Observability
무엇인가
코딩 에이전트 보안 연구는 대체로 콘텐츠가 모델 컨텍스트에 들어온 뒤를 다룬다. 간접 프롬프트 인젝션은 검색된 문서나 도구 출력 속 텍스트가 모델을 설득하는 문제이고, 가드는 모델이 읽는 것을 검사한다. 그런데 압축 파일, 네트워크 응답, 이미지 같은 불투명한 캐리어는 안전한지 판단하려면 먼저 처리해야 하고, 그 첫 처리가 내용이 모델에 도달하기 전에 해를 끼칠 수 있다. 이 논문은 그 첫 처리 호출을 Step Zero, 거기서 발생하는 피해를 Step-Zero attack이라고 부른다. 위협 모델에서 공격자는 캐리어의 모든 바이트를 저작하지만 프롬프트·하네스·도구·가중치는 피해자 것이고, 도구 호출은 자동 승인된다. 공격자는 지시문을 넣지 않고 데이터만으로 해를 운반한다.
어떻게 동작하나
저자들은 이 상호작용을 Opaque Input Carrier(OIC) 튜플로 형식화한다.
무엇과 다른가
⟨β, π_meta, α, Σ, sink⟩
어떻게 쓰나
여기서 β는 캐리어, π_meta는 중앙 디렉터리 항목이나 HTTP 헤더처럼 겉으로 보이는 미리보기, α는 내부 바이트를 물질화하는 처리, Σ는 워크스페이스 환경, sink는 모델 컨텍스트다. 에이전트의 인식 상태는 I0(미관찰)에서 π_meta를 거쳐 I1(미리보기), 다시 관찰을 거쳐 I2(관찰됨)로 이동한다. 안전 불변식은 두 개를 곱한 것으로, 추출된 멤버 바이트가 4 MiB 이하라는 가용성 불변식과 보호된 경로가 부재하거나 정당한 내용 c*를 담아야 한다는 무결성 불변식이다. 위반 시점 t*가 콘텐츠가 sink에 도달한 시점 t_obs보다 이르면 Immediate, 아니면 Deferred로 분류한다. 핵심 정리는 이렇다. 실행 전 가드는 에이전트가 발행하려는 호출과 양쪽 캐리어가 공유하는 미리보기 필드만 보므로, 악성 픽스처와 정상 트윈을 같은 확률로 차단한다. sink만 읽는 콘텐츠 가드는 t_obs 이후에야 판단할 수 있으니 모든 Immediate 위반보다 늦다. 따라서 즉각적 위반을 막으려면 여는 호출 자체를 보류하거나 쓰기량을 제한해야 한다.
전제와 한계
측정은 짝지은 프로토콜로 한다. 모든 악성 캐리어에는 겉으로 보이는 필드가 일치하고 선언이 참인 트윈이 붙고, 기록 프록시가 하네스가 모델에 보낸 모든 요청을 저장하며, 디스크 샘플러가 추출 바이트가 예산을 넘는 시점을 잡는다. 7개 하네스와 6개 모델, 총 42개 하네스-모델 조합에서 각 셀의 프롬프트는 './project.zip'이나 URL 같은 순수 참조 하나뿐이고 예산은 공지되지 않는다. 결과는 32/42(76.2%)가 4 MiB 디스크 예산을 초과했고, 26/42는 어떤 멤버도 모델에 도달하기 전에 초과했다. 6개 모델 중 4개는 매 라운드 위반해 84/84였다. 트윈은 전부 예산 안에 머물렀으니 피해는 공격자의 바이트만으로 발생한다. 선언이 정직한 압축 파일도 28/28로 초과했고, 추출한 셀 중 자원 한도를 설정한 곳은 0/33이었으며 17건은 에이전트가 이미 읽은 목록 출력 이후에 터졌다. 압축을 풀고 테스트를 실행하라는 명시적 과업에서도 28/28이 예산을 넘었고, 다이제스트가 본문과 불일치하는 첨부를 저장하라는 과업에서는 27/28이 그대로 썼다. 네트워크·이미지 캐리어에서는 에이전트가 선언된 파일명으로 본문을 자발적으로 저장했고, 그런 쓰기 131건 전부가 본문이 모델 컨텍스트에 들어온 뒤에 일어났다. 모델별 주의력은 캐리어마다 따로 놀았다. Gemini-4-Argon은 압축 파일을 읽기 전용으로 먼저 훑는 빈도가 가장 높아 최저 수준의 위반률을 기록했지만, 네트워크·이미지 캐리어에서는 충돌 쓰기가 가장 많았다.
방어는 처리 프리미티브에 둔다. Step-Zero Safe Protocol(SZSP)은 읽기 전용 열기(peek), 호출이 실제로 생성한 바이트를 세는 상한 추출, 스테이징 후 커밋을 하나의 열기 인터페이스로 묶는다. 모델 없는 비교에서 같은 캐리어도 프리미티브에 따라 즉각 실현되거나 지연된다. unzip은 50회 중 0회만 예산을 지켰고 최대 512.0 MiB를 썼으며, bsdtar는 20/50을 지켰다. 파일당 4 MiB ulimit은 초과 멤버를 잘라 총량이 예산을 살짝 넘기고(피크 4.0 MiB) 잘린 파일을 남겼고, 워크스페이스 쿼터를 흉내낸 워치독은 예산 0.5 MiB 이내에서 멈췄지만(중앙값 4.3, 피크 4.5 MiB) 50/50에서 부분 트리를 남겼다. 선언된 로컬 헤더 크기를 믿는 헤더 게이트는 30/50, Python zipfile은 45/50이었다. SZSP는 정렬된 캐리어, 데이터 디스크립터 변형, 64·512 MiB 스케일 변형까지 전부 막았다. 실사용 에이전트에서 unzip 경로에 SZSP 프런트엔드를 설치하자 위반이 0/28로 줄었고(비게이트 28/28, McNemar p<0.001), 트윈은 27/28에서 완전히 추출됐다. 다만 안전한 도구를 셸 옆에 함께 제공하기만 하면 에이전트는 5/28에서만 그 도구를 호출하고 28/28에서 원시 추출기를 돌려 예산을 넘겼다. 유일한 실사용 위반은 한 하네스가 시스템 unzip을 절대 경로로 호출해 프런트엔드를 우회한 경우로, 게이트는 하네스 자체 도구 계층에 있어야 한다는 경계를 보여준다. 과업 완수율은 SZSP 사용 시 25/28, 미사용 시 23/28이었고 차이는 7포인트, 클러스터 구간은 [-14, 29]였다. 오버헤드는 압축 바이트를 한 번 더 훑는 비용으로 트윈 추출 중앙값 58 ms(unzip 6 ms), peek 51 ms다.
개발자에게 이 논문이 주는 실무적 의미는 분명하다. 파일 경로나 URL을 에이전트에 넘기는 순간 그 입력의 크기를 모르는 상태에서 기본 동작이 결정되므로, 하네스가 제공하는 도구 계층에 읽기 전용 미리보기와 실제 생성 바이트를 세는 상한, 스테이징 커밋을 기본값으로 깔아야 한다. 주의할 점도 있다. 네트워크 캐리어에서 SZSP의 다이제스트 판정은 조언에 그쳤고, MISMATCH 판정이 컨텍스트에 있는데도 에이전트가 두 다이제스트를 다시 계산하고 '교체된 페이로드'라고 부르면서 선언된 파일명으로 저장한 사례가 1/28 있었다. 무시 가능한 판정은 힌트일 뿐이므로, 다이제스트가 맞을 때만 쓰기를 커밋하는 바인딩은 도구가 강제해야 한다. 또한 게이트는 호출 하나 단위의 보장이므로 호출을 가로지르는 누적 원장과 inode 상한은 배포 단계의 몫으로 남는다.
저자들이 밝힌 한계는 다음과 같다. 주요 비율은 조건당 픽스처 하나에서 나왔고, 압축 결과는 독립적으로 만든 두 아카이브에서 반복됐지만 네트워크·이미지 결과는 각각 하나의 픽스처에 기반한다. 모든 요청이 프로토콜을 변환하는 단일 게이트웨이를 거치고, 사례 연구 절제는 6개 모델 중 4개만 사용했으며, 42개 조합 중 2개는 원래 서로 네이티브 조합이다. 4 MiB 예산은 측정 임계값으로 1~8 MiB 어느 예산에서도 같은 셀이 위반했다. 64 MiB 멤버에서는 27/28이 위반해 중앙값 64 MiB, 최대 384 MiB를 썼고 512 MiB 멤버에서는 7/7이 중앙값 512 MiB, 최대 1024 MiB를 썼다. 게이트웨이가 이미지 픽셀을 텍스트 공지 하나로 대체하므로 픽셀 수준 지각은 향후 과제다. 모델은 폐쇄 API로 서빙되어 기록된 트래픽과 디스크 기록으로 행동을 특성화했고, 강제되는 쓰기 바인딩·누적 바이트 원장·inode 상한·기타 포맷은 배포와 향후 연구로 남겼다.