코딩 에이전트는 파일을 읽고 쓰고 명령을 실행한다. 이 권한은 기능의 핵심이지만 동시에 공격면이다. 저장소의 문서, 의존성의 README, 웹에서 가져온 문서에 들어 있는 텍스트가 에이전트의 다음 행동에 영향을 줄 수 있다는 점을 전제로 설계해야 한다.
방어의 기본 원칙은 하나다. 에이전트가 무엇을 하든 피해 범위가 한정되게 만든다. 완벽한 통제는 불가능하므로, 통제 대신 경계를 설계한다.
나는 에이전트에게 처음 쉘 실행 권한을 줬을 때 막연히 불안했다. 그런데 무엇이 불안한지 구체화하기 어려웠다. 정리해 보니 질문은 네 개였다. 어디까지 읽을 수 있나, 어디까지 쓸 수 있나, 어디로 데이터를 보낼 수 있나, 무엇을 되돌릴 수 없나. 이 네 가지에 답하는 것이 곧 샌드박스 설계다.
네 가지 경계
| 경계 | 통제 대상 | 대표 수단 |
|---|---|---|
| 파일 읽기 | 시크릿·개인 자료 노출 | 작업 디렉터리 제한, 경로 정규화 후 검사 |
| 파일 쓰기 | 시스템 손상·백도어 삽입 | 쓰기 가능 경로 화이트리스트, 버전 관리로 변경 추적 |
| 네트워크 | 데이터 유출·악성 페이로드 수신 | 이그레스 허용 목록, 프록시 경유 |
| 되돌릴 수 없는 동작 | 배포·삭제·전송·결제 | 승인 게이트, 드라이런 기본값 |
파일 경계 — 경로 검사는 정규화 후에
경로 제한의 고전적 실수는 문자열 비교로 검사하는 것이다. ../나 심볼릭 링크로 쉽게 우회된다. 반드시 정규화한 절대 경로로 비교해야 한다.
// 취약한 검사
function isAllowed(p) {
return p.startsWith('/workspace'); // '/workspace/../etc/passwd' 통과
}
// 안전한 검사
import path from 'node:path';
import fs from 'node:fs';
function isAllowed(p, root = '/workspace') {
const realRoot = fs.realpathSync(root);
let target = path.resolve(root, p);
// 심볼릭 링크 해석 — 존재하지 않는 경로는 상위 디렉터리 기준으로
try { target = fs.realpathSync(target); }
catch { target = path.resolve(fs.realpathSync(path.dirname(target)), path.basename(target)); }
const rel = path.relative(realRoot, target);
return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
}
~/.aws, ~/.ssh, ~/.config)이 접근 범위 밖인가.env와 시크릿 파일을 읽기 대상에서 제외했는가네트워크 경계 — 이그레스가 핵심
인바운드보다 아웃바운드가 중요하다. 데이터 유출은 나가는 연결로 일어나기 때문이다. 기본 차단 후 필요한 도메인만 허용하는 구성이 이상적이다.
# 컨테이너 기반 에이전트의 이그레스 통제 예 (개념)
# 1) 기본 아웃바운드 차단
iptables -P OUTPUT DROP
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 2) DNS 는 내부 리졸버로만
iptables -A OUTPUT -p udp --dport 53 -d 10.0.0.2 -j ACCEPT
# 3) 필요한 목적지만 허용 (패키지 레지스트리, 내부 저장소 등)
# IP 는 변할 수 있으므로 실무에서는 프록시 경유가 더 관리하기 쉽다
# HTTP(S)_PROXY 를 강제하고, 프록시에서 도메인 허용 목록을 관리
# 4) 프록시 로그로 어디에 접속했는지 감사
# "빌드가 낯선 도메인에 접속했다"는 강력한 침해 신호다
프록시 방식이 관리하기 편한 이유는 도메인 단위로 허용할 수 있고, 로그가 남기 때문이다. IP 기반 규칙은 CDN을 쓰는 서비스에서 금방 무너진다.
시크릿 — 애초에 두지 않기
경계를 아무리 잘 그어도, 환경 안에 자격증명이 있으면 언젠가 컨텍스트나 로그에 섞인다. 가장 확실한 방어는 훔칠 것을 두지 않는 것이다.
# 로그 마스킹 예 (간단한 형태)
MASK='s/(sk-[A-Za-z0-9]{16,}|ghp_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16})/[REDACTED]/g'
agent-run 2>&1 | sed -E "$MASK" | tee run.log
# 더 나은 방법: 애초에 환경에 없게 만든다
env -i PATH=/usr/bin:/bin HOME=/workspace agent-run
승인 게이트 — 되돌릴 수 있는가로 나눈다
모든 동작에 승인을 요구하면 자동화의 의미가 없다. 기준은 단순하게 잡는 게 좋다. 되돌릴 수 있으면 자동, 되돌릴 수 없으면 승인.
| 동작 | 되돌릴 수 있나 | 정책 |
|---|---|---|
| 파일 수정(버전 관리 하) | 가능 | 자동 허용 |
| 테스트·빌드 실행 | 가능 | 자동 허용 |
| 의존성 설치 | 대체로 가능 | 자동 허용 (락파일 변경은 리뷰) |
| 브랜치 푸시 | 가능(되돌리기 번거로움) | 조건부 — 보호 브랜치는 금지 |
| 프로덕션 배포 | 어려움 | 승인 필수 |
| 데이터 삭제·외부 전송 | 불가 | 승인 필수 + 감사 로그 |
외부 콘텐츠를 데이터로 취급하기
에이전트가 읽는 파일, 웹 페이지, 이슈 코멘트에는 지시문처럼 보이는 문장이 들어 있을 수 있다. 이를 명령으로 실행하면 사고가 된다. 완전한 방어는 어렵지만, 구조적으로 위험을 줄일 수 있다.
- 출처 표시 — 외부에서 가져온 내용은 경계를 명확히 표시해 컨텍스트에 넣는다.
- 권한 분리 — 외부 콘텐츠를 읽은 세션과 배포 권한을 가진 세션을 분리한다.
- 행동 제한 — 외부 콘텐츠 처리 중에는 되돌릴 수 없는 도구를 비활성화한다.
- 검증 우선 — 결과는 항상 테스트·린트 같은 객관적 수단으로 확인한다.
- 감사 로그 — 어떤 입력이 어떤 행동으로 이어졌는지 추적 가능하게 남긴다.
실행 환경 선택
- 파일·네트워크 격리가 명확
- 환경 재현이 쉽다
- 이그레스 통제를 붙이기 좋다
- 손상 시 폐기하고 다시 만들면 된다
- 설정이 간단하고 빠르다
- 로컬 도구·캐시를 그대로 쓴다
- 대신 격리가 사실상 없다
- 홈 디렉터리 전체가 위험 범위
개인 실험은 호스트에서 해도 되지만, 자동화된 파이프라인이나 공유 환경에서는 컨테이너가 기본이다. 특히 에이전트가 사람의 승인 없이 도는 구간이 있다면 격리는 선택이 아니라 요건이다.
자주 묻는 질문
에이전트에게 쉘 실행 권한을 줘도 되나요?
작업 범위가 격리돼 있고 시크릿이 환경에 없다면 실용적입니다. 반대로 개인 노트북의 홈 디렉터리에서 무제한 실행하는 구성은 위험합니다. 최소한 작업 디렉터리 제한과 시크릿 분리는 갖춘 뒤 사용하세요.
경로 제한은 어떻게 구현하나요?
문자열 접두사 비교가 아니라 경로를 절대 경로로 정규화하고 심볼릭 링크를 해석한 뒤, 작업 루트와의 상대 경로가 상위로 올라가지 않는지 확인해야 합니다. 이 과정을 생략하면 쉽게 우회됩니다.
네트워크는 어디까지 막아야 하나요?
기본 차단 후 필요한 목적지만 허용하는 구성이 이상적입니다. 실무에서는 프록시를 강제하고 도메인 허용 목록을 프록시에서 관리하는 방식이 운영하기 편하며, 접속 로그가 남아 이상 탐지에도 쓸 수 있습니다.
시크릿이 컨텍스트에 섞이는 것을 어떻게 막나요?
가장 확실한 방법은 에이전트가 도는 환경에 자격증명을 두지 않는 것입니다. 꼭 필요한 단계만 별도 프로세스로 분리하고 단기 토큰을 사용하며, 로그와 도구 출력에 마스킹을 적용하세요.
어떤 동작에 사람 승인을 요구해야 하나요?
되돌릴 수 있는지를 기준으로 나누면 명확합니다. 버전 관리 하의 파일 수정이나 테스트 실행은 자동으로 두고, 프로덕션 배포·데이터 삭제·외부 전송처럼 복구가 어려운 동작에는 승인을 요구하세요.
외부 문서의 지시문이 에이전트를 움직이는 문제는요?
완전한 차단은 어렵지만 구조로 줄일 수 있습니다. 외부 콘텐츠는 출처를 표시해 데이터로 취급하고, 그것을 읽는 세션에서는 되돌릴 수 없는 도구를 비활성화하며, 결과는 항상 테스트 같은 객관적 수단으로 검증하세요.

댓글 0