
rm -rf를 차단해도 AI의 운영 환경 사고는 막을 수 없다 ── hook에 '환경'을 판정하게 하기
요약
AI 에이전트에게 운영 환경 권한을 부여할 때, 단순 명령어 차단을 넘어 '대상 환경'을 판정하는 설계의 중요성을 다룹니다. hook을 활용해 명령어가 운영 환경을 향하는지 분석하고, 환경에 따라 실행 권한을 allow, ask, deny로 차등 부여하는 전략을 제안합니다.
핵심 포인트
- 단순 명령어 차단(rm -rf 등)은 운영 환경 사고를 막기에 불충분함
- hook을 사용하여 명령어가 타겟팅하는 환경(운영 vs 스테이징)을 동적으로 판정해야 함
- 환경과 작업 성격에 따라 실행 권한을 4단계(read, non-prod write, prod write, destructive)로 분류
- AWS_PROFILE, 엔드포인트, 호스트명 등을 통해 환경을 기계적으로 식별 가능
서론: rm -rf를 막아도 운영 환경은 지킬 수 없다
rm -rf를 차단하는 hook을 넣어 AI에게 운영 환경을 맡길 준비가 되었다고 생각했습니다. 하지만 그것은 착각이었습니다.
terraform destroy도, 데이터베이스에 대한 DELETE / DROP도, 스테이징(Staging) 환경이라면 일상적으로 실행하는 명령어입니다. 위험한 것은 명령어의 종류가 아니라, 그것이 운영 환경을 향하고 있는지 여부입니다. rm -rf를 막아도, 운영 환경을 향한 destroy는 그대로 통과됩니다. 그리고 까다로운 점은, 에이전트(Agent)가 자신이 지금 운영 환경을 건드리려 하는지, 스테이징인지 확신을 가지고 구분하지 않는다는 것입니다.
평소에는 AWS 상의 인프라 구축·운용과 프론트엔드/백엔드 개발을 담당하면서, 최근에는 사내 IT 기반 및 보안 관련 업무로 영역을 넓히고 있습니다. 다루는 범위가 넓은 만큼 운영 환경을 CLI로 다루는 상황도 많으며, 그 작업을 에이전트에게 맡기기 시작하면서 가장 먼저 부딪힌 문제가 이것이었습니다.
참고로 rm -rf나 sudo를 차단하는 기본적인 hook은 공식 문서의 block-rm.sh 샘플이나 Trail of Bits의 권장 설정 등 이미 좋은 글들이 많이 있습니다. 기본은 그쪽을 따르고, 이 글에서는 한 단계 더 앞선 ―― "운영 환경으로의 변경만 승인 단계로 올리는" 설계를 다룹니다.
왜 settings의 deny만으로는 부족한가
위험한 명령어를 차단하는 수단은 hook만이 아닙니다. settings.json의 permissions.deny에 패턴을 작성하는 방법도 있습니다. 하지만 이것은 문자열 패턴 매칭입니다. "terraform destroy를 포함하는 명령어를 거부"할 수는 있어도, "연결 대상이 운영 환경일 때만 거부"할 수는 없습니다. 환경 판정에는 로직이 필요하며, 정적인 패턴 지정으로는 작성할 수 없습니다.
hook은 외부 명령어로 임의의 코드를 실행할 수 있습니다. 환경 변수를 읽거나, 현재의 연결 대상 컨텍스트를 조사하거나, 인수를 분석하여 조건 분기를 하는 등의 판정이 가능합니다. 따라서 "환경을 보고 deny인지 ask인지 allow인지 결정하는" 역할은 hook이 담당하는 것이 자연스럽습니다.
또 하나, 실운용에서 효과적인 것은 발화 타이밍입니다. PreToolUse hook은 현재의 permission mode와 관계없이 동작합니다. --dangerously-skip-permissions(이른바 bypass 모드)로 확인 프롬프트를 생략하여 속도를 높이고 있더라도, hook이 반환하는 deny / ask는 유효합니다. read는 그대로 통과시켜 비운영 환경을 빠르게 돌리면서, 운영 환경으로의 변경만은 확실히 막는 운용이 가능해집니다.
설계: 환경을 판정하여 취급을 3가지로 분류하기
조작을 명령어의 종류와 대상 환경의 조합으로 분류합니다.
- read (목록·취득·상태 확인) → 어떤 환경에서도
allow. 부작용(Side effect)이 없으므로 자유롭게 실행하게 합니다. - 비운영 환경으로의 write (스테이징·로컬로의 변경) →
allow또는 가벼운ask. 개발 속도를 저해하지 않습니다. - 운영 환경으로의 write (생성·갱신·삭제·배포) →
ask. 인간에게 승인을 요구합니다. - 운영 환경의 파괴적 조작 (되돌릴 수 없는 삭제, 방어 계층의 무효화 등) →
deny. 애초에 실행시키지 않습니다.
핵심은 "운영 환경인지 아닌지"를 어떻게 기계적으로 판정하느냐입니다. 단서는 환경에 여러 가지가 있습니다.
AWS_PROFILE이나AWS_ACCESS_KEY_ID가 운영 계정을 가리키고 있는가- 연결 대상의 호스트명·DB 엔드포인트가 운영 환경의 것인가
- 베이스캠프(Bastion host)·VPN 등 운영 환경으로 가는 경로를 경유하는가
- 조작 대상 리소스의 명명 규칙 (운영 리소스에 일정한 prefix가 있다면 그것으로 판별 가능)
이것들은 문자열로서 관측할 수 있으므로, hook 내부에서 grep이나 조건 분기로 구현할 수 있습니다. 명명 규칙이나 profile 명을 기계적으로 판별할 수 있을수록 가드는 정확해집니다. 반대로, 운영 환경과 스테이징을 이름으로 구분할 수 없는 환경이라면 우선 그 부분부터 정비하는 것이 먼저입니다.
구현: 운영 환경 판정을 hook 내부에 넣기
판정은 모두 동일한 출력 규약(Contract)에 맞춥니다. 여러 개의 hook을 작성해도 동작을 예측할 수 있게 됩니다.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
...
hook 본체는 명령어를 분류하고, 운영 환경(production) 여부를 판정하여 계약된 대로 반환할 뿐입니다. 제품에 의존하지 않는 의사 코드(pseudo-code)로 작성하겠습니다.
#!/bin/bash
# PreToolUse hook (matcher: Bash)
# 위험한 서브 명령어를 "대상 환경"에 따라 deny / ask / allow로 분류함
...
read나 비운영 환경에서 exit 0을 사용하는 이유는, 의도적으로 명시적인 allow를 반환하지 않기 위해서입니다. allow를 반환하면 권한 시스템(permission system) 자체를 우회하게 되어, 별도로 설정한 rm -rf의 deny 규칙 등까지 무효화해 버립니다. 막고 싶은 것만 ask / deny를 반환하고, 대상 외에는 exit 0으로 일반적인 권한 처리에 맡기는 것이 안전합니다.
이를 settings.json의 PreToolUse에 matcher: "Bash"로 등록합니다.
{
"hooks": {
"PreToolUse": [
...
저 자신도 동일한 구조의 것을 자사의 액세스 제어(access control)나 방어 계열 인프라 관리 CLI에도 적용하고 있습니다. "대상이 운영 환경의 액세스 제어이므로 중단한다", "보호 대상 리소스이므로 거부한다"와 같이, 대상의 성격에 따라 판정하는 hook을 여러 개 실행하고 있습니다. 상세한 대상은 밝힐 수 없지만, 사고방식은 이 의사 코드와 같습니다.
deny가 아니라 ask에 비중을 두기
전부 deny로 설정하면 개발이 중단됩니다. 실운영에서 효과적인 것은 deny를 최소화하고 대부분을 ask로 돌리는 배분입니다.
deny는 "되돌릴 수 없는 운영 작업"에만 한정한다. 이곳은 인간이 승인하더라도 막아야 하는 최후의 보루다.- 운영 환경에 대한 변경 대부분은
ask로 설정하여, 인간이 내용을 보고 승인한다. - 비운영 환경은 기본적으로
allow로 설정하여, 에이전트의 속도를 살린다.
ask는 "실행을 중단하는 것"이 아니라 "실행 전에 내용을 보여주고 판단을 거치는" 메커니즘입니다. 승인 절차는 늘어나지만, 그 절차를 운영 환경에 대한 변경에만 집중시킬 수 있습니다. "모든 행을 읽는 것"이 아니라 "운영 환경을 향한 작업일 때만 눈을 들어 확인하는" 운영 방식으로 바뀝니다.
솔직한 한계: 이것은 벽이 아니라 가드레일입니다
마지막으로, 이러한 종류의 hook으로 지킬 수 없는 것도 적어두겠습니다. 도입을 검토한다면 이 부분을 알고 있어야 안전하게 사용할 수 있습니다.
PreToolUse hook이 보고 있는 것은 에이전트가 실행하려는 명령어 문자열입니다. 따라서 문자열이 변형되면 판정을 빠져나갈 수 있습니다.
- 에일리어스(alias)나 함수를 통해 명령어 이름이 바뀌는 경우
- 서브쉘(subshell)이나 변수 확장(variable expansion)으로 명령어를 조립하는 경우 (예:
$(echo des)troy와 같은 파편화) - 환경 변수를 바꿔서 운영 환경 판정을 속이는 경우 (
AWS_PROFILE을 일시적으로 다른 이름으로 설정하는 등)
단순한 정규 표현식 가드는 이러한 변형을 통해 뚫릴 수 있습니다. 즉, 이것은 악의적인 회피를 완전히 방지하는 벽이 아닙니다. 실수로 운영 환경을 망가뜨리는 사고를 구조적으로 줄여주는 가드레일입니다. Trail of Bits 또한 hook을 "보안 경계가 아니다 (프롬프트 인젝션(prompt injection)으로 회피될 수 있다)"라고 명시하고 있으며, 같은 위치에 있습니다.
회피 내성을 높이는 방향은 있습니다. 명령어를 정규화(normalize)한 뒤 판정하거나, 허용 목록(allowlist) 방식(기본 deny 후 허용 항목만 나열)으로 전환하거나, 운영 환경으로의 접속 자체를 별도의 권한 경계로 제한하는 방식 등입니다. 하지만 아무리 노력해도 hook 단독으로는 최종 방어선이 될 수 없습니다. 운영 권한의 분리나 감사 로그(audit log)와 결합하여 다층적으로 방어해야 합니다.
덧붙여, 이 기사에서 구체적인 제품명이나 운영 환경의 접속처를 적지 않은 것도 동일한 방어적 사고방식입니다. 방어 구성을 공개하면 그 자체로 공격 측의 정찰 자료가 됩니다. 노하우는 공유하되, 자신의 운영 환경 지도는 공개하지 않습니다.
마치며
rm -rf를 막는 것만이 목적이라면 공식 샘플로 충분합니다. 실운영에서 효과를 발휘하는 것은 그 한 단계 앞선 것 ── 같은 명령어라도 운영 환경을 향할 때만 승인 절차로 넘기는 판정을 hook에 갖추는 것입니다.
read는 자유롭게, 비운영 환경은 빠르게, 운영 환경의 변경은 승인으로, 되돌릴 수 없는 운영 작업은 거부로. 이 분류를 "대상 환경 판정"으로 구현하는 것만으로도, AI에게 운영 환경을 맡기는 두려움의 대부분을 구조적인 측면에서 낮출 수 있습니다. 완벽한 벽은 되지 못하더라도, 실수를 줄이는 가드레일로서는 충분히 작동할 것입니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기