-
에이전트에게 셸을 풀어주되, 사고 반경은 봉쇄하기 — Bash 샌드박스 경계 설계IT 2026. 7. 27. 22:00
AI 코딩 에이전트에게 셸을 열어 주는 순간, 우리는 묘한 거래를 한다. 에이전트가
npm install도 돌리고grep으로 코드도 뒤지고 테스트도 실행하길 바라지만, 그 똑같은 셸로rm -rf를 부르거나curl로 내 파일을 외부에 던지는 것은 원치 않는다. 명령을 실행하는 능력 자체에는 "좋은 명령"과 "나쁜 명령"이 새겨져 있지 않다. 같은bash한 줄이 빌드도 되고 사고도 된다.이 글은 그 거래를 설계로 푸는 방법 — 샌드박스 모드(sandbox mode), 즉 명령의 실행 권한과는 별개로 명령이 미칠 수 있는 영향의 범위를 따로 제한하는 장치 — 에 대한 정리다. 개인용 로컬 AI 서버에서 에이전트를 굴리며 직접 부딪힌 경계 설계의 트레이드오프를 옮겼다.
배경 — 셸은 너무 강력해서 위험하다
에이전트에게 임의 셸 실행을 허용하는 것은 생산성 면에서 거의 마법 같다. 사람이 일일이 명령을 골라 줄 필요 없이, 에이전트가 스스로 "이 디렉토리 구조를 보고, 이 파일을 읽고, 테스트를 돌려 보겠다"를 판단해 실행한다. 문제는 그 자유도가 파괴력과 정확히 같은 크기라는 점이다.
셸 한 줄이 일으킬 수 있는 사고는 크게 세 갈래다.
- 파일시스템 파괴 —
rm,mv, 리다이렉트(>)로 인한 덮어쓰기. 의도한 디렉토리 밖을 건드리면 복구 불가능한 데이터 손실이 난다. - 데이터 유출(exfiltration) —
curl·scp로 로컬 파일이나 비밀 토큰을 외부로 전송. 에이전트가 악의가 없어도, 조작된 입력(프롬프트 인젝션)에 휘둘리면 일어날 수 있다. - 시스템 변경 — 패키지 제거, 서비스 중단, 설정 파일 변경처럼 환경 자체를 흔드는 명령.
핵심은 이 세 가지가 "실행할 수 있다"는 능력 안에 뭉뚱그려 들어 있다는 것이다. 셸을 열면 탐색도 열리지만 파괴도 같이 열린다. 그래서 "셸을 줄 거냐 말 거냐"의 이분법으로는 답이 안 나온다. 줘야 일을 하고, 주면 위험하다.
문제 — 무엇을 막고 무엇을 풀어줄까
경계를 그으려고 처음 떠올리는 방법은 권한(permission) 통제 — "어떤 명령을 호출할 수 있는가"를 화이트리스트/블랙리스트로 관리하는 것이다. 이 접근은 필요하지만 충분하지 않다. 명령 이름만으로 안전을 판정하기 어렵기 때문이다.
rm은 위험해 보이지만, 빌드 디렉토리 안의rm -rf ./dist는 일상적이고 안전하다.curl은 데이터 유출 수단이지만, 패키지 설치 중 의존성을 받는 데도 똑같이 필요하다.python script.py는 무해해 보이지만, 그 스크립트가 안에서 무슨 짓을 할지는 명령 이름에 안 적혀 있다.
즉 명령의 이름으로 위험을 가르려 하면 너무 막아도 일을 못 하고(안전한
rm까지 차단), 너무 풀어도 위험하다(악성python은 통과). 우리가 진짜 통제하고 싶은 것은 명령의 이름이 아니라 그 명령이 미치는 영향의 범위 — 어떤 파일을 쓸 수 있고, 외부로 나갈 수 있느냐다.다이어그램 설명. 이 그림은 명령 이름만으로 안전을 가르려는 접근이 왜 막다른 길인지 보여준다. 위에서 아래로 읽으면, 같은 명령이라도 맥락에 따라 안전하거나 위험해서 — 위험해 보이는 삭제 명령이 빌드 산출물 정리에서는 안전하고, 무해해 보이는 스크립트 실행은 내부가 불투명해 오히려 위험하다 — 이름 기반 판정이 과차단과 과허용을 동시에 낳는다. 결론은 맨 아래 한 줄: 통제 축을 "무슨 명령이냐"에서 "그 명령이 무엇에 영향을 주느냐"로 옮긴다. 흔한 오해는 블랙리스트를 더 촘촘히 짜면 해결된다는 생각인데, 스크립트 내부 동작은 명령 이름에 드러나지 않으므로 리스트로는 끝내 메울 수 없다.
방법 — 권한과 샌드박스를 분리한다
해법의 출발점은 두 가지 통제를 서로 다른 축으로 분리하는 것이다.
- 권한(permission) — 무엇을 호출할 수 있는가. 어떤 명령·도구를 실행 허용할지의 화이트리스트/블랙리스트.
- 샌드박스(sandbox) — 호출의 영향이 어디까지 미치는가. 실행이 허용된 명령이라도, 파일시스템 쓰기 범위와 네트워크 접근을 운영체제 수준에서 가둔다.
비유하자면 권한은 "이 사람을 건물에 들여보낼까"이고, 샌드박스는 "들여보내되 어느 층 어느 방까지 열어 줄까"다. 들여보낸 사람이 좋은 의도를 가졌는지 일일이 심사하는 대신, 애초에 그가 갈 수 있는 곳을 좁혀 두면 나쁜 짓을 해도 피해가 그 방 안에 갇힌다.
샌드박스 모드의 기본값은 두 줄로 요약된다.
- 파일시스템 쓰기 — 작업 디렉토리(프로젝트 폴더) 안으로만 허용. 그 밖은 읽기는 되어도 쓰기는 막는다.
rm -rf /가 들어와도 샌드박스 밖을 못 건드린다. - 네트워크 접근 — 기본 차단.
curl로 외부에 데이터를 보내려 해도 연결 자체가 실패한다.
다이어그램 설명. 이 그림은 샌드박스가 셸 명령에 대해 읽기·쓰기·네트워크 세 축을 각각 어디까지 풀어 주는지를 한눈에 보여준다. 읽기는 넓게 열어 둔다 — 에이전트가 코드와 설정을 자유롭게 둘러봐야 일을 하므로 탐색의 자유는 그대로 둔다. 반면 쓰기는 작업 디렉토리 안으로 좁히고, 네트워크는 기본적으로 닫는다. 점선으로 막힌 두 갈래가 핵심인데, 사용자의 SSH 키나 시스템 설정 같은 작업 폴더 밖 쓰기와 외부로의 데이터 전송이 그 지점에서 봉쇄된다. 설계 의도는 "탐색의 자유는 최대로, 파괴의 자유는 최소로"를 동시에 만족시키는 것이다. 놓치기 쉬운 점은 읽기까지 막지는 않는다는 것 — 읽기를 막으면 에이전트가 무력해지므로, 위험의 대부분을 차지하는 쓰기와 네트워크만 가둔다.
예외적 해제 — 꼭 필요할 때만 명시적으로 연다
네트워크를 기본 차단하면 한 가지 문제가 생긴다.
npm install,pip install,git clone처럼 정당하게 네트워크가 필요한 작업이 막힌다. 그래서 샌드박스 모드에는 짝이 되는 장치가 필요하다 — 예외적 해제(escalation), 즉 특정 명령에 한해 샌드박스를 명시적으로 벗어나 실행하는 통로다.중요한 것은 이 해제의 방향이다. "기본은 풀어 두고 위험한 것만 막는다"가 아니라 "기본은 가두고 필요한 것만 푼다" — 보안에서 fail-safe default(안전한 쪽을 기본값으로)라 부르는 원칙이다. 차단 목록을 빠짐없이 채우는 일(블랙리스트)은 영원히 끝나지 않지만, 허용 통로를 좁게 여는 일(화이트리스트)은 명확하다.
다이어그램 설명. 이 그림은 명령 하나가 들어왔을 때의 의사결정 흐름이다. 위에서 시작해 첫 갈림길은 "이 명령이 샌드박스 안에서 그대로 실행 가능한가"를 묻는다. 대부분의 명령 — 코드 탐색, 파일 읽기, 작업 폴더 내 편집, 테스트 실행 — 은 "예"로 빠져 제한된 환경에서 그냥 돌아간다. "아니오"로 가는 건 네트워크나 작업 폴더 밖 쓰기가 필요한 소수다. 이때 곧장 해제하지 않고 "이 작업에 해제가 정말 필요한가"를 한 번 더 묻는 두 번째 갈림길이 있다는 점이 핵심이다 — 확실히 필요하면(의존성 설치, 빌드) 그 작업에 한정해 좁게 해제하고, 불확실하면 사람에게 확인을 넘긴다. 설계 의도는 해제를 예외 경로로 만들어, 거치는 명령마다 "정말 풀어야 하나"라는 마찰을 한 번 두는 것이다. 흔한 함정은 한 번 해제한 환경을 세션 내내 열어 두는 것 — 그러면 사실상 샌드박스가 꺼진 것과 같아진다.
효과와 트레이드오프 — 사고 반경을 좁히되, 해제는 작게
이 설계가 주는 것은 한 마디로 사고 반경(blast radius)의 축소다. 에이전트가 무언가를 잘못하더라도 — 환각으로 엉뚱한 경로를 지우려 하든, 조작된 입력에 휘둘리든 — 그 피해가 작업 디렉토리 안과 차단된 네트워크 경계 안에 갇힌다. 에이전트는 여전히 시스템 전역을 자유롭게 탐색하지만, 파괴적 영향은 작은 상자 안에 봉쇄된다. 탐색의 자유와 안전을 한쪽을 희생하지 않고 동시에 얻는 셈이다.
대신 분명한 트레이드오프가 있다. 해제가 잦으면 보호가 얇아진다. 모든 명령마다 "이것도 네트워크가 필요하다"며 샌드박스를 벗어나면, 결국 샌드박스가 없는 것과 다를 바 없다. 그래서 해제의 원칙은 단 하나다 — 작업 단위로, 가능한 한 좁게.
다이어그램 설명. 이 그림은 같은 "해제"라도 좁게 푸느냐 넓게 푸느냐에 따라 보호 수준이 갈린다는 점을 대비시킨다. 위에서 같은 출발점(네트워크가 필요한 작업)이 두 갈래로 나뉜다. 왼쪽 좁은 해제는 그 명령 하나만 풀고 끝나면 즉시 다시 닫혀, 사고 반경이 그 명령으로 한정된다. 오른쪽 넓은 해제는 편하다는 이유로 세션 전체를 열어 둬, 그 뒤에 들어오는 모든 명령이 보호 없이 실행된다. 핵심 메시지는 해제 자체가 나쁜 게 아니라 해제의 지속 시간과 범위가 관건이라는 것 — 같은 도구라도 "그 작업에만, 끝나면 닫힘"으로 쓰면 안전하고, "한 번 열고 계속"으로 쓰면 샌드박스를 사실상 무력화한다. 함정은 "어차피 또 설치할 텐데 열어 두자"는 편의주의로, 이게 보호를 가장 흔하게 무너뜨린다.
정리 — 능력은 넓게, 영향은 좁게
에이전트에게 셸을 풀어 주는 문제의 본질은 "셸을 줄까 말까"가 아니라 "셸의 능력은 주되 그 능력의 영향은 어디까지 미치게 둘까"였다. 권한(무엇을 호출하나)과 샌드박스(호출이 어디까지 미치나)를 두 축으로 분리하면, 명령 이름으로 위험을 가르는 끝없는 블랙리스트 대신 영향 범위를 가두는 명확한 경계를 그을 수 있다.
그 경계의 기본값은 "쓰기는 작업 폴더 안만, 네트워크는 차단", 그리고 그 위에 "꼭 필요할 때만 작업 단위로 좁게 해제"를 얹는다. 이 '기본 안전, 예외적 해제' 구조가 에이전트에게는 자유로운 탐색을, 사용자에게는 잠든 사이에도 무너지지 않는 사고 반경을 동시에 준다. 에이전트를 더 풀어 주고 싶다면, 권한을 넓히기 전에 샌드박스가 영향을 잘 가두고 있는지를 먼저 확인하는 편이 안전하다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
스킬이 50개 넘으니 엉뚱한 게 불렸다 — 겹침을 임베딩으로 자동 탐지하기 (0) 2026.07.29 스킬은 키워드가 아니라 '의미'로 불려 온다 — 의미 기반 매칭의 양날 (0) 2026.07.28 어떤 모델보다 '얼마나 깊게 생각하게 할까' — model × effort 2축으로 LLM 제어하기 (0) 2026.07.28 에이전트 셋을 동시에 풀었더니 서로의 코드를 덮어썼다 — git worktree로 푼 병렬 격리 (0) 2026.07.28 Docker 없이 격리하기 — 단일 로컬 서버에서 사고 반경을 줄이는 네 가지 경량 수단 (0) 2026.07.27 에이전트를 통제하는 두 개의 손 — 무엇을 코드로 막고 무엇을 LLM에게 맡길까 (0) 2026.07.27 자동화의 두 층 — OpenClaw는 '언제 깨울지', Hermes는 '깨어난 여럿이 어떻게 나눌지' (0) 2026.07.26 AI가 긴 작업에서 계획을 어디에 두나 — OpenClaw는 맥락 속에, Hermes는 외부 목록에 (1) 2026.07.25 AI가 나를 오래 기억하는 두 방식 — OpenClaw는 검색으로 회상하고, Hermes는 늘 머릿속에 둔다 (0) 2026.07.25 AI에게 파일 수정을 맡길 때 — OpenClaw는 어긋나면 멈추고, Hermes는 어긋나도 고친다 (0) 2026.07.25 - 파일시스템 파괴 —