본문으로 건너뛰기
메뉴
나에게 필요할까?에이전트가 내 컴퓨터에서 닿을 수 있는 것작동 방식에이전트와 시스템 사이에 놓이는 계층비교내장 샌드박스, Docker, VM설정작업에 필요한 것만 열기보안위협 모델과 한계다운로드바이너리, 체크섬, 릴리스 노트설치
보안

한계가 분명한 유용한 계층

ai-jail은 신뢰하는 에이전트의 실수를 막고 피해를 프로젝트 안에 가두도록 만들었습니다. 프로젝트 디렉터리가 영향 범위이고, 홈과 키, 네트워크는 그 바깥에 있습니다. 호스트와 커널을 공유하는 프로세스 샌드박스이므로 악의적인 코드나 작정한 공격자를 막는 경계는 아니고, 일회용 가상 머신을 대신하지도 못합니다.

먼저 승인하는 사람을 위해, 그다음 보안 엔지니어를 위해 썼습니다.

막으려고 만든 것

신뢰하는 에이전트도 명령을 잘못 치고, 프로젝트 루트 위로 올라가는 스크립트를 돌리고, 웹 페이지나 패키지에 넘어갑니다. 그다음에 에이전트가 찾으려 들지만 찾지 못하는 것들입니다.

↔ 옆으로 밀어 전체 다이어그램을 확인하세요

기본값입니다. 바깥에 있는 항목은 직접 열기 전까지 그대로 바깥에 있습니다.

닿을 수 없음

  • 홈 디렉터리와 그 안의 자격 증명. 에이전트는 새로 만든 빈 홈을 받으므로 그 안에는 ~/.ssh도 ~/.aws도 없습니다.
  • 셸에 있는 시크릿. 환경 변수는 작은 허용 목록에 있는 것만 남기므로 export한 토큰은 바깥에 남습니다. 유일한 예외는 실행한 하네스 자신의 API 키, 예를 들어 Claude의 ANTHROPIC_API_KEY로, 로그인과 함께 기본으로 복사됩니다. --no-agent-state로 막을 수 있습니다.
  • 네트워크. 닫혀 있고, 필터링 프록시를 지나는 예외가 둘뿐입니다. 툴체인이 켜져 있을 때(Linux의 기본값)의 짧은 패키지 레지스트리 목록, 그리고 알려진 에이전트라면 자기 API 호스트이며, 두 플랫폼 모두 해당합니다. 텔레메트리 호스트는 닫힌 채로 남습니다. --no-network는 둘 다 닫습니다.
  • 호스트 공유 메모리, 디스플레이 서버, 클립보드. 에이전트 출력은 터미널의 클립보드 시퀀스와 질의 시퀀스를 버리는 필터를 거칩니다.
  • 터미널에 입력 밀어 넣기. Linux에서는 키 입력을 위조하는 TIOCSTI 호출을 막고, macOS에서는 터미널 제어를 ai-jail이 그 실행용으로 만든 터미널 하나로 제한합니다.
  • 감옥 자체의 규칙. 저장소 안의 .ai-jail 파일은 신뢰하지 않습니다. 샌드박스를 더 조일 수만 있고 열 수는 없습니다.
계층별로 막는 방법

벽이 끝나는 곳

감옥과 내 시스템은 같은 커널 위에 서 있습니다. 그 커널이나 드라이버, 샌드박스 백엔드에 버그가 있으면 벽을 돌아가는 길이 생기고, ai-jail이 할 수 있는 일은 없습니다. 가상 머신은 자체 커널을 가져오기 때문에 더 강한 경계입니다.

↔ 옆으로 밀어 전체 다이어그램을 확인하세요

샌드박스는 지키려는 대상과 같은 바닥을 쓰고, VM은 자기 바닥을 가져옵니다.

