Post

AI Agent Security (Part 6): 처리량 워킹 노트 — 확립·닫힘·미해결

AI Agent Security (Part 6): 처리량 워킹 노트 — 확립·닫힘·미해결

AI Agent Security (Part 6): 처리량 워킹 노트 — 확립·닫힘·미해결

이 대회는 도구(웹 요청·파일 접근 등)를 쥔 AI 에이전트를, 프롬프트만으로 속여 비밀 값을 바깥 주소로 흘리게 만드는 red-team 문제다. 방어 장치를 통과해 유출이 성립하면 점수가 붙는다. Part 5에서 이 게임의 정체가 순수 처리량 싸움 — 정해진 계산 예산 안에서 유출을 몇 번 성립시키느냐 — 임을 확인했다. 이 글은 서사가 아니라 상태 점검이다. 지금까지 무엇이 확립됐고, 무엇이 닫혔고, 무엇이 진행 중이며, 무엇이 남았는지를, 각 항목마다 근거(소스 또는 측정)를 달아 정리한다. 배경 지식이 없어도 따라올 수 있도록, 필요한 개념은 그 자리에서 풀어 쓴다.

대회 링크: AI Agent Security — Multi-Step Tool Attacks

측정은 실제 서빙되는 모델·환경·방어 장치를 그대로 얹은 로컬 하네스에서 한다. 모델이 greedy로 디코딩(매 단계에서 확률이 가장 높은 토큰 하나만 고름)하므로, 같은 입력이면 서버와 노트북이 같은 토큰을 낸다. 그래서 “몇 토큰을 생성하는가, 유출에 성공하는가” 같은 행동은 오프라인에서 정확히 잰다. 다만 벽시계 시간은 하드웨어가 달라 그대로 옮겨오지 못한다.


0. 지금 위치

우리 엔진은 유출 한 건을 최대한 촘촘히 반복하는 형태다. 공개 상위권은 처리량이 우리보다 약 20% 높고, 며칠째 특정 구간에 몰려 더 오르지 않는다. 그들의 방법은 공개돼 있지 않다 — 공개된 최고 노트북조차 우리가 이미 넘어선 것들이다.

이 노트의 작업 가설은 하나다. 그 20%는 한 방의 발견이 아니라, 하나하나는 사소한 개선 여럿이 곱해진 결과라는 것. 아래는 전부 이 가설을 검증하는 범위 안에 있다.


1. 마스터 방정식과 두 개의 손잡이

유출 한 번은 정해진 점수를 준다. 그러니 점수는 유출 횟수 $N$에 비례한다. 유출은 정해진 계산 예산 안에서 반복되므로,

\[N \;\approx\; \frac{\text{쓸 수 있는 예산}}{\text{유출 한 번의 비용}}.\]

한 시간에 트랙을 몇 바퀴 도느냐를 생각하면 된다 — 시간 ÷ 한 바퀴에 걸리는 시간. 바꿀 수 있는 건 두 가지뿐이다. 한 바퀴를 빠르게 하거나(분모 줄이기), 급수대에서 버리는 시간을 줄이거나(분자 알뜰히 쓰기). 아래 모든 레버가 이 둘 중 하나다. 세 번째 축은 없다(§6).


2. 유출 한 번의 비용은 무엇인가 (첫 번째 정정)

분모를 공략하려면 그 비용이 무엇으로 이루어졌는지부터 알아야 한다.

자연스러운 걱정이 하나 있었다. 유출 한 번마다 모델에게 도구 설명서(약 1,200토큰)를 처음부터 다시 읽혀야 하고, 실제 서버는 노트북보다 한 번에 열 배쯤 느리니, 그 느림의 대부분이 이 재-읽기 고정비이며 손댈 수 없다는 것. 사실이라면 분모는 못 건드리고 게임은 여기서 끝난다.

측정이 이 걱정을 지운다. 모델 서버는 시도 사이에 상태를 버리지 않는다. 첫 시도에서 설명서를 한 번 읽고 나면 그 계산 결과가 캐시로 남아, 둘째·셋째 시도는 같은 부분을 다시 계산하지 않는다. 실제로 첫 시도의 첫 단계는 약 $1.9$초(설명서를 처음 읽는 콜드 비용)지만, 같은 프롬프트의 다음 시도는 약 $0.5$초다. 유출 한 번의 비용은 대부분 모델이 그때그때 새로 생성하는 토큰이고, 생성은 프롬프트가 통제한다.

