글만든 이유와 원리

AI가 rm -rf를 실행하기 직전에: Codex 삭제 방지 훅을 만들며 정한 것들

DELETE-DENIED는 Codex의 PreToolUse 훅으로 터미널 삭제 명령을 실행 직전에 검사합니다. 왜 이 방식을 골랐는지, 무엇을 막고 무엇은 막지 못하는지 정리했습니다.

터미널의 rm -rf 명령이 거부되는 화면

AI 코딩 에이전트에게 터미널을 맡기면 편합니다. 빌드도, 테스트도, 파일 정리도 알아서 합니다. 그런데 정리하던 임시 폴더 경로가 잘못 풀려 실제 홈 폴더를 통째로 지우는 일도 생길 수 있습니다. 2026년에는 코딩 에이전트가 사용자의 Mac 홈 폴더로 보이는 경로에 rm -rf를 실행했다는 사례가 공개적으로 보고되기도 했습니다.

DELETE-DENIED는 이런 사고를 막으려고 만든 작은 안전장치입니다. Codex가 터미널 명령을 실행하기 직전에, 중요한 상위 폴더를 지우려는 명령인지 검사합니다. 만들면서 가장 오래 고민한 건 어디에서, 어디까지 막을 것인가였습니다.

어디에서 막을까: 세 가지 후보

Codex에서 위험한 명령을 막는 방법은 여러 가지입니다.

  • 명령 규칙(rules): 특정 명령 형태를 금지하는 방식입니다. 하지만 $HOME, 상대 경로, .., 심볼릭 링크처럼 실제로 어디를 가리키는지 계산해야 하는 경우를 규칙만으로 판단하기 어렵습니다. find -delete, rsync --delete, 다른 셸 안에서 다시 셸을 부르는 명령도 일관되게 다루기 어렵습니다.
  • 권한·샌드박스를 좁히기: 확실하지만 정상적인 빌드, 패키지 설치, 프로젝트 밖 경로 작업까지 막습니다. Full Access로 쓰는 사람의 사용성을 크게 바꿉니다.
  • 실행 직전 훅(hook): Codex의 공식 기능인 PreToolUse 훅은 도구가 실행되기 직전에 검사 프로그램을 끼워 넣습니다. 명령을 실제 경로 기준으로 해석할 수 있고, 다른 작업은 그대로 둡니다.

사고가 주로 일어나는 곳이 Codex의 셸 명령이므로, 그 호출 직전의 한 지점을 검사하는 훅이 가장 작은 경계라고 판단했습니다. 규칙을 보조로 같이 두는 방법도 생각했지만, 두 정책의 우선순위를 함께 관리해야 하는 부담이 커서 훅 하나만 결정 지점으로 두었습니다.

명령 한 번이 지나가는 길

Codex가 Bash 도구를 부를 때마다 검사 프로그램이 한 번 실행됩니다.

  1. 입력을 읽습니다. 크기에 상한을 둡니다(전체 256KiB, 명령 64KiB). 예상보다 큰 값은 받지 않습니다.
  2. 빠른 사전 검사: 삭제와 관련된 명령이 없으면 설정 파일도 열지 않고 바로 통과합니다. 대부분의 명령이 여기서 끝납니다.
  3. 삭제 후보가 있으면 보호 경로 설정을 읽고, 대상 경로를 실제 위치로 풀어서 비교합니다. ~, .., 심볼릭 링크를 따라가고, 아직 없는 경로는 존재하는 가장 가까운 부모를 기준으로 봅니다.
  4. 보호 경로나 지금 작업 폴더의 상위 폴더를 지우려 하면 거부 응답을 돌려주도록 설계했습니다. 프로젝트 안의 구체적인 하위 폴더 정리는 허용합니다.
  5. 검사가 끝나면 프로세스도 종료됩니다.

보호하는 경로는 파일 시스템 루트, 사용자 폴더들의 상위 폴더, 홈 폴더, 문서·데스크톱·다운로드 같은 폴더, 그리고 지금 작업 중인 폴더의 모든 조상입니다. 마지막 항목이 중요합니다. 프로젝트 폴더에서 일하다가 rm -rf ..이 실행되는 경우를 막기 때문입니다.

검사기가 읽을 줄 아는 삭제 형태는 rm, rmdir, unlink, find -delete, xargs rm, rsync --delete, git clean, 그리고 이것들을 감싼 래퍼와 중첩된 셸입니다. POSIX 셸, PowerShell, Windows cmd, 제한된 Node.js·Python 한 줄 코드까지 해석합니다.

애매하면 막는다