내 데이터에 미치는 영향

  • 프로젝트 안의 시크릿. 저장소에 있는 .env 파일은 마스킹하지 않으면 읽을 수 있습니다. 파일 마스킹 방법 보기.
  • 마스킹은 샌드박스가 시작될 때 존재하는 경로에만 적용됩니다. 세션 도중에 나중에 생긴 시크릿 파일은 숨겨지지 않습니다.
  • 허용된 호스트는 통째로 신뢰됩니다. --allow-host로 github.com을 허용하면 에이전트는 내 저장소뿐 아니라 거기 있는 어느 저장소에든 푸시할 수 있습니다.
  • --network는 네트워크 전체를 열기 때문에, 에이전트가 읽을 수 있는 것은 내 프로젝트를 포함해 무엇이든 밖으로 보낼 수 있습니다.
  • ai-jail은 에이전트 자신의 로그인을 기본으로 읽기·쓰기 마운트하므로, 속아 넘어간 에이전트가 실행이 끝날 때까지 그 로그인을 쓸 수 있습니다. 프로젝트는 이것을 편의를 위한 맞교환으로 문서화하고 있고, --no-agent-state를 주면 로그아웃 상태로 실행됩니다.
  • 호스트 쪽 git은 프로젝트의 .git/을 신뢰하는데, 그곳은 쓸 수 있는 곳입니다. .git/config에 core.fsmonitor나 core.pager로 지정된 프로그램이나 .git/hooks/에 들어간 프로그램은, 다음에 호스트에서 해당 git 명령이 돌 때 샌드박스 밖에서 내 권한으로 실행됩니다. core.fsmonitor라면 평범한 git status만으로 충분하고, 에디터가 백그라운드에서 돌리는 것도 해당하며, git diff에는 아무것도 보이지 않습니다. --lockdown은 프로젝트를 읽기 전용으로 만들어 이를 막고, --map .git은 일반 실행에서도 저장소를 읽기 전용으로 유지하는 대신 안에서 커밋할 수 없게 됩니다. 뭔가 이상했던 세션 뒤에는 호스트에서 git을 돌리기 전에 일반 파일 도구로 .git/config를 읽고 .git/hooks를 살펴보세요. git config --list 자체가 설정된 페이저를 실행합니다.
  • --mask와 --deny-path는 샌드박스가 보는 것만 바꿉니다. 샌드박스 밖에서 에이전트를 대신해 움직이는 것은 여전히 실제 파일을 읽습니다. --docker 아래의 Docker 데몬, 매핑된 소켓으로 닿는 MCP 서버, --forward-port 뒤의 서비스가 그렇습니다.
보안 엔지니어를 위해
  • 커널과 드라이버 취약점. 샌드박스 안의 모든 프로세스는 컴퓨터의 나머지 부분과 같은 커널을 씁니다.
  • 터미널 에뮬레이터 버그. 출력 필터를 끄는 --terminal-passthrough를 쓸 때 특히 그렇습니다.
  • 샌드박스 백엔드 자체의 결함. Linux에서는 bubblewrap, Landlock, seccomp이고 macOS에서는 sandbox-exec입니다.
  • 커널 자체 규칙인 Landlock이 추상 Unix 소켓과 시그널에 울타리를 치는 것은 커널 6.12 이상에서만이고, 그것도 최선 노력입니다. 더 오래된 커널에는 파일시스템과 네트워크 규칙만 남으므로, --x11, --network, --systemd-user를 쓰면 호스트의 추상 소켓과 시그널에 그대로 닿을 수 있습니다.
  • 사이드 채널과 일부 유형의 프로세스 간 통신.
  • 필터링된 외부 연결은 TCP 전용입니다. UDP가 없어서 UDP가 필요한 프로그램은 동작하지 않고, 그 경로로 새어 나갈 수도 없습니다.
  • --allow-host에서의 DNS. Linux에서는 샌드박스 안의 이름 조회가 실패하므로 밖으로 나가는 DNS 채널이 없습니다. macOS에서는 시스템 리졸버에 울타리가 없어서 대역폭이 낮은 DNS 채널이 남습니다. Anthropic이 자체 sandbox-runtime에 대해 문서화한 것과 같은 빈틈입니다.
  • 프록시는 연결마다 호스트, 포트, 시각을 봅니다. 그 안을 오가는 내용은 양쪽 끝 사이에만 머뭅니다. 필터링된 외부 연결의 구조.
  • 팬텀 자격 증명은 신뢰를 ai-jail 자신에게 옮깁니다. --secret을 쓰면 묶어 둔 호스트로 가는 요청은 실제 키를 넣을 수 있도록 ai-jail을 평문으로 지나가고, 나갈 때 다시 암호화됩니다. 다른 호스트로 가는 터널은 여전히 열어 보지 않습니다. 그 호스트에 대해 프록시로 평문 HTTP를 보낼 수 있는 클라이언트만 해당합니다.
  • --github, --aws, --kube, --gcloud, --docker-config로 넘긴 자격 증명. 읽기 전용은 파일이 바뀌는 것을 막을 뿐 자격 증명이 쓰이는 것은 막지 못합니다. 샌드박스 안의 무엇이든 그 자격 증명이 유효한 동안 해당 서비스에서 나처럼 행동할 수 있습니다.
  • macOS의 sandbox-exec는 Apple이 deprecated로 지정했고 Linux의 격리와 동등하지 않습니다. macOS에서 다른 점 보기.