여기서 하류의 결론들이 무너진다. “여기가 천장이다”, “레버는 숨겨져 있다” 같은 말은 전부 비용이 고정이라는 가정 위에 서 있었다. 비용이 생성-지배적이고 통제 가능하다면, 그건 결론이 아니라 가정의 그림자다. 문제는 §1의 두 손잡이로 환원된다. (Part 5는 이미 로컬에서 비용의 대부분이 생성임을 쟀다. 이 절이 더한 건, 서버가 느린 이유를 고정비로 돌리고 싶은 유혹을 실측으로 닫은 것뿐이다.)


3. 한 번의 시도로 여러 번 유출할 수 있나 (닫힘)

분모를 다르게 공략하는 길이 있다. 한 시도 안에서 유출을 여러 번 일으키면, 그 시도의 준비비용을 여러 유출이 나눠 낸다. 이건 준비비용이 클 때만 이득이다. 그런데 §2에서 준비비용(설명서 읽기)은 이미 캐시된다. 캐시가 켜져 있으면 나눠 낼 것이 없고, 추가 유출은 순수한 추가 생성일 뿐이다 — 공짜가 없다.

두 극단을 재서 확인한다. 캐시를 채(서버가 하는 대로면) 유출당 비용은 묶음 크기와 무관하게 평평하다(유출당 약 $0.95$초, 묶음이 1이든 6이든). 캐시를 끄면 묶을수록 싸진다(6개 묶음에서 유출당 약 $1.8$배 이득). 서버는 캐시를 켠 쪽이므로 실제 조건에서 묶기는 무이득이다. 실제 서버 실험도 같은 방향이었다 — 묶음을 키울수록 점수가 오히려 미세하게 내려갔는데, 이것이 “캐시 켜짐”의 서명이다(캐시가 꺼진 서버였다면 키울수록 올랐어야 한다). 닫힘.


4. 생성의 바닥, 그리고 그 안의 압축 가능한 조각 (확립)

유출하려면 모델이 도구 호출 — 목적지 주소와 비밀 값을 담은 짧은 구조 — 을 반드시 생성해야 한다. 이것이 분모의 하드 바닥이다. 지시문을 더 줄이려 하면 모델이 그만큼 마무리 말을 더 붙여 오히려 나빠진다. 두 모델 모두 이미 이 바닥에 앉아 있다(각각 약 29·32토큰).

그런데 “바닥”을 토큰 수 하나로만 보면 놓치는 게 있다. 도구 호출 안의 주소(URL)는 자유 문자열이고, 검사기는 그 주소에서 오직 호스트 이름만 본다. 유출 성립에는 비밀 값이 데이터 칸에 들어 있으면 그만이다. 그러니 주소는 세 조건 — 모델이 그대로 받아쓰고, 여전히 유출에 성공하며, 시도마다 서로 구별되는 — 을 지키는 가장 짧은 형태로 줄일 수 있다. 재보니 한 모델은 http://ab.co://ab로 써도 그대로 통과했고, 이것만으로 3토큰이 준다. 반드시 보내야 하는 메시지의 뼈대는 고정이어도, 그 안 한 칸은 자유 입력이라 압축되는 셈이다. 결정론적이고, 잃는 것이 없다.


5. 정직한 회계 — 레버가 아니라 스택 (진행 중)

상위권과의 처리량 격차를 항목으로 쪼개면 두 갈래다. 분모를 깎는 것들(주소 압축, 시도 라벨 압축)과 분자의 낭비를 없애는 것들(모델 종류를 가리려고 초반에 태우는 시도들, 실제보다 크게 잡아둔 안전 여유, 예산을 끝까지 밀지 않는 보수성). 하나하나는 처리량의 1~3%짜리다. 검증된 것만 정직하게 합하면 대략 +7% — 20%가 아니다.

