한계가 분명한 유용한 계층
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이 할 수 있는 일은 없습니다. 가상 머신은 자체 커널을 가져오기 때문에 더 강한 경계입니다.
↔ 옆으로 밀어 전체 다이어그램을 확인하세요
내 데이터에 미치는 영향
- 프로젝트 안의 시크릿. 저장소에 있는
.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와 함께 쓰면 거부됩니다. |
--toolchains | Linux에서는 기본으로 켜짐. 내 툴체인이 안에서 그대로 동작합니다. ~/.cargo/bin과 ~/.rustup은 읽기 전용으로 마운트하고, 의존성 캐시는 실행 사이에 유지되는 감옥 전용 저장소 ~/.local/share/ai-jail/cache/에 두며, 패키지 레지스트리로의 필터링된 외부 연결을 허용합니다. 호스트의 실제 캐시는 절대 마운트하지 않습니다. --lockdown에서는 꺼지고, --no-toolchains로 끌 수 있습니다. |
--secret | 새로 열리는 것은 없습니다. 그 키의 실제 값은 샌드박스에 들어가지 않습니다. 에이전트에는 플레이스홀더가 보이고, 프록시는 키를 묶어 둔 호스트 하나로 가는 평문 HTTP 요청에만 실제 값을 바꿔 넣습니다. ai-jail 자신의 프로세스는 그 요청을 평문으로 봅니다. |
--audit-log | 새로 열리는 것은 없습니다. 이 스위치는 아무것도 약하게 하지 않습니다. 실행할 때마다 샌드박스 바깥에 있는 모드 0600 파일에 한 줄을 쓰므로 에이전트는 이 파일을 보지 못합니다. |
--docker | Docker 데몬을 통해 내 컴퓨터에서 root처럼 행동합니다. |
--kvm | 커널의 가상화 인터페이스인 /dev/kvm을 열어 QEMU, Firecracker, Android 에뮬레이터를 하드웨어 속도로 돌립니다. Linux 전용. 게스트는 샌드박스의 네트워크만 쓸 수 있고, /dev/net/tun은 넘기지 않습니다. |
--x11 | 키 입력을 기록하고 다른 X11 창의 스크린샷을 찍습니다. |
--systemd-user | systemd 사용자 매니저에게 호스트에서 서비스를 실행해 달라고 요청합니다. |
--inherit-env | 시크릿을 포함한 셸 환경 전체를 읽습니다. API 키라면 --env-from-file로 파일 하나만 넘기는 방법이 있습니다. |
--agent-state | 기본으로 켜짐. Claude의 ~/.claude나 Devin의 ~/.local/share/devin과 ~/.config/devin처럼 에이전트에 저장된 로그인을 읽기·쓰기로 마운트하고, 셸에 설정된 API 키를 복사해서 로그인된 상태로 시작합니다. 샌드박스 안의 모든 것이 실행이 끝날 때까지 그 자격 증명을 쓸 수 있습니다. --no-agent-state를 주면 격리된 로그아웃 상태로 실행되고, --lockdown에서는 꺼집니다. |
--github | GitHub에서 나처럼 행동합니다. 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을 도입할 계획입니다.
- 메인테이너가 한 명이라 변경 사항이나 릴리스를 검토할 두 번째 리뷰어가 없습니다.
취약점 신고
공개 이슈로 올리지 마세요. GitHub의 비공개 취약점 신고 기능을 쓰고, 재현 절차와 영향받는 버전을 적어 주세요. 수정을 조율할 시간도 필요합니다.
자주 묻는 질문
에이전트가 탈출할 수 있나요?
네트워크를 켜고 실행해도 안전한가요?
--network를 켜면 에이전트는 읽을 수 있는 것을 무엇이든 보낼 수 있고, ai-jail은 작정한 공격자가 그렇게 하는 것을 막도록 만든 것이 아닙니다. 중간 지점은 --allow-host입니다. 나열한 호스트에만 닿을 수 있고, 각 호스트는 통째로 신뢰됩니다. 먼저 프로젝트 안의 시크릿을 마스킹하세요. 마스킹 방법 보기.감사를 받았나요?
에이전트를 철창 안에 넣으세요
ai-jail은 바이너리 하나입니다. 데몬도 root 권한도 필요 없습니다. 평소 실행하던 명령 앞에 단어 하나만 붙이면 됩니다.
brew tap akitaonrails/tap && brew install ai-jailai-jail claude