벽을 여는 스위치

아래 항목은 모두 직접 켜기 전까지 꺼져 있습니다. 예외는 기본으로 켜진 스위치 두 개, Linux에서의 --toolchains와 모든 플랫폼에서의 --agent-state입니다. 대부분은 켜면 에이전트가 실제 접근 권한을 얻습니다. 설정 방법은 설정 페이지에 있습니다.

모든 스위치와 영향을 보기
스위치켜면 샌드박스 안의 모든 것이 할 수 있는 일
--network읽을 수 있는 것을 전부 어디로든 보냅니다. 트래픽에 제한이 없습니다. 기본값은 알려진 에이전트 자신의 API 호스트와, Linux에서는 패키지 레지스트리만 허용하고, --allow-host가 더 조이는 대안입니다.
--allow-host나열한 호스트와 그 서브도메인에만, 내장 프록시를 거쳐 TCP로 닿습니다. 나열한 호스트는 각각 통째로 신뢰됩니다. 내 목록은 에이전트 자신의 API 호스트에 더해집니다. --no-network와 목록을 함께 쓰면 그 목록만 허용됩니다.
--forward-port호스트 루프백의 서비스 하나를 통째로 씁니다. 그 포트의 MCP 서버, 에디터 소켓, 데이터베이스가 받아 주는 것이면 무엇이든 에이전트가 요청할 수 있고, 허용 목록도 검사도 감사 기록도 없습니다. 그 밖의 호스트 포트는 모두 닫힌 채입니다. Linux 전용이고, --network와 함께 쓰면 거부됩니다.
--toolchainsLinux에서는 기본으로 켜짐. 내 툴체인이 안에서 그대로 동작합니다. ~/.cargo/bin과 ~/.rustup은 읽기 전용으로 마운트하고, 의존성 캐시는 실행 사이에 유지되는 감옥 전용 저장소 ~/.local/share/ai-jail/cache/에 두며, 패키지 레지스트리로의 필터링된 외부 연결을 허용합니다. 호스트의 실제 캐시는 절대 마운트하지 않습니다. --lockdown에서는 꺼지고, --no-toolchains로 끌 수 있습니다.
--secret새로 열리는 것은 없습니다. 그 키의 실제 값은 샌드박스에 들어가지 않습니다. 에이전트에는 플레이스홀더가 보이고, 프록시는 키를 묶어 둔 호스트 하나로 가는 평문 HTTP 요청에만 실제 값을 바꿔 넣습니다. ai-jail 자신의 프로세스는 그 요청을 평문으로 봅니다.
--audit-log새로 열리는 것은 없습니다. 이 스위치는 아무것도 약하게 하지 않습니다. 실행할 때마다 샌드박스 바깥에 있는 모드 0600 파일에 한 줄을 쓰므로 에이전트는 이 파일을 보지 못합니다.
--dockerDocker 데몬을 통해 내 컴퓨터에서 root처럼 행동합니다.
--kvm커널의 가상화 인터페이스인 /dev/kvm을 열어 QEMU, Firecracker, Android 에뮬레이터를 하드웨어 속도로 돌립니다. Linux 전용. 게스트는 샌드박스의 네트워크만 쓸 수 있고, /dev/net/tun은 넘기지 않습니다.
--x11키 입력을 기록하고 다른 X11 창의 스크린샷을 찍습니다.
--systemd-usersystemd 사용자 매니저에게 호스트에서 서비스를 실행해 달라고 요청합니다.
--inherit-env시크릿을 포함한 셸 환경 전체를 읽습니다. API 키라면 --env-from-file로 파일 하나만 넘기는 방법이 있습니다.
--agent-state기본으로 켜짐. Claude의 ~/.claude나 Devin의 ~/.local/share/devin과 ~/.config/devin처럼 에이전트에 저장된 로그인을 읽기·쓰기로 마운트하고, 셸에 설정된 API 키를 복사해서 로그인된 상태로 시작합니다. 샌드박스 안의 모든 것이 실행이 끝날 때까지 그 자격 증명을 쓸 수 있습니다. --no-agent-state를 주면 격리된 로그아웃 상태로 실행되고, --lockdown에서는 꺼집니다.
--githubGitHub에서 나처럼 행동합니다. gh 설정 디렉터리는 읽기 전용으로 마운트되고, 활성 github.com 토큰은 호스트의 시스템 키링에서 읽어 그 실행에 한해 GH_TOKEN으로 전달되며, 명령줄이나 디스크에는 두지 않습니다. 읽기 전용은 파일을 보호할 뿐이고, 토큰은 유효한 동안 그대로 쓸 수 있습니다.
--aws~/.aws로 AWS에서 나처럼 행동합니다. 읽기 전용으로 마운트됩니다.
--kube~/.kube에 있는 모든 클러스터에서 나처럼 행동합니다. 읽기 전용으로 마운트됩니다.
--gcloud~/.config/gcloud로 Google Cloud에서 나처럼 행동합니다. 읽기 전용으로 마운트됩니다.
--docker-config~/.docker/config.json에 있는 컨테이너 레지스트리 로그인을 사용합니다. 읽기 전용으로 마운트됩니다.
--audio켜져 있는 동안 오디오를 녹음하고 재생합니다.
--no-private-home실제 홈 디렉터리를 봅니다. 범위가 넓은 예외이니 필요한 경로 하나만 매핑하는 편이 낫습니다.

