실제 사용 중인 차단 목록(denylist) 1,709개를 측정한 결과, 대부분이 원래 막으려던 동작을 완전히 막지 못했습니다.
명령어 필터링은 서로 다른 두 방식으로 무너집니다. 하나는 matcher가 문자열을 읽은 뒤 bash가 텍스트를 다시 쓰는 경우입니다. r''m, rm$IFS-rf$IFS/, $(echo rm) -rf /는 정규식을 통과하지만 셸에서는 rm으로 실행됩니다. 이를 셸 확장(expansion) 문제라고 합니다. 또 하나는 matcher가 이미 신뢰하는 바이너리가 정상적인 이름과 기능으로 제한된 컨텍스트를 벗어나는 경우입니다. GTFOBins는 이런 바이너리를 정리한 목록입니다. 두 문제는 서로 독립적으로 발생하며, 코딩 에이전트와 프로덕션 호스트 사이의 matcher 하나만으로는 통제보다 추측에 가깝습니다. 이 글에서는 각 메커니즘과 실행 제어가 있어야 할 위치를 살펴봅니다. 명령이 입력되기 전에 접근 범위를 제한하는 이 시리즈의 방식을 적대적(adversarial) 입력 관점에서 설명합니다.
규칙 자체는 맞는데도 명령어 차단 목록(denylist)이 뚫리는 이유
요약: 현장에서 수집한 차단 목록 1,709개를 분석한 결과, 동작 종류에 따라 69.0~98.6%가 원래 막으려던 명령을 완전히 막지 못했습니다. 원인은 규칙이 허술해서가 아니라 셸과 OS가 작동하는 방식 자체에 있습니다.
2026년 6월 발표된 오하이오 주립대 논문 "One Goal, Many Commands"는 공개 저장소의 규칙 13,332개로 이뤄진 실제 차단 목록 1,709개를 평가했습니다. 동작 종류에 따라 69.0%에서 98.6%가 원래 막으려던 동작을 완전히 막지 못했고, 논문의 기준 호스트에서는 파일 읽기 동작만 완전히 막으려 해도 평균 217개의 추가 명령이 필요했습니다. 연구진은 차단 목록이 핵심 역할을 맡고 있지만 그 자체로는 취약하므로 다른 기법과 함께 써야 한다고 결론 내렸습니다.
규칙의 품질만으로 해결할 수 있는 문제가 아닙니다. GTFOBins에는 문서화된 정상 기능만으로 제한된 컨텍스트를 벗어날 수 있는 일반 유닉스 실행 파일 수백 개가 정리돼 있습니다. find -exec와 에디터 안에서 실행하는 :!sh가 예입니다. Matcher는 원문 텍스트를 검사하지만, bash는 실행 전에 텍스트를 확장하고 인용부호를 제거한 뒤 다시 씁니다. Adversa AI의 GuardFall 연구(2026년 6월)는 조사한 오픈소스 코딩 에이전트 11개 중 10개에서 에이전트와 셸 사이의 경계를 악용할 수 있음을 확인했습니다. 연구진의 표현대로 "lexing is not evaluation(어휘 분석은 실행 판단이 아니다)"입니다. 인용부호 제거, $IFS 치환, base64로 인코딩된 내용을 디코딩해 sh로 넘기는 방식은 모두 커널이 받을 내용을 보지 못하는 matcher를 우회합니다.
허용 목록(allowlist)을 쓰면 해결될까
요약: 아닙니다. 허용 목록은 반대쪽에서 뚫립니다. make, docker, go test를 허용하면, 에이전트가 파일까지 함께 수정할 수 있는 순간 임의 실행까지 허용하는 셈이 됩니다.
Claude Code를 대상으로 쓴 Formal의 벤더 리서치도 같은 지점을 짚습니다. 일부 bash 명령만 허용하는 것이 사실상 전체를 허용하는 것과 같아지는 경우가 많다는 겁니다. go build를 허용하면 -toolexec까지 허용하는 셈이고, find를 허용하면 -exec까지 허용하는 셈이며, make를 허용하면 에이전트가 직접 수정한 파일 자체가 공격 수단이 됩니다.
2026년에 나온 보안 권고 두 건이 이 패턴을 실제 제품에서 보여 줍니다. Cursor의 터미널 허용 목록은 우회할 수 있었습니다. 일부 셸 내장 명령이 허용 목록에 나타나지도 않은 채 Auto-Run으로 실행됐습니다(Cursor 2.3에서 수정). Pillar Security가 2026년 7월 잇달아 내놓은 조사 결과는 Codex CLI의 허용 목록이 읽기 전용이 아닌 호출도 이름만 보고 git show를 신뢰한 사례를 확인했습니다(v0.95.0에서 수정). 두 사례 모두 패치는 끝났지만, 문제의 유형 자체는 그대로입니다. 허용 목록은 명령의 이름을 신뢰할 뿐이고, 그 인자를 실제로 실행하는 것은 셸입니다.
그렇다면 패턴 매칭 자체가 쓸모없다는 뜻인가
요약: 그렇지 않습니다. 다만 판단을 마무리 짓는 계층으로 쓰기엔 적합하지 않습니다. 결정론적 규칙과 기준선(baseline)이 먼저 처리하고, 애매한 영역만 모델이 판단하고, 그래도 부족하면 사람에게 넘기며, 최종 판정은 실제로 실행되는 명령에 암호학적으로 묶입니다.
Alpacon의 파이프라인은 범위(scope)를 가장 먼저 확인합니다. 세션이 선언한 범위를 벗어난 동작은 어떤 규칙이나 모델에도 도달하기 전에 거부됩니다. 범위 안에서는 결정론적 규칙과 행동 기준선(behavioral baseline)이 대부분의 명령을 처리합니다. 여전히 애매한 일부만 모델로 넘어가고, 모델은 다시 사람에게 넘길 수 있습니다. 명백히 위험하다고 판정된 명령은 둘 중 어느 쪽도 기다리지 않고 즉시 거부됩니다.
범위를 먼저 확인하고 결정론적 규칙을 적용한 뒤, 애매한 경우에만 모델이 판단합니다. 범위 밖 동작은 비용이 큰 단계에 이르기 전에 거부됩니다. 오하이오 주립대 논문도 정적 차단 목록만으로는 핵심 계층 역할을 하기 어렵다고 결론 내리고, 능력 기반 샌드박싱(capability-based sandboxing)과 LLM 감사자(auditor)를 결합하는 방식을 제시합니다. 저희 구조와는 다른 통제입니다. 샌드박싱은 환경을 제한하고, 저희는 동작을 제한합니다. 이 논문은 문제 진단의 근거이며 저희의 계층 구성을 뒷받침하는 자료는 아닙니다.
GuardFall 조사에서 공격 표면을 가장 많이 막아낸 에이전트 하나는 방식이 달랐습니다. 실행 전에 확장(expansion)을 결정론적으로 해석해 낸 것입니다. 저희의 계층들도 그 방식으로는 이 확장 문제를 풀어내지는 못합니다. 문자열을 얼마나 결정론적으로 읽든, 모델이 얼마나 애써 판단하든, 그것은 셸이 실제로 실행할 내용을 읽는 것과는 다릅니다. 이 간극은 첫 다이어그램의 문자열 다섯 개 중 세 개, 앞서 짚은 확장 사례에서 그대로 드러납니다.
더 정교한 파서만으로는 해결할 수 없습니다. 난독화된 명령을 정확히 해독하지 못해도 유지되는 보장이 필요합니다. 가장 먼저 적용되는 보장은 범위(scope)입니다. 세션이 선언한 범위는 시도할 수 있는 동작의 상한이며, 범위 밖 명령은 해독 여부와 관계없이 거부됩니다. 그러나 세션이 정당하게 가진 범위 안의 난독화된 파괴 명령에는 이 상한이 도움이 되지 않습니다. 이 경우의 한계는 문자열 해석보다 판단의 품질에 있습니다.
판단은 플랫폼 쪽에서 중앙화되어 이뤄지고, 그 판정에는 서명이 붙습니다. Alpacon의 분석 서비스는 명령줄, 대상 호스트, 분석 시각을 함께 묶어 Ed25519 서명을 반환합니다. enforce 모드에서는 호스트에 있는 Alpacon 에이전트가 실행 전에 이 서명을 검증하고, 서명된 명령은 서명 대상 호스트에서만 유효합니다.
검증은 기록 모드와 거부 모드로 동작합니다. 서명 없이 들어온 명령을 기록만 할 수도 있고 실행을 거부할 수도 있습니다. 거부 모드에서는 실행되는 명령이 승인받은 바로 그 명령임을 증명할 수 있습니다. 필터를 통과한 유사 명령으로 바꿀 수 없고, 서명된 승인을 다른 호스트에서 재사용할 수도 없습니다. 서명은 사칭과 재사용(replay)을 막지만 셸 확장 문제를 해결하지는 않습니다.
Alpacon은 execve 단계에서 가로채지 않습니다. vi에서 빠져나온 셸이나, 그 자체로 서명된 스크립트가 만들어 낸 서브프로세스는 판정할 명령으로 다시 올라오지 않습니다. 그것은 커널 수준에서 가로채는 방식이고, Alpacon은 그렇게 하지 않습니다.
범위는 이 간극을 줄입니다. 처음부터 vi가 범위에 없는 세션은 에디터를 열 수 없으므로 셸로 빠져나갈 수도 없습니다. GTFOBins 문제는 세션에 실제로 필요한 바이너리로 좁혀집니다. 남은 바이너리에서 제한을 벗어나려 할 때 입력은 여전히 명령 문자열입니다. base64 | sh 같은 난독화나 세션이 선언한 목적과 맞지 않는 동작은 잡아낼 수 있습니다. 그러나 이런 난독화나 의도 불일치를 잘못 판단하면 위험한 명령이 승인됩니다. 승인 이후에 명령이 바뀌는 실패와는 다르며, 서명이 배제하는 것은 후자뿐입니다.
분류기가 틀렸을 때도 남는 것은 무엇인가
요약: 문자열 해석이 틀려도 유지되는 보장은 세 가지입니다. 동작이 따라야 할 선언된 범위, 요청 채널과 분리된 승인, 난독화와 관계없이 남는 귀속입니다.
Matcher는 언젠가 틀립니다. 그 가능성을 전제로 삼아야 합니다. 가질 만한 가치가 있는 계층은 matcher가 틀렸다고 해서 함께 무너지지 않는 계층입니다.
→ RBAC 위에 놓인 범위 상한. 세션은 시작할 때 무엇을 하려는지 선언하고, 역할 권한 위에 놓인 상한을 물려받습니다. 이 상한은 어떤 규칙이나 모델이 실행되기 전에 확인됩니다. 범위에 sudo가 없는 세션은 sudo 권한을 요청해도 거부되고, 이 거부는 명령을 제대로 해독했는지와 무관하게 성립합니다. 확인 대상이 문자열이 무엇처럼 보이는가가 아니라 그 세션이 무엇을 시도할 수 있는가이기 때문입니다. 이 상한은 세션에 묶입니다. 세션 밖에서 쓰이는 자격증명은 대신 귀속(attribution)이 담당합니다.
→ 아웃오브밴드(out-of-band) 승인, 에이전트가 명령을 입력하는 호스트 바깥에서. 권한이 있는 세션과 그 안에서 특정 명령에 대해 일어나는 에스컬레이션은 요청을 보낸 채널과는 다른 채널을 통해 사람 승인자에게 전달됩니다. 요청 채널은 그 자신이 승인 주체가 되지 않습니다. 여기서도 난독화는 아무 이득이 없습니다. 승인자가 보는 것은 파싱해야 할 문자열이 아니라 요청이기 때문입니다.
→ 속임수에도 살아남는 귀속. 명령과 파일 전송은 그것을 실행한 토큰에 귀속됩니다. 교묘하게 인코딩된 명령도 결국 특정 자격증명이 발행한 명령이고, 이 연결은 그 내용을 누가 이해했는지와 무관하게 유지됩니다.
이 보장은 비용 없이 자동으로 적용되지 않습니다. 여기서 언급한 범위 밖 동작 거부, 위험 명령 거부, 아웃오브밴드 승인, 서명 검증은 모두 기록 모드와 차단 모드로 제공됩니다. 특정 배포에서 판정을 기록만 할지 실제로 집행할지는 설정에 달려 있습니다. 기록 모드로 운영되는 계층은 실행을 막는 통제가 아니라 기록을 남기는 기능입니다. 저희를 포함한 벤더에게는 차단 기능의 존재보다 실제 운영 환경의 기본값이 무엇인지 물어야 합니다.
자주 묻는 질문
차단 목록을 지워야 할까요? 아니요. 비용이 적게 드는 결정론적 계층으로 남겨 두는 게 맞습니다. 빠르고, 감사하기 쉽고, 모델이 끼어들 필요도 없습니다. 기계가 발급한 명령에서 연산자나 체이닝 메타문자를 막는 것도 여전히 할 만한 일입니다. 문제는 이 계층을 마지막 계층으로 두는 것입니다.
명령 앞에 모델을 두면 전체가 느려지지 않나요? 모델이 모든 명령 앞에 있는 것은 아닙니다. 규칙과 기준선이 대부분의 명령을 처리합니다. 모델은 애매한 일부만 다루고, 그중 스스로 판단하지 못한 것만 사람에게 넘깁니다. 명백히 위험하다고 판정된 명령은 둘 다 거치지 않고 곧바로 거부됩니다.
에이전트가 입력한 문자열은 신뢰할 수 없는 입력입니다. AI-native PAM의 실행 제어는 문자열을 중앙에서 판단하고 그 판정을 실제 실행 명령에 묶습니다. 필터의 정확도만 높이는 방식과는 다릅니다.