명령을 완벽하게 해석할 수 없는 경우가 있습니다. 변수가 실행 시점에야 정해지거나, 백틱이나 eval로 명령을 만들어 실행하는 경우입니다. 이럴 때의 원칙을 정했습니다.

  • 재귀 삭제나 와일드카드가 있는데 대상을 알 수 없으면 거부합니다.
  • 입력 형식이 깨졌거나, 크기 제한을 넘었거나, 설정 파일에 오류가 있어도 거부합니다.
  • 반대로 $(...) 안이 단순한 읽기·진단 명령이면 통과시킵니다. 모든 치환을 막으면 평범한 명령까지 막히기 때문입니다.

안전장치가 너무 자주 막으면 사람들은 끄고 맙니다. 그래서 확실히 위험한 것과 해석할 수 없는 위험한 것만 막고, 나머지는 통과시키는 쪽으로 기준을 맞췄습니다.

매번 실행돼도 가벼워야 한다

훅은 삭제 명령뿐 아니라 모든 Bash 호출마다 실행됩니다. Codex의 훅은 명령 내용이 아니라 도구 이름(^Bash$)으로 걸리기 때문입니다. 그래서 속도가 중요했습니다.

  • 검사기는 Rust로 만든 단일 실행 파일이고, 다른 런타임(Python, Node 등)이나 네트워크를 쓰지 않습니다.
  • 호출 사이에 떠 있는 프로세스가 없습니다. 폴더를 계속 감시하지도 않습니다.
  • 관리 명령(설치, 상태 확인, 일시 중지)은 별도의 실행 파일로 분리해, 매 호출에 실리지 않게 했습니다.

설치할 때도 조심한다

안전장치를 설치하는 과정이 다른 설정을 망가뜨리면 안 됩니다.

  • 현재 사용자의 ~/.codex 아래에만 씁니다. 시스템 폴더는 건드리지 않습니다.
  • 기존 훅 설정 파일은 처음 한 번 백업하고, DELETE-DENIED 항목만 추가하거나 제거합니다.
  • 내려받은 릴리스는 SHA-256으로 확인합니다.
  • Codex는 훅을 실행하기 전에 사용자의 신뢰를 요구합니다. 설치기는 Codex app-server를 통해 정확히 이 훅 하나만 신뢰 목록에 올리고, 다시 조회해 확인합니다. app-server를 쓸 수 없으면 아무것도 쓰지 않고 멈춥니다.
  • 상태는 숨기지 않습니다. 설치만 되고 신뢰가 아직 없으면 “보호 중”이 아니라 “신뢰 대기 중”으로 표시합니다.

막지 못하는 것

이 부분을 가장 분명하게 적어 두고 싶었습니다. DELETE-DENIED는 OS 전체의 삭제 차단기가 아닙니다.

  • Codex의 Bash 훅으로 전달된 터미널 명령만 검사합니다. Codex가 다른 방식으로 파일을 바꾸거나, 파일 관리 앱을 직접 조작하거나, 원격 서버·클라우드·데이터베이스에서 작업하는 경우는 범위 밖입니다.
  • Codex 문서도 PreToolUse를 완전한 차단 경계가 아닌 안전장치로 설명합니다. 모든 셸 호출이 훅을 거친다고 보장되지 않습니다.
  • 검사한 직후 경로가 바뀌는 경쟁 상황은 막을 수 없습니다. 커널 수준의 잠금이 아니기 때문입니다.
  • 훅이 거부 응답을 돌려주는 것까지는 테스트로 확인했지만, 실제 Codex 앱이 그 응답을 받아 실행을 멈추는 전 과정은 아직 직접 검증하지 못했습니다. 저장소 문서에도 미확인으로 적어 두었습니다.
  • Claude Code는 아직 지원하지 않습니다.

그래서 이 도구를 설명할 때는 “모든 삭제를 막는다”가 아니라 “지원 범위 안에서 훅으로 전달된 Codex 터미널 명령의 중요 경로 삭제를 검사하도록 설계했다”고만 말합니다. 안전장치가 실제보다 크게 소개되면, 사용자는 백업처럼 정말 필요한 다른 대비를 소홀히 하게 됩니다.

써 보려면

Codex 메인 세션에 다음 문장을 그대로 보내면 Codex가 운영체제에 맞는 설치 명령을 실행합니다.

Codex에 보낼 문장
DELETE-DENIED를 설치해. README의 현재 OS용 한 줄 명령을 바로 실행해: https://github.com/Seokwoooo/DELETE-DENIED

직접 설치하는 명령, 잠시 끄고 다시 켜는 방법, 보호 범위의 자세한 목록은 프로젝트 페이지와 저장소 문서에 있습니다. 그리고 어떤 안전장치를 쓰든, 중요한 폴더는 따로 백업해 두는 것이 가장 확실한 대비입니다.

이 글에서 다룬 도구

DELETE-DENIED

중요한 폴더를 지우기 전에

자세히 보기

루샨 · lucianLabs 운영자

AI 에이전트와 일하며 필요한 도구를 직접 만들고, 그 과정에서 얻은 숫자와 시행착오를 기록합니다. 글에 틀린 내용이 있으면 알려주세요.