애매하면 시작을 거부합니다

요청한 것보다 약한 상태로 조용히 시작하는 샌드박스는 오류를 내고 멈추는 샌드박스보다 나쁩니다. 아래 경우에 ai-jail은 멈춥니다.

  • 설정 파일이 있는데 내용이 잘못되었거나 읽을 수 없는 경우.
  • 프로젝트 .ai-jail이 심볼릭 링크인 경우. 전역 ~/.ai-jail이 심볼릭 링크이면, 대상이 내 소유이고 다른 사람이 쓸 수 없으며 프로젝트 바깥에 있는 일반 파일일 때만 따라갑니다.
  • --lockdown인데 커널 안에서 파일시스템 규칙을 적용하는 Landlock이 없거나, 일부만 지원되거나, 적용에 실패한 경우. --lockdown --no-landlock도 거부됩니다.
  • --allow-tcp-port. 이 인터페이스는 UDP를 제한할 수 없어서, 호환성을 위해 플래그는 받되 실행은 실패하고 오류 메시지는 --allow-host를 가리킵니다.
  • --network와 --allow-host를 함께 쓰거나, --browser와 --allow-host를 함께 쓰는 경우. 서로 모순되므로 ai-jail이 실행을 거부합니다.
  • --forward-port를 --network와 함께 쓰는 경우(호스트 루프백에 이미 닿을 수 있으므로), Linux 밖에서 쓰는 경우, 그리고 샌드박스가 바인드할 수 없는 포트 0이나 1024 미만 포트를 주는 경우. 전역 파일 기본 테이블의 forward_ports 항목은 모든 브라우저 실행과 모든 macOS 실행에서 앞의 두 조건에 걸립니다.
  • --env-from-file 파일이 심볼릭 링크이거나, 그룹이나 모두가 읽을 수 있거나, 다른 사람 소유이거나, 프로젝트 안에 있는 경우.
  • BWRAP_BIN이 root가 아닌 누군가가 바꿔치기할 수 있었던 bubblewrap 바이너리를 가리키는 경우. 경고와 함께 무시되고, 신뢰할 수 있는 bubblewrap이 없으면 실행이 실패합니다.
  • 오버레이를 준비할 수 없는 경우. 원본이 없거나, 대상이 다른 매핑과 겹치거나, 저장소 생성에 실패한 경우입니다. 2.4부터는 실제 파일을 그대로 읽기·쓰기로 바인드하는 방식으로 물러서지 않고 시작을 중단합니다.

