지난 글까지는 가드를 어디에 세우는가를 다뤘다. 입력에서 무엇을 거르고, 출력에서 무엇을 검사하고, 개인정보를 어느 지점에서 지우는지였다. 세 글의 공통 전제는 "가드를 잘 설계하면 그 자리는 끝난다"였는데, 탈옥에 관해서는 이 전제가 성립하지 않는다.
탈옥(jailbreak)은 모델이 따르기로 되어 있는 정책을 우회해 원래는 거절했을 응답을 끌어내는 시도를 말한다. 어떤 패턴이 왜 통하는지는 탈옥 공격의 유형에서 다뤘으니 여기서는 그다음 이야기를 한다. 패턴을 다 알고 방어를 다 붙여 놓아도, 그 방어는 몇 주 뒤에 낡는다. 이 글은 그 낡음을 어떻게 관리하느냐에 관한 것이다.
낡는 방식이 특이하다. 코드는 그대로고 모델도 그대로인데 방어만 낡는다. 우리가 아무것도 바꾸지 않아도 바깥에서 새 표현이 계속 생기기 때문이다. 그래서 이 자리에서는 "무엇을 붙였는가"보다 "붙인 것을 어떻게 갱신하는가"가 실력이 된다. 왜 그런지를 먼저 짚고, 이어지는 여섯 절이 그 갱신을 돌리는 한 바퀴 — 관측, 재현, 심각도, 패치, 고정, 지표 — 를 순서대로 따라간다.
비대칭
이 문제가 다른 가드레일 문제와 다른 이유는 전부 한 단어로 설명된다. 공격 쪽과 방어 쪽이 치르는 비용이 대칭이 아니다.
변형 비용
공격자는 통하는 프롬프트 하나를 찾으면 되고, 그것을 변형하는 비용이 거의 0이다. 문장 하나를 바꾸고, 언어를 바꾸고, 역할을 한 겹 더 씌우면 새 변형이 나온다. 스무 개를 만들어 던지고 그중 하나만 통과하면 성공이다.
방어 쪽은 정반대다. 스무 개 전부를 막아야 하고, 동시에 정상 사용자를 막으면 안 된다. 이 차이는 확률로 쓰면 더 분명해진다. 변형 하나를 막을 확률이 0.9로 꽤 높다고 해도, 서로 독립인 변형 스무 개를 전부 막을 확률은 이렇게 된다.
열에 아홉을 막는 방어가 스무 번 시도 앞에서는 여덟 번 중 일곱 번 뚫린다. 공격자는 "하나라도 되면"이고 방어자는 "전부 되어야"이기 때문에, 개별 검사기의 정확도를 조금 올리는 방식으로는 이 격차를 못 따라잡는다.
게다가 성공은 복제된다. 한 사람이 찾은 우회는 게시물 하나로 퍼지고, 그때부터는 그 우회를 찾을 실력이 없는 사람도 그대로 쓸 수 있다. 발견 비용은 한 번만 지불되고 사용 비용은 계속 0이다.
조임의 대가
그렇다고 방어를 세게 조이면 되는 것도 아니다. 조일 때마다 정상 요청의 거절률이 같이 오른다. 폭발물 제조를 막으려고 "폭발"이라는 낱말이 든 요청을 전부 거르면, 화학 교사와 산업안전 담당자와 소설가의 요청이 함께 막힌다. 임계값을 0.7에서 0.5로 내리면 잡히는 탈옥이 늘지만 오탐도 같이 늘고, 그 오탐은 조용히 이탈로 나타난다. 막힌 사용자는 항의하지 않고 그냥 안 쓴다.
그래서 "탈옥을 완전히 막는 설정"은 대개 제품이 못 쓰게 되는 설정과 같은 지점에 있다. 극단을 보면 분명하다. 모든 요청을 거절하는 서비스는 탈옥률이 0이다. 이 사실이 농담이 아닌 이유는, 조이는 방향으로만 몇 달을 굴리면 실제로 그 지점 쪽으로 조금씩 밀려가기 때문이다.
세 개의 시간
이 비대칭에서 나오는 결론이 하나 있다. 완전히 막는 것을 목표로 두면 목표를 못 세운다. 달성했는지 확인할 방법이 없는 목표라서다. "이번 분기에 탈옥을 다 막았다"는 문장은 검증할 수도 반증할 수도 없다.
대신 목표를 시간으로 다시 세운다.
- 뚫렸을 때 얼마나 빨리 아는가
- 알고 나서 얼마나 빨리 고치는가
- 고친 것이 다시 뚫리지 않게 고정되는가
셋 다 시간에 관한 질문이지 완전성에 관한 질문이 아니다. 그리고 셋 다 잴 수 있다. 지난달 사례의 탐지 시간이 나흘이고 이번 달이 여섯 시간이면 나아진 것이고, 그 숫자는 누구와도 다툴 여지가 없다. 탈옥 방어를 기능 목록이 아니라 돌아가는 고리로 다뤄야 하는 이유가 여기 있다.
관측
고리의 첫 칸이자 가장 자주 비어 있는 칸이다. 방어는 붙여 놨는데 뚫렸을 때 아무 데도 표시가 안 난다.
원인은 로그의 비대칭이다. 막힌 요청은 가드가 남기니까 로그에 있고, 통과한 요청은 그냥 정상 트래픽으로 섞여 들어간다. 그래서 대시보드에는 실패한 공격만 쌓이고 성공한 공격은 한 줄도 안 남는다. 차단 건수를 보며 "잘 막고 있다"고 말하는 팀이 실은 성공 사례를 한 번도 본 적이 없는 팀인 경우가 흔하다.
네 신호
실무에서 쓰는 신호는 대략 넷이고, 비용과 잡는 것이 다르다.
| 신호 | 어디서 오는가 | 잡는 것 | 한계 |
|---|---|---|---|
| 가드 거절 로그 | 입력·출력 가드 | 시도의 양과 형태 | 통과한 것은 안 보인다 |
| 사용자 신고 | 제품 안의 신고 버튼 | 실제로 뚫린 사례 | 느리고 양이 적다 |
| 표본 재검사 | 통과한 응답 중 일부를 사후에 다시 검사 | 통과한 것 중 문제 | 표본 밖은 못 본다 |
| 계정·세션 집계 | 요청 로그의 묶음 통계 | 탐색 패턴 | 사후에만 보인다 |
넷을 다 두는 이유는 서로의 사각을 덮기 때문이다. 거절 로그는 실시간이지만 성공을 못 보고, 신고는 성공만 보지만 며칠이 걸리고, 표본은 성공을 보지만 표본 밖은 못 보고, 집계는 개별 요청이 아니라 흐름을 본다. 하나만 골라 두면 그 신호가 못 보는 자리가 통째로 사각이 된다.
표본 재검사
셋째 줄이 가장 많이 빠진다. 통과한 응답의 일부를 비동기로 다시 검사하는 경로를 만들어 두지 않으면 "우리 서비스에서 탈옥이 성공한 적이 있는가"라는 질문에 영원히 답할 수 없다. 응답을 이미 사용자에게 보낸 뒤에 도는 경로라 지연 예산에서 자유롭고, 실시간에서는 비싸서 못 돌리는 큰 분류기나 사람 검토까지 여기서는 붙일 수 있다.
표본 비율은 트래픽 규모에 따라 0.5~5% 사이에서 잡는다. 이 숫자가 무엇을 할 수 있고 무엇을 못 하는지는 한 번 따라가 보면 분명해진다. 하루 10만 건에 1%면 1,000건을 다시 검사한다. 성공한 탈옥이 전체의 0.1%, 즉 하루 100건이라면 그중 표본에 걸리는 것은 평균 한 건이다. 표본 재검사는 개별 사건을 잡는 장치가 아니라 비율을 재는 장치다. 어제 0.1%였던 것이 오늘 0.4%가 되었다는 사실을 하루 안에 알려 주는 것이 이 경로의 값이고, 개별 사건을 집어내는 일은 신고와 계정 집계가 맡는다.
표본을 고를 때 완전히 무작위로만 뽑을 필요는 없다. 신고가 들어온 계정, 거절을 여러 번 받은 세션, 새로 만든 계정처럼 사전 확률이 높은 쪽은 표본과 무관하게 전수로 돌린다. 무작위 표본은 비율을 재고, 가중 표본은 사건을 잡는다. 둘을 한 경로에서 같이 돌리되 집계는 갈라야 한다 — 섞으면 위에서 잰 비율이 부풀려진다.
계정 단위 집계
넷째 줄이 왜 필요한지는 그림 하나로 충분하다.
탐색은 한 요청 안에 들어 있지 않고 요청들 사이에 있다. 같은 계정이 짧은 시간 안에 거의 같은 요청을 조금씩 바꿔 가며 보내는 패턴은, 요청 하나만 보는 검사기로는 절대 안 보인다. 각각은 전부 정상 범위 안에 있기 때문이다.
계정·세션·IP 단위로 시간 창을 잡고 세 가지만 세도 대부분 잡힌다. 창 안의 시도 횟수, 거절률, 그리고 연속 요청 사이의 문자열 유사도다. 마지막 것이 핵심이다. 열 번을 물어도 매번 다른 주제면 그냥 활발한 사용자이고, 열 번이 전부 한 글자씩만 다르면 그건 탐색이다.
주의할 것이 하나 있다. 이 신호는 차단이 아니라 조사 큐로 보낸다. 정상 사용자도 원하는 답이 안 나오면 같은 질문을 조금씩 고쳐 다시 묻고, 그 행동은 탐색과 통계적으로 구별되지 않는다. 여기서 바로 차단하면 가장 열심히 쓰는 사용자를 끊는 규칙이 된다.
원문 보관
여기서 개인정보 마스킹의 원칙과 충돌이 하나 생긴다. 재현하려면 요청 원문이 있어야 하는데, 원문 보관은 개인정보 정책이 가장 싫어하는 것이다.
실무에서 쓰는 절충은 이렇다. 가드에 걸린 요청과 표본 재검사에서 문제로 나온 요청만 짧은 보관 기간으로 따로 저장한다. 보통 7~30일이고, 그 저장소는 접근 권한과 감사 로그를 본 서비스와 별도로 둔다. 정상 트래픽 전체를 원문으로 쌓아 두는 것과는 완전히 다른 이야기다.
그리고 재현이 끝나면 원문을 지운다. 다음 절에서 만들 최소 재현 케이스는 사용자가 보낸 문장이 아니라 우리가 그 원리를 보고 다시 쓴 문장이어야 한다. 회귀 세트는 몇 년을 남는 자산인데, 거기에 실제 사용자의 원문이 들어가 있으면 그 세트 자체가 보관 기간 없는 개인정보 저장소가 된다.
재현
신고나 로그로 올라온 것은 대개 대화 스무 턴짜리 통짜 기록이다. 그대로는 아무것도 못 한다. 패치할 자리를 고를 수도, 고쳤는지 확인할 수도, 테스트로 남길 수도 없다. 재현 단계에서 하는 일은 그 기록을 다음 단계들이 기계적으로 소화할 수 있는 모양으로 바꾸는 것이다.
최소 재현 케이스
최소 재현 케이스는 문제의 응답을 끌어내는 데 실제로 필요했던 최소한의 요청을 말한다. 스무 턴 중 열여덟 턴은 대개 아무 역할이 없다.
줄이는 방법은 단순하다. 턴을 절반씩 잘라 가며 여전히 재현되는지 본다. 앞 열 턴을 지우고 재현되면 그 열 턴은 필요 없었던 것이고, 안 되면 되돌려 뒤쪽을 자른다. 스무 턴이면 네다섯 번 만에 끝나고, 대개 두세 턴이 남는다. 그 두세 턴을 나란히 놓고 보면 무엇이 통했는지가 눈에 보인다 — 이 단계는 사실상 진단이다.
한 가지 함정이 있다. 한 번 안 나왔다고 재현 실패로 치지 않는다. 온도가 0이 아닌 이상 같은 입력이 매번 같은 응답을 내지 않으므로, 다섯 번 돌려 한 번 나오는 우회가 흔하다. 그래서 판정 기준을 "n번 중 k번"으로 미리 정해 두고 그 기준으로 자른다. 이 기준이 없으면 줄이는 도중에 우연히 안 나온 지점에서 잘못 멈추고, 필요했던 턴을 필요 없다고 지운 채로 다음 단계로 넘어간다.
변형군
줄인 다음에는 변형군으로 넓힌다. 같은 원리를 쓰는 다른 표현들을 만들어 함께 시험하는 것이다. 원본 하나만 막으면 정확히 그 하나만 막히기 때문이다.
- 같은 요청을 다른 언어로
- 같은 요청을 역할극 한 겹 안에서
- 같은 요청을 코드 주석이나 문서 인용 안에서
- 같은 요청을 인코딩·오탈자·띄어쓰기 변형으로
변형군은 두 가지 일을 한다. 첫째, 패치의 합격 기준이 된다. 원본만 막히고 변형 다섯이 그대로 통과하면 그 패치는 실패다. 이 기준이 없으면 "고쳤다"의 뜻이 사람마다 다르다. 둘째, 문제의 크기를 알려 준다. 변형 20개 중 하나만 통과했다면 우연에 가깝고, 12개가 통과했다면 그 층의 방어가 원리째 뚫린 것이다.
변형 생성
변형군을 만들 때 모델을 쓰면 빠르다. "이 요청과 같은 목적을 가지되 표현이 다른 문장 20개"를 만들게 하고, 그중 실제로 통과하는 것만 남긴다. 손으로 스무 개를 짜내는 것보다 훨씬 빠르고, 사람이 잘 못 만드는 방향 — 같은 뜻의 다른 언어 표현이나 어색한 우회 어법 — 까지 나온다.
이 작업은 성격상 정책 위반 요청을 대량으로 만드는 일이므로 자리를 갈라 둔다. 프로덕션 키를 쓰지 않고, 생성된 문장은 사례 저장소 안에만 두고, 누가 언제 무엇을 만들었는지 기록을 남긴다. 회귀 세트에 넣기 전에 사람이 한 번 읽는 절차도 필요하다 — 실제 조력이 되는 세부가 들어간 문장은 테스트 자산으로도 남기지 않는다.
사례 기록
사례 하나를 이런 형태로 남겨 두면 이후 단계가 전부 기계적으로 돌아간다.
id: jb-2026-0184
found_at: 2026-08-14
source: sampled_recheck # 신고가 아니라 사후 표본에서 나옴
technique: nested_roleplay # 분류 라벨
minimal_repro: 2 # 최소 재현 턴 수
variants_tested: 20
variants_passing: 6 # 패치 전
severity: high
patched_at: 2026-08-15
patch_layer: [system_policy, output_classifier]
regression_case: rt/jailbreak/nested_roleplay_0184
source와 found_at이 있으면 탐지 시간이 저절로 계산되고, patched_at이 있으면 패치 시간이 나오고, regression_case가 있으면 고정까지 갔는지가 확인된다. 뒤에 나올 지표 셋이 전부 이 파일에서 나온다. 인시던트마다 문서를 자유 서식으로 쓰면 그 셋 중 하나도 못 센다.
technique 라벨은 나중에 가장 큰 차이를 만든다. 몇 달 지나면 "어떤 기법이 우리에게 반복해서 통하는가"를 셀 수 있게 되고, 그 통계가 다음에 무엇을 고칠지를 알려 준다. 매번 새 기법으로 뚫리는 것과 같은 기법으로 반복해 뚫리는 것은 완전히 다른 문제이고, 뒤쪽이면 패치가 표면만 건드리고 있다는 뜻이다. 라벨 값은 처음부터 잘 지으려 하지 말고 열 개 남짓으로 시작해 겹치는 것을 합쳐 가며 다듬는다.
심각도
사례를 전부 같은 무게로 다루면 고리가 멎는다. 사소한 것까지 즉시 대응으로 올리면 대응하는 사람이 지치고, 실제 피해가 날 것을 주간 검토로 미루면 그 며칠이 사고가 된다. 그래서 재현이 끝난 사례에는 등급이 붙는다.
재진술과 조력
가르는 첫 선은 공개된 정보의 재진술과 실질적 조력 사이에 있다. 백과사전이나 교과서에 실려 있는 수준의 설명이 나온 것과, 실행에 필요한 단계·수량·조달처가 나온 것은 무게가 다르다. 앞쪽도 정책 위반이 맞지만, 검색 한 번으로 얻을 수 있는 것을 모델이 대신 말했다는 뜻이라 실제 피해의 증분이 작다.
이 선을 안 그으면 두 가지가 같이 나빠진다. 하나는 사소한 사례가 대응 큐를 채워 진짜가 묻히는 것이고, 다른 하나는 그 사소한 것들을 다 막으려고 정책 문구를 조여 정상 요청의 거절률이 오르는 것이다. 판정은 "이 응답이 없었으면 못 했을 일인가"를 묻는 쪽이 대체로 정확하다.
도구 실행
두 번째 선은 더 분명하다. 텍스트만 나온 것과 도구가 실제로 실행된 것은 등급이 다르다. 모델이 위험한 문장을 출력한 것은 사람이 읽고 거기서 멈출 수 있지만, 에이전트가 메일을 보내고 결제를 승인하고 파일을 지운 것은 되돌릴 수 없다.
그래서 도구를 쥐고 있는 구성에서는 심각도의 기준이 응답 내용이 아니라 권한이 된다. 같은 우회 문장이라도 읽기 전용 도구만 붙은 조립에서는 중간 등급이고, 쓰기 권한이 붙어 있으면 최고 등급이다. 그리고 이 자리에서만은 방어가 확실하다 — 텍스트 검사는 확률이지만 권한 축소는 결정론이다. 애초에 없는 권한은 어떤 문장으로도 못 부른다.
호출과 기한
심각도가 실제로 정하는 것은 둘뿐이다. 누구를 언제 부르는가, 그리고 언제까지 고치는가.
| 등급 | 해당하는 것 | 호출 | 패치 기한 |
|---|---|---|---|
| 높음 | 도구 실행까지 갔거나 실질적 조력이 나온 것 | 즉시 온콜 | 당일 |
| 중간 | 정책 위반 응답이지만 공개 정보의 재진술 | 근무 시간 내 | 며칠 |
| 낮음 | 톤·형식만 어긋난 것, 재현율이 매우 낮은 것 | 주간 검토 | 다음 주기 |
등급 정의보다 중요한 것은 낮음 칸을 실제로 쓰는 것이다. 전부 즉시 호출로 두면 몇 주 만에 아무도 안 본다. 새벽에 불려 나가 열어 봤더니 사소한 것이었던 경험이 서너 번 쌓이면 그다음부터는 알림이 무시되고, 그 시점에 이 고리는 멈춘 것이다.
패치
같은 사례라도 고칠 수 있는 자리가 여럿이고, 자리마다 비용과 부작용이 다르다. 어느 층을 고를지가 이 단계의 전부다.
다섯 층
| 고치는 자리 | 반영 속도 | 부작용 | 어울리는 경우 |
|---|---|---|---|
| 시스템 정책 문구 | 즉시 | 다른 정상 요청의 응답 톤까지 바뀜 | 경계가 애매해서 생긴 실패 |
| 입력 분류기 임계값 | 즉시 | 오탐 증가가 곧바로 따라옴 | 이미 잡히는데 문턱이 높았던 경우 |
| 출력 검사 규칙 | 즉시 | 스트리밍 지연 | 위험이 응답 쪽에 뚜렷한 경우 |
| 도구·권한 축소 | 배포 필요 | 기능 상실 | 실제 피해가 도구 실행에서 나는 경우 |
| 모델 교체·미세조정 | 며칠~몇 주 | 전면적 회귀 위험 | 같은 기법이 반복해 통하는 경우 |
층을 고르는 기준은 "어디서 막는 것이 가장 쉬운가"가 아니라 "그 실패가 어느 층의 빈틈에서 나왔는가"다. 정책 문구가 애매해서 모델이 헷갈린 것을 임계값으로 막으면, 임계값만 낮아지고 애매함은 그대로 남아 다른 표현에서 같은 실패가 다시 난다.
급한 불과 근본
위쪽 셋은 빠르고 아래쪽 둘은 느리다. 그래서 실무에서는 위쪽으로 급한 불을 끄고 아래쪽으로 근본을 고치는 두 단계로 간다.
문제는 급한 불을 끄고 나면 아래쪽을 안 하게 된다는 것이다. 대시보드에서 사건이 사라지고 알림이 멎으면 그 사례는 심리적으로 닫힌다. 이걸 막는 방법은 절차 하나뿐이다. 응급 패치와 근본 패치를 처음부터 티켓 두 장으로 끊는다. 응급 쪽은 당일에 닫고, 근본 쪽은 기한을 따로 받아 남긴다. 한 장으로 두면 앞쪽이 닫힐 때 뒤쪽이 같이 닫힌다.
거절률
패치할 때 반드시 같이 재는 것이 하나 있다. 정상 요청의 거절률이다. 임계값을 내리거나 정책 문구를 강하게 쓰면 탈옥은 줄지만 정상 요청도 같이 막힌다.
재려면 기준이 필요하다. 실제 트래픽에서 뽑아 사람이 "이건 응답해야 한다"고 확인해 둔 정상 요청 몇백 건을 골든 세트로 미리 만들어 둔다. 그리고 패치 전후로 이 세트를 돌려 거절률 변화를 함께 기록한다. 탈옥 변형 6개가 0개로 줄었는데 골든 세트 거절률이 2%에서 9%로 올랐다면, 그건 성공한 패치가 아니라 교환이고 그 교환을 받아들일지는 따로 판단할 일이다.
이 값을 안 재고 조이기만 하면 몇 달 뒤에 "우리 제품은 자꾸 안 된다고 한다"는 피드백으로 돌아온다. 그때는 어느 패치가 원인인지 되짚을 수 없다 — 조인 기록만 있고 대가의 기록이 없기 때문이다. 거절이 늘어나는 문제 자체를 어떻게 설계할지는 거절 설계에서 따로 다룬다.
반복 기법
인시던트 기록에 patch_layer를 남기고 몇 달 뒤에 세어 보면 한 가지가 보인다. 특정 기법이 상단 층 패치만으로 계속 되돌아오는 자리다. 같은 technique 라벨이 석 달 동안 다섯 번 올라왔고 그때마다 정책 문구나 임계값으로 막았다면, 그건 다섯 번 고친 것이 아니라 한 번도 안 고친 것이다.
그게 아래쪽을 손댈 때라는 신호다. 미세조정이나 모델 교체는 비싸고 위험해서 늘 미뤄지는데, 미룰지 말지를 감이 아니라 이 통계로 정하면 논의가 짧아진다. "이 기법으로 이번 분기에 다섯 번 뚫렸고 상단 패치의 평균 수명이 3주였다"는 문장은 그 자체로 결재 근거가 된다.
고정
패치는 되돌아온다. 다른 사람이 정책 문구를 정리하다가 한 줄을 지우고, 임계값을 지연 문제로 다시 올리고, 모델을 갈아 끼우면 옛날 우회가 되살아난다. 그래서 고리의 마지막 칸은 사례를 회귀 테스트에 넣는 것이다.
회귀 자산
최소 재현 케이스와 통과했던 변형들을 테스트 자산으로 저장한다. 저장할 때 입력만 넣으면 안 되고 판정 기준을 함께 넣어야 한다. "이 응답이 실패인가"를 사람이 매번 읽어야 하는 세트는 사례가 200건을 넘는 순간 아무도 안 돌린다.
판정을 문자열 매칭으로 두는 것은 대개 실패한다. 금지 문구 목록으로 검사하면 표현만 바꾼 같은 내용을 통과시키고, 반대로 거절문 안에 그 낱말이 들어 있으면 정상 거절을 실패로 잡는다. 판정 자체를 분류기나 루브릭 채점으로 두고, 그 판정기의 정확도도 따로 관리 대상에 넣는다. 세트를 어떻게 구성하고 무엇을 기준선으로 삼을지는 가드레일 테스트에서 자세히 다룬다.
배포 게이트
세트는 돌아야 뜻이 있고, 돌려면 배포 흐름 안에 박혀 있어야 한다.
- 배포 전에 세트를 돌려 통과율이 기준을 넘지 못하면 배포를 막는다
- 모델을 바꿀 때는 전수로 돌린다
- 정책 문구와 임계값은 코드와 같은 리뷰를 거치게 둔다
세트가 커지면 전수 실행이 몇십 분씩 걸리므로 두 층으로 나눈다. 매 배포에 도는 빠른 부분집합 — 심각도 높음 사례와 최근 사례 위주 — 과, 모델 교체나 주간 일정에만 도는 전수다. 전부 매번 돌리려다 느려지면 결국 게이트를 끄게 되고, 꺼진 게이트는 없는 게이트다.
세트의 성장
이 세트는 시간이 갈수록 값이 올라가는 유일한 산출물이다. 방어 규칙은 낡지만 "예전에 우리를 뚫었던 요청 목록"은 안 낡는다. 모델을 바꿔도, 프레임워크를 바꿔도, 팀이 바뀌어도 그 목록은 그대로 쓰인다.
그래서 세트의 크기와 증가 속도를 따로 본다. 두 달 동안 사례가 한 건도 안 늘었다면 우리가 안전해진 것이 아니라 관측이 멎은 것이다. 이 신호가 관측 절과 이어지면서 고리가 닫힌다 — 고정 칸의 정체는 거의 언제나 관측 칸의 고장이다.
지표
이 고리가 도는지 안 도는지는 지표로만 확인된다. 그런데 고르기 쉬운 지표일수록 방향이 틀렸다.
| 지표 | 재는 것 | 함정 |
|---|---|---|
| 차단 건수 | 가드가 일한 양 | 공격 시도가 늘어도, 가드가 오탐을 내도 같이 오른다 |
| 탐지까지 걸린 시간 | 관측의 실력 | 신고에만 의존하면 영원히 며칠 단위다 |
| 패치까지 걸린 시간 | 대응의 실력 | 급한 불만 끄면 짧아 보인다 |
| 회귀 세트 통과율 | 고정의 실력 | 세트가 안 늘면 100%가 계속 나온다 |
| 정상 요청 거절률 | 조인 대가 | 이 값을 안 보면 조이기만 한다 |
차단 건수
첫 줄만 대시보드에 올려 둔 팀이 많다. 세기 쉽고 그래프가 예쁘기 때문인데, 이 숫자는 올라도 나쁘고 내려도 나쁘다. 올랐다면 공격이 늘었거나 가드가 오탐을 내는 것이고, 내렸다면 공격이 줄었거나 가드가 놓치고 있는 것이다. 어느 쪽인지 이 숫자만으로는 절대 알 수 없다.
버릴 필요는 없다. 다만 위치를 바꾼다. 차단 건수는 결론을 주는 지표가 아니라 다른 지표를 들여다볼 때가 되었다고 알려 주는 알림에 가깝다. 어제의 두 배가 되었으면 그때 거절률과 표본 재검사 결과를 함께 열어 보면 된다.
탐지와 패치 시간
실제로 봐야 하는 것은 가운데 둘이다. 그리고 정의를 먼저 못 박아야 한다. 탐지 시간은 첫 성공 요청이 들어온 시각부터 우리가 그것을 안 시각까지이지, 티켓을 만든 시각부터가 아니다. 뒤쪽으로 재면 신고가 사흘 걸려 들어온 사건도 탐지 30분으로 기록된다.
이 정의로 재면 관측 설계가 숫자에 그대로 나온다. 신고에만 의존하는 팀은 며칠 단위에서 안 내려가고, 표본 재검사를 붙이면 시간 단위로 내려간다. 패치 시간은 앞 절의 티켓 두 장을 따라 둘로 나눠 센다 — 응급 패치까지의 시간과 근본 패치까지의 시간이다. 하나로 합쳐 재면 급한 불만 끄는 팀이 가장 빨라 보인다.
거절률과 세트 성장
나머지 둘은 짝으로 놓는다. 회귀 세트 통과율은 분모가 자랄 때만 뜻이 있는 값이다. 세트가 석 달째 200건 그대로인데 통과율 100%가 나오고 있다면 그 100%는 "안 뚫린다"가 아니라 "새 사례를 안 넣고 있다"를 뜻한다. 그래서 통과율과 세트 크기를 같은 화면에 붙여 둔다.
정상 요청 거절률은 다른 넷과 반대 방향으로 움직이는 값이라 반드시 같은 화면에 있어야 한다. 이것만 빼 놓으면 모든 지표가 "더 조여라"를 가리키고, 그 화면을 몇 달 보고 있으면 팀 전체가 그 방향으로만 움직인다. 다섯 줄을 나란히 두는 일 자체가 균형 장치다.
자동화의 경계
마지막으로 이 고리에서 자동화가 되는 칸과 안 되는 칸이 갈린다.
관측과 고정은 자동화된다. 표본 재검사, 계정 단위 집계, 회귀 세트 실행은 전부 기계가 한다. 재현도 절반은 자동화된다 — 턴을 줄이는 것과 변형을 만드는 것은 스크립트가 한다. 이 칸들을 사람에게 맡기면 사람이 바쁜 주에 고리가 통째로 멎는다.
심각도 판정과 패치 판단은 안 된다. 어느 층을 고칠지, 거절률을 얼마나 올려도 되는지, 이 사례가 실질적 조력인지 재진술인지는 제품 맥락을 아는 사람이 정해야 한다. 그래서 탈옥 대응은 온콜 대상에 넣되 호출 기준을 심각도로 나눈다. 자동화의 목표는 사람을 빼는 것이 아니라, 사람이 판단할 것만 사람 앞에 놓이게 하는 것이다.
정리하면 탈옥 방어에서 "다 막았다"는 상태는 없다. 있는 것은 고리가 도는 속도뿐이다. 통과한 것을 보는 경로를 만들고, 사례를 최소 재현 케이스와 변형군으로 바꾸고, 등급에 따라 부르고, 급한 층으로 불을 끄되 반복되는 기법은 아래층에서 고치고, 고친 것은 회귀 세트에 넣는다. 그리고 차단 건수 대신 시간과 거절률을 본다.
다음 글은 이 고리 옆에 나란히 서는 다른 판정 문제를 다룬다. 탈옥이 "정책을 우회하려는 시도"를 가리는 일이라면, 콘텐츠 조정은 "이 내용이 우리 서비스에서 허용되는가"를 가리는 일이고, 둘은 필요한 장치가 서로 다르다.
읽어주셔서 감사합니다. 😊