그럼 나머지 격차는 어디에? 세 갈래로 설명되고, 어느 것도 숨은 배수가 아니다.

  • 변동성. 같은 코드라도 실행마다 결과가 몇 % 흔들린다(뽑히는 하드웨어 상태 탓). 우리가 기준으로 삼은 점수는 그 분포의 위쪽 값이라, 명목 격차를 실제보다 크게 보이게 한다.
  • 예산 절벽. 예산을 끝까지 미는 건 좋은 실행에서만 값을 하는데, 그 안전 지점이 로컬에서는 재현되지 않는다(로컬은 GPU 한 장이라 흔들림이 거의 없어 지나치게 낙관적이다).
  • 몰림 = 절벽 앞의 정체. 상위권이 좁은 구간에 여럿 쌓여 더 안 오르는 건, 숨은 배수가 아니라 다들 예산 절벽 바로 앞에 서 있다는 신호로 읽힌다. 한 눈금 더 밀면 일부 실행이 시간 초과로 통째로 0이 되는 그 경계.

정직한 답은 낭만적이지 않다. “숨은 사적 레버가 있다”보다, “같은 단발 엔진을, 좋은 실행에서, 절벽에 한 눈금 더 붙여 돌린 것”에 훨씬 가깝다. 틀렸을 수도 있지만, 소스와 측정이 실제로 받쳐주는 건 이쪽이다.


6. 닫힌 것 / 남은 것

닫힘(다시 쓰지 말 것). 유출 한 번의 점수 상한을 넘기는 두 번째 이벤트는 방어 장치의 taint 규칙이 잠근다(Part 5). 묶기는 무이득(§3). 지시문 축소는 바닥(§4). 하드웨어 — 두 GPU는 메모리를 나눠 쓸 뿐 속도를 겹쳐 내지 못하고, 그 설정에 우리 코드가 닿지도 못한다.

남음.두 모델 중 어느 쪽이 병목인가. 채점은 모델별로 따로 돌지만 공개되는 건 둘의 평균 하나뿐이라, 어느 행이 발목을 잡는지 안 보인다. 한쪽만 빠르게 하면 되는 상황일 수도 있다. ② 예산 절벽의 안전 지점 — 서버 실행만이 답한다. ③ 잔여 격차의 정확한 출처 — 위 세 갈래가 각각 몇 점씩인지.


7. 방법론 — 왜 로컬에서 재고, 묶어서 던지나

두 원칙이 이 노트를 지탱한다.

행동은 옮겨오고, 시간은 아니다. greedy 생성이라 토큰 수와 성공·실패는 서버·노트북이 같다. 그래서 모든 행동 주장(몇 토큰, 유출 성공, 시도마다 구별됨)은 비용 없이 결정론적으로 검증한다. 반면 점수 주장은 반드시 서버에서 대조한다 — 벽시계 시간은 옮겨오지 않으니까.

개별 이득은 변동성보다 작으니, 묶어서 던진다. 실행 간 흔들림이 몇 %인데 개선 하나가 1%라면, 그것만 던지면 노이즈에 묻혀 보이지 않는다. 그래서 각 개선을 노이즈 없는 로컬에서 하나씩 검증하고, 합이 흔들림을 넘는 스택을 한 번에 던진다. 지금 나가 있는 실험도 그렇게 짰다 — 어느 행이 병목인지 읽는 것 하나, 스택 없는 기준 하나, 그리고 예산을 세 단계로 미는 사다리. 숫자 하나로는 원인을 못 가리지만, 설계된 묶음의 숫자들은 가린다.


8. 요약

  1. 유출 한 번의 비용은 고정 간접비가 아니라 모델의 생성이 지배한다(설명서는 시도 간 캐시됨). 통제 가능하다는 뜻이다.
  2. 묶기는 서버에서 무이득으로 닫혔다 — 로컬 유추가 아니라 실측으로.
  3. 생성은 바닥이지만, 그 안 주소는 압축된다 — 자유 문자열이라 짧게 줄여도 유출은 그대로 성립한다.
  4. 검증된 길은 단일 레버가 아니라 작은 개선들의 스택(약 +7%)이고, 상위권까지의 잔여 격차는 변동성과 예산 절벽으로 가장 잘 설명된다 — 숨은 배수가 아니라.
  5. 시리즈를 관통하는 문장은 그대로다. 채점 함수는 애초에 제약이 아니었다. 제약은 늘 그 바깥에 있었다 — 에이전트가 자신을 반복하려는 의지, 방어 장치가 입력의 출처를 기억하는 것, 그리고 이번엔, 우리가 재보지 않고 사실로 적어둔 가정들.
This post is licensed under CC BY 4.0 by the author.