프로젝트를 검증하는 방법

메인테이너 한 명이 프로젝트를 공개적으로 운영합니다. 무엇을 검증하고 무엇을 검증하지 못하는지 아래에 적었습니다.

23
샌드박스 탈출 통합 테스트. /usr에 쓰기, ~/.ssh 읽기, ptrace와 bpf 호출을 시도하고 실패해야 통과합니다
959
버전 2.9.0 소스 트리에 있는 단위 테스트
0
실행 시 네트워크 요청 수. 업데이트 확인은 --update-check로 켜는 옵트인입니다

최근 보안 수정

  • 2.9.0: --x11만 줘도 X11이 통과합니다. 전에는 --display가 없으면 아무 말 없이 아무것도 하지 않았습니다.
  • 2.9.0: 자동 저장이 신뢰하지 않는 로드가 거부할 옵트인을 더는 프로젝트 파일에 쓰지 않으므로, 저장된 파일이 다음 실행에서 경고를 내지 않습니다.
  • 2.9.0: macOS: Codex가 setuid 호스트 ps 대신 전용 ps 심(shim)을 받아 Seatbelt 아래에서 다시 실행됩니다.
  • 2.8.0: macOS: seatbelt 프로파일이 Claude Code의 Keychain 로그인을 유지하고 Codex 제어 소켓을 허용해서, 둘 다 로그인된 상태로 시작합니다.
  • 2.8.0: 런처가 보내는 커서 위치 요청에 가상 터미널이 응답하므로 런처가 더는 멈추지 않습니다.
  • 2.6.2: --lockdown은 --network를 줘도 항상 네트워크 네임스페이스를 격리합니다. 전에는 이 모순된 조합에서 네임스페이스가 공유된 채로 남았습니다.
  • 2.6.2: --env와 --env-from-file의 값이 더는 런처의 명령줄에 놓이지 않습니다. 거기서는 내 계정으로 도는 어떤 프로세스든 읽을 수 있었습니다. 이제 메모리 안의 파일로 전달됩니다.
  • 2.4.0: 저장소의 worktree 메타데이터로 호스트의 임의 디렉터리를 읽기·쓰기로 바인드할 수 없게 되었습니다.
  • 2.4.0: 환경 변수 항목이 중복되어도 --secret 키의 실제 값이 샌드박스에 들어가지 않습니다.
  • 2.4.0: 신뢰하지 않는 프로젝트 .ai-jail의 브라우저 명령이 락다운을 해제하지 못합니다.
  • 2.4.0: TIOCSTI를 커널이 읽는 대로 32비트로 비교하므로, 상위 비트를 세운 변형이 seccomp를 통과하지 못합니다.
  • 2.4.0: 커널 6.12 이상에서는 Landlock이 추상 Unix 소켓과 시그널에 울타리를 칩니다.
  • 2.4.0: macOS: 터미널 접근이 이번 실행의 터미널로만 제한됩니다. /dev/ttys* 전체가 아닙니다.
  • 2.4.0: macOS: --mask 경로는 읽기뿐 아니라 쓰기도 거부됩니다.
  • 2.4.0: 출력 필터가 ESC 두 번에 무너지지 않습니다. 무너지면 클립보드 쓰기를 몰래 통과시킬 수 있었습니다.
  • 2.4.0: 프록시의 테스트용 탈출구는 릴리스 빌드에서 빠집니다.

릴리스를 만드는 과정

  • 릴리스 태그에 서명하고, CI가 저장소에 고정된 키 지문으로 태그를 하나씩 검증합니다.
  • 모든 GitHub Action은 커밋 SHA로 고정되어 있고, 허용 목록에 있는 액션만 쓸 수 있습니다.
  • 빌드에는 버전을 고정한 Rust 툴체인과 --locked 의존성을 씁니다.
  • macOS 바이너리는 서명한 뒤 Apple의 공증을 받습니다.
  • 압축 파일마다 SHA-256 체크섬이 함께 제공되고, 배포 작업에서 체크섬을 다시 확인합니다.

릴리스 문서가 밝힌, 아직 부족한 점

  • 서명과 배포에 쓰는 시크릿이 아직 저장소 수준에 저장되어 있습니다. 릴리스 환경으로 옮겨야 합니다.
  • 모든 v* 태그에 서명을 강제하는 ruleset이 아직 없습니다.
  • 불변 릴리스(immutable releases)가 켜져 있지 않습니다.
  • crates.io 배포에 아직 토큰을 씁니다. Trusted publishing을 도입할 계획입니다.
  • 메인테이너가 한 명이라 변경 사항이나 릴리스를 검토할 두 번째 리뷰어가 없습니다.

RELEASE_SECURITY.md 읽기

취약점 신고

공개 이슈로 올리지 마세요. GitHub의 비공개 취약점 신고 기능을 쓰고, 재현 절차와 영향받는 버전을 적어 주세요. 수정을 조율할 시간도 필요합니다.

자주 묻는 질문

에이전트가 탈출할 수 있나요?
기본값은 실수한 에이전트가 가는 일반적인 경로인 파일, 환경, 네트워크, 호스트 IPC를 막도록 만들고 테스트했습니다. 작정한 공격자를 견디도록 만든 것은 아닙니다. 적극적으로 빠져나가려는 것은 커널이나 드라이버, 터미널, 샌드박스 백엔드의 버그를 쓸 수 있고, 그래서 악의적인 코드는 일회용 VM에서 돌려야 합니다.
네트워크를 켜고 실행해도 안전한가요?
에이전트를 그냥 실행하는 것보다는 안전합니다. 키와 홈 디렉터리는 여전히 그 안에 없어서 보낼 수 없기 때문입니다. --network를 켜면 에이전트는 읽을 수 있는 것을 무엇이든 보낼 수 있고, ai-jail은 작정한 공격자가 그렇게 하는 것을 막도록 만든 것이 아닙니다. 중간 지점은 --allow-host입니다. 나열한 호스트에만 닿을 수 있고, 각 호스트는 통째로 신뢰됩니다. 먼저 프로젝트 안의 시크릿을 마스킹하세요. 마스킹 방법 보기.
감사를 받았나요?
공개적으로 진행되었습니다. 1.0 이전에는 프로젝트가 스스로 감사했고, 2026년 9월에는 2.3.0을 대상으로 한 독립적인 소스 감사 두 건이 저장소에 공개되었습니다. 거기서 나온 보안 권고 두 건은 2.4.0에서 해결되었습니다. 제3자 업체의 감사는 없었습니다. 코드는 GPL 3.0으로 공개되어 있고, 비공개 취약점 신고를 환영합니다.

에이전트를 철창 안에 넣으세요

ai-jail은 바이너리 하나입니다. 데몬도 root 권한도 필요 없습니다. 평소 실행하던 명령 앞에 단어 하나만 붙이면 됩니다.

terminal
brew tap akitaonrails/tap && brew install ai-jailai-jail claude