본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 10. 08:22

Claude Code 무인 자율 편 — ask가 무력해지는 세상에서 메커니즘으로 보호하기

요약

Claude Code를 사람이 개입하지 않는 무인 환경에서 운용할 때, 승인 절차(ask)의 무력함을 극복하기 위한 다층 방어 설계 전략을 다룹니다. 단순 승인을 넘어 deny, hook, sandbox, backup을 결합하여 안전망을 구축하는 방법을 제시합니다.

핵심 포인트

  • 무인 운용 시 ask(승인) 메커니즘은 작동하지 않으므로 정적 경계 설정이 필수적임
  • deny, hook, sandbox, backup을 겹쳐 사용하는 다층 방어 체계 구축
  • 위협 유형을 6가지로 분류하여 각 위협에 맞는 메커니즘 매핑
  • 불가역적 외부 작용과 비밀 정보 유출에 대한 강력한 차단 전략

Claude Code(AI 코딩 에이전트)의 권한 설계를 "사람이 화면 앞에 있다"는 전제로 짜면, 한 가지 놓치는 점이 있습니다. 무인 운용——야간 배치(batch)나 정기 처리로 에이전트를 돌리는 상황에서는, ask(허가를 구하는 확인 다이얼로그. 이하 prompt)에 답할 사람이 없다는 것입니다.

전편 「Claude Code의 권한 설계 실무 가이드」는 "사람이 prompt를 보고 승인하는" 세상의 이야기였습니다. 이 기사는 그 후속편으로, 사람이 보지 않는 세상——ask가 무력해지는 세상에서 어떻게 보호할 것인가를 다룹니다. 결론부터 말하자면, 무인 운용에서는 안전을

, 그리고

ask가 아니라 allow/deny/hook/sandbox라는 정적인 경계에 각인하여 prevention(방지)과 recovery(복구/backup)를 다층적으로 겹치는 것입니다. 단일 메커니즘으로 보호할 수 있다는 전제를 버리는 것이 이 기사의 핵심입니다.

전제: Claude Code의 settings.json(allow/deny/hooks/sandbox를 작성하는 파일)을 편집할 수 있고, PreToolUse hook(도구 실행 전에 실행되는 외부 스크립트)을 설치할 수 있음을 전제로 합니다. 무인 실행은 스케줄러(macOS라면 launchd, Linux라면 cron/systemd)로 에이전트의 정기 처리를 기동하는 형태를 상정합니다. 동작은 2026년 6월 시점·macOS의 CLI 환경(claude --version으로 자신의 버전을 확인하세요)에서 확인한 것이며, 버전에 따라 달라질 수 있으므로(특히 sandbox의 bypass는 버전에 따라 막혀 있을 가능성이 있습니다) 마지막에는 자신의 환경에서 확인하시기 바랍니다.

본고에서 다루는 내용

  • 무인 운용에서 ask가 무력해지는 이유와, dontAsk로 그것을 deny로 바꾸는 설계 (안전망을 유지한 채 prompt를 0으로 만듦)
  • 그 위에 겹치는 계층 (hook / sandbox / deny / backup)
  • 위협을 "자동으로 해도 좋은 것"과 "메커니즘으로 막아야 하는 것"으로 나누는 지도
  • hook과 sandbox의 강점·약점과, 다층으로 보완하는 설계
  • backup을 recovery의 최종 방어선에 두는 이유
  • 이용자 타입별 최소 구성

본고의 범위 외 (별도 글로 분할)

  • allow/deny의 기본과 패턴 매칭의 함정 (→ 전편 「권한 설계 실무 가이드」)
  • 퍼미션 모드(acceptEdits/bypassPermissions 등)의 선택 방법 (→ 「모드 편」)
  • Read(.env)의 앵커 의미론 및 symlink (→ 「deny/경로 편」)

본고의 위협 모델과 다층 방어의 사고방식(위협 6유형·lethal trifecta·backup을 최종층으로)은 permission 모델을 가진 다른 AI 코딩 에이전트에도 응용할 수 있지만, dontAsk/sandbox/settings.json의 키나 구문은 Claude Code 고유입니다 (다른 에이전트에서는 동등한 개념으로 치환하여 이해해 주세요).

우선 위협을 6유형으로 나누기 (무엇에 무엇이 필요한지에 대한 지도)

"에이전트가 폭주하면 무섭다"는 너무 막연해서 설계로 이어지지 않습니다. 무엇이 일어나면 곤란한가를 분류하면 대책이 메커니즘과 대응됩니다. 제가 사용하고 있는 지도는 이 6유형입니다.

#위협예시git으로 되돌릴 수 있는가주요 대책
불가역적인 외부 작용공개·전송·deploy·force push✕ (외부로 나가면 되돌릴 수 없음)deny + 공개 게이트 (사람의 승인)
비밀 정보 유출injection을 통한 .env 외부 전송Read deny + 전송 경로 차단
git 외부의 운영 데이터 파괴Sheets / DB / SaaS 삭제·덮어쓰기✕ (git 관리 외)backup (recovery)
git 이력 파괴reset --hard / force push△ (이력 전제가 깨짐)deny + backup
가역적인 로컬 변경작업 파일 편집·일시 삭제◯ (git으로 복구 가능)자동화해도 무방 (prompt 불필요)
임의 코드 실행python -c / uv run의 내용①~④의 입구가 될 수 있음sandbox로 격리

이 지도의 효용은 두 가지가 있습니다. 하나는, ⑤(git으로 되돌릴 수 있는 가역적 로컬 변경)는 막을 필요가 없다는 점이 명확해진다는 것입니다. 여기에 prompt를 내보내는 것은 순수한 friction (마찰)이며, 무인 운용에서는 처리를 중단시키는 해악일 뿐입니다. 다른 하나는, ③(git 외부 데이터)과 ④(이력 파괴)는 git이 지켜주지 못한다는 사실을 깨닫는 것입니다. "전부 git에 넣어두었으니 복구할 수 있다"는 말은 ①②③④에는 통하지 않습니다.

많은 권한 설정 가이드는 "위험한 명령어를 deny 하자" 수준에서 멈추지만,

어떤 위협에 어떤 기구가 필요한지까지 구분하지 않으면, git으로 되돌릴 수 있는 변경에는 prompt를 내보내 막으려 하고, git으로 되돌릴 수 없는 데이터에는 아무런 대비를 하지 않는——역전 현상이 발생합니다.

ask가 무력한 이유

무인 운용에서 ask가 효과가 없는 이유는 두 가지입니다.

첫 번째는 단순합니다, 대답할 사람이 없습니다. 야간 배치(batch) 작업 중 에이전트가 "이 명령어를 실행해도 될까요?"라고 prompt를 내보내도, 아무도 보고 있지 않습니다. 처리는 승인 대기 상태로 멈추거나, --dangerously-skip-permissions (모든 prompt를 건너뛰는 위험 플래그)를 사용하여 전부 통과시키는 것 중 하나를 선택해야 하는 것처럼 보입니다. 전자는 자동화의 의미를 상실하고, 후자는 안전망을 완전히 제거합니다. ——하지만, 후술할 내용처럼 제3의 길이 있습니다.

두 번째는 더 근본적인 문제입니다. 재석(in-person) 운용에서도 ask를 과신할 수 없습니다. approval fatigue (승인 피로) 실측 결과에 따르면, 사용자는 허가 prompt의 약 93%를 정밀하게 검토하지 않고 승인합니다. prompt를 너무 많이 내보내면, 사람은 내용을 읽지 않고 반사적으로 "예"를 누르게 됩니다.

과도한 prompt는 오히려 안전망을 공동화(hollow out)시킵니다. 그래서 전편에서 "불필요한 prompt를 없애자"고 쓴 것은 편의를 위해서가 아니라 보안(security) 측면에서 옳기 때문입니다. critical한 것만 사람에게 묻고, low-risk한 것은 알림(notification)에 머물게 하는 tiering (계층화)이 양쪽 연구의 공통된 해답입니다.

무인 운용은 이러한 "ask에 의존하지 않는 설계"를 강제적으로 마주하게 만드는 극단적인 사례일 뿐입니다. 재석 운용에서도 본질은 같습니다. 안전은 사람의 판단(runtime의 oracle)이 아니라, 사전에 작성된 정적 경계(static boundary)로 옮겨가야 합니다. 다만——이것이 이 글의 관통하는 주제입니다만——정적 경계 또한 단일로는 뚫릴 수 있습니다 (후술하듯 hook도 sandbox도 bypass 사례가 존재합니다). 따라서 하나에만 의존하지 않고, 다층으로 쌓으며, 마지막에 backup을 배치합니다.

askdeny로 바꾸기 — 안전망을 유지하면서 prompt를 0으로 만들기

"ask에 답할 사람이 없다"는 문제를 해결하는 단순한 방법은 --dangerously-skip-permissions (모든 prompt를 건너뛰기)이지만, 이는 안전망 자체를 버리는 행위입니다. 이 모드는 permission 층을 통째로 스킵하기 때문에, 공들여 작성한 deny도 작동하지 않습니다 (공식 doc은 bypassPermissions를 "skips the permission layer entirely"라고 명시하고 있습니다). injection이나 폭주에 무방비 상태가 됩니다.

더 좋은 방법이 있습니다. 프롬프트(prompt)를 '사람에게 확인'하는 것이 아니라 '거부(deny)'로 바꿔버리는 것입니다. Claude Code에는

dontAsk

라는 권한 모드(permission mode)가 있으며, "본래 프롬프트가 나올 상황을 모두 거부로 변환한다. 단, 허용(allow) 규칙이나 훅(hook)으로 허가된 것은 평소와 같이 실행한다"라고 명시하고 있습니다). injection이나 폭주에 무방비 상태가 됩니다.

즉:

  • 안전하고 허가됨 (allow 규칙 / sandbox auto-allow) → 실행
  • 그 외 → 프롬프트가 아닌 즉시 (아무도 기다리지 않음 = 멈추지 않음) deny

dontAsk는 (skip-permissions와 달리 permission 계층이 유효함) = deny 규칙은 살아있는 채로 안전망을 버리지 않습니다.

평가 순서는 **hook → deny → allow → ask → 모드 기본값(mode default)**입니다. dontAsk에서는 이 '모드 기본값'이 거부(deny)가 됩니다.

deny되면 에이전트가 올바른 방식으로 수정한다

여기가 핵심입니다. deny는 사람을 멈추게 하는 것이 아니라 에이전트에게 즉시 돌아갑니다. 에이전트는 거부된 명령을 허가되는 형태(처음부터 그렇게 쓰도록 지정해 둔 형태)로 다시 작성하여 계속 진행할 수 있습니다. 예를 들어 "... | python3 -c "..."로 JSON 정렬"이 거부되면 "jq로 정렬"로, 복잡한 heredoc은 "파일에 써서 실행"으로 바꿉니다. 사람의 승인을 단 한 번도 거치지 않고 작업이 진행됩니다. 사람이 자리에 있더라도 마찬가지로, 에이전트가 프롬프트가 나올 법한 방식으로 작성하더라도 사람이 승인하는 대신 deny로 차단되면, 에이전트가 이를 수정하여 계속 이어갑니다. 즉, 사람의 주의를 빼앗지 않습니다.

이는 "AI가 조심하겠다"라는 믿을 수 없는 약속을 결정론적인 거부(deterministic deny) + 자기 수정(self-correction) 루프로 대체하는 발상입니다. 거부는 메커니즘(확실함)이며, 수정 지침은 사전 지정(CLAUDE.md 등의 운영 규칙)입니다. 수정할 수 없을 때는 무한 루프에 빠지는 대신 중단하고 보고하도록 상한선을 설정하면, hang(멈춤)이 아니라 "미완성 + 나중에 사람이 리뷰" 상태로 착지하게 됩니다.

실제로 이 동작은 격리 환경에서 dontAsk 세션을 열어 확인했습니다. deny 대상(curl)을 에이전트에게 실행시키자, 확인 다이얼로그는 뜨지 않고 즉시 거부되었으며, 에이전트는 멈추지 않고 "curl은 허용되지 않습니다. 네트워크가 필요하다면 WebFetch 도구로 가져올 수 있습니다"라고 대체 수단을 제시했습니다.

deny가 사람을 기다리지 않고 에이전트에게 돌아가며, 에이전트가 허가되는 경로로 전환하는 이 일련의 과정이 실제 기기에서 관찰되었습니다.

sandbox auto-allow와 결합하면 "안전한 것은 실행 · 나머지는 거부"가 된다

dontAsk 단독으로는 allow 규칙에 없는 대부분이 거부되어 불편합니다. 여기서 2계층인 sandbox auto-allow(autoAllowBashIfSandboxed)가 효과를 발휘합니다. sandbox로 감쌀 수 있는 일반적인 Bash는 샌드박스가 경계를 보장하므로 자동으로 실행되고, 감쌀 수 없는 것(excludedCommands, 신규 도메인, home으로의 rm 등)만 dontAsk에 의해 거부로 떨어집니다. 결과적으로 안전한 다수는 통과 · 위험한 소수만 거부라는 이상적인 배분이 이루어집니다.

실기에서의 주의사항 (doc/issue ≠ 실제 동작):
dontAsk + sandbox 환경에서 쉘 확장(shell expansion, $(...))을 포함한 명령이 permission 계층에서 거부된다는 보고(Issue #51001)가 있습니다. 하지만 제가 가진 환경(2.1.169)에서는 재현되지 않았습니다(버전 및 조건 의존성). 이전 글에서도 언급했듯이 "문서(doc)가 말하는 동작과 실제 바이너리가 어긋나는" 전형적인 사례이므로, python3 -c "print($(echo 2))" (확장이 포함되어 있으며, 맨 앞이 내장되지 않은 python3인 경우)를 실제로 실행해 본 결과, 거부되지 않고 실행되었습니다. 이러한 종류의 동작은 맹신하지 말고 자신의 환경에서 dontAsk 세션으로 직접 확인하는 것이 안전합니다.

지금까지의 판정 흐름을 한 장으로 정리하면 다음과 같습니다. 명령은 hook → deny → allow/sandbox → mode 순으로 평가되며, dontAsk에서는 "어느 것에도 해당하지 않음 = 거부"로 귀결됩니다 (default라면 같은 위치에서 "사람에게 확인"이 되어 무인 상태에서는 stall(정지)됩니다). 거부는 에이전트에게 돌아가며 자기 수정 루프에 진입합니다.

이 그림의 "dontAsk → 거부 → 자기 수정"의 고리와, "default

→ 사람에게 확인 → 무인 stall」의 분기 차이가 본고의 주장 그 자체입니다.

이것이 「ask가 무력한 세계에서 메커니즘으로 보호한다」의 도달점입니다:

ask를 없애기 위해 skip-permissions(안전망을 버림)를 쓰는 것이 아니라 dontAsk(안전망을 유지함)를 사용하고, deny를 「벽」과 「이정표」 양쪽 모두로 사용합니다. 다음에 나열할 계층(hook / sandbox / backup)은 이 allow/deny의 배분을 안전한 쪽으로 두텁게 만드는 부품입니다. container는 그 위에 덧씌우는 **다중 방어 (Multi-layered Defense)**이며, skip-permissions의 대용품이 아닙니다.

계층 1: hook — 실행 전의 정적 판정 (빠르지만 허점이 있음)

첫 번째 계층은 PreToolUse hook입니다. 툴이 실행되기 전에 커맨드 문자열을 받아 위험하다면 exit 2로 중단합니다. 이전 글에서 다루었듯이, Claude Code의 설계에서 PreToolUse hook의 exit 2는 permission rule의 평가보다 먼저 작동하여 allow보다 우선되므로, 「절대로 멈추고 싶다」를 의도(intent) 기반으로 표현할 수 있는 것이 강점입니다 (평가 순서는 공식 permissions doc과 이전 글을 통해 확인 완료했습니다). 저는 위험한 커맨드(main 브랜치로 직접 push 등)를 막는 guard를 hook으로 가지고 있습니다.

하지만 hook에는 구조적인 두 가지 허점이 있습니다.

허점 1: 문자열의 정적 판정이므로 오판할 수 있다. hook은 커맨드를 실행하지 않고 문자열만 보기 때문에, 정말로 위험한지 확실히 알 수 없습니다. 안전한 쪽으로 기울이면 마찰(friction)이 생기고, 느슨하게 하면 허점이 생깁니다.

허점 2: 래퍼(wrapper)를 벗겨내지 않으면 회피된다. 이것은 저 자신이 직접 저지른 실수였습니다. main 브랜치로의 직접 push를 막는 guard를 작성했다고 생각했는데,

env FOO=bar git push origin main # ← guard를 그대로 통과함

env / timeout / nice / nohup / xargs와 같은 래퍼를 앞에 붙이면, 선두 토큰이 git이 아니게 되어 단순한 선두 일치 방식이 빗나갑니다. 구현 직후 재점검을 통해 처음으로 깨달았습니다. 수정 방법은 판정 전에 래퍼 명령어와 VAR=val 형태, 옵션, 수치를 건너뛰고 본체 토큰을 추출하는 것입니다.

같은 함정은 auto-approve hook(안전한 커맨드를 자동 승인하여 마찰을 없애는 hook)에도 있습니다. env VAR=val <cmd>를 제대로 벗겨내지 못하면, PATHLD_PRELOAD를 주입하여 다른 실행 파일로 변장시키는 env-injection이 통과될 수 있습니다. 저는 「VAR=val을 발견하면 일괄적으로 defer(자동 승인하지 않음)한다」는 범주적(categorical)인 처리로 막았습니다. 요컨대, 판정 전에 이것만큼은 벗겨내야 합니다:

# 위험 판정 전에 본체 토큰을 추출하는 최소 로직 (의사 코드)
WRAPPERS = {"env", "timeout", "nice", "nohup", "stdbuf", "xargs", "setsid"}
def head_token(tokens):
...

래퍼는 「벗겨서 내부를 본다」가 원칙이며, 벗기는 방법을 하나라도 잊으면 허점이 됩니다 —— hook을 작성한다면, 이 회피 형태를 처음부터 테스트에 포함해야 합니다. 저는 위험한 형태와 bypass 형태가 모두 defer가 되도록 약 90개의 결정론적 테스트(deterministic test)로 고정해 두었습니다 (hook의 로직은 LLM이 아닌 순수한 문자열 판정이므로, 테스트로 동작을 동결할 수 있습니다).

그리고 hook의 사정거리도 솔직히 말씀드리면: PreToolUse의 Bash matcher는 Bash 커맨드에만 작동합니다. Read/Edit 툴이나 MCP를 통해 외부 API를 직접 호출하는 경로는 별도의 matcher / 별도의 메커니즘이 필요합니다. hook은 「계층 1」일 뿐 전부가 아니라는 점 또한 다층 방어가 필요한 이유 중 하나입니다.

계층 2: sandbox / container — OS로 봉쇄 (견고하지만 경계는 아님)

두 번째 계층은 sandbox 입니다. hook가 "실행 전 문자열 판정"인 것과 달리, sandbox는 OS 레벨에서 실행 중인 프로세스의 fs/network를 봉쇄합니다 (macOS라면 Seatbelt, Linux라면 bubblewrap, 더 강력하게는 container). 명령의 내용이 무엇이든, OS가 쓸 수 있는 장소와 연결할 대상을 물리적으로 제한합니다. 이는 python -c의 내용을 정적으로 판정하는 것보다 훨씬 견고합니다.

…하지만, sandbox는 hard boundary (뚫을 수 없는 벽)가 아니라 어디까지나 하나의 계층일 뿐입니다. 2026년에 Claude Code의 sandbox에서 여러 번의 bypass (우회)가 실증되었습니다.

network allowlist 관통: SOCKS5의 hostname에 null 바이트를 주입하는 수법으로 wildcard allowlist를 통과할 수 있다고 보고되었습니다 (GA 이후 약 5.5개월 동안 실질적으로 기능하지 않았다는 지적도 있으나, 이는 개인 블로그의 보고이며 1차 확인은 하지 않았습니다).

에이전트 스스로에 의한 sandbox 자기 무효화: 에이전트가 마련된 escape hatch (탈출구)를 사용하여 bubblewrap을 무효화하고, kernel 강제 하에서도 동적 링커 (dynamic linker)를 통해 빠져나간 실험.

domain fronting: 허가된 넓은 도메인(github.com이나 CDN 등)을 경유하여 allowlist 외로 데이터를 유출합니다. 이는 외부의 지적이 아니라 Claude Code 공식 sandboxing doc 자체에서 경고하고 있습니다. 그 이유는 "proxy는 클라이언트가 신고한 hostname으로 허가 판정을 하며 TLS의 내용을 검사하지 않기" 때문입니다.

게다가 sandbox에는 적용 범위의 한계가 있으며, 이 또한 공식 doc에 명시되어 있습니다. sandbox가 둘러싸는 것은 Bash 서브 프로세스뿐이며, Read/Edit/Write 툴은 별도의 permission (권한) 계통을 통하고, MCP를 경유하거나 HTTPS로 외부 API를 호출하는 송신은 sandbox의 밖에 있습니다. 그리고 기본 read 제한으로는 .env나 ~/.aws/credentials, ~/.ssh/가 에이전트에게 그대로 노출됩니다 (공식 doc에서 "default read policy still allows reading credential files"라고 명시함). 즉, sandbox를 켠 것만으로는 비밀을 지킬 수 없으며,

denyRead나 후술할 Read deny로 별도로 막아야 합니다.

저는 이 세션의 초기에 "sandbox를 켜면 python -c / uv run을 안전하게 자유롭게 실행할 수 있다"고 생각했습니다. 이는 과신이었습니다. sandbox는 allowUnsandboxedCommands:false (탈출을 차단)하고, 도메인은 wildcard가 아닌 narrow (좁게) 설정하며, denyRead로 creds (자격 증명)를 차단하고, backup과 병용할 때 비로소 의미를 갖는 하나의 계층일 뿐, 단독으로 의지할 벽이 아닙니다.

다층으로 보완하기 — 설계 시 "3가지 조건" 중 하나를 제외하기

hook (실행 전·문자열·빠르지만 오판/회피 가능)와 sandbox (실행 중·OS·견고하지만 범위 한정/bypass 가능)는 강점과 약점이 서로 보완됩니다. 따라서 최종 형태는 hook + sandbox/container입니다. 어느 하나가 아니라 겹쳐서 사용해야 합니다.

겹칠 때의 설계 지침으로서, 해외에서 정착되고 있는 두 가지 관점이 유용합니다.

lethal trifecta (Simon Willison): "기밀 데이터에 대한 접근", "의심스러운 콘텐츠의 노출", "외부로의 송신 경로"라는 3가지 조건이 갖춰지면, 모델의 지능이나 system prompt의 강화 여부와 관계없이, indirect prompt injection (간접 프롬프트 주입)에 무조건적으로 취약해집니다.

Rule of Two (Meta AI · 2026): trifecta의 "외부 송신 경로"를 "외부 state의 변경"으로 확장한 일반화(송신뿐만 아니라 쓰기·실행과 같은 부작용 전반을 포함하는 차원)로, 에이전트에게 부여하는 권능은 {의심스러운 입력 처리 / 기밀 시스템 접근 / 외부 state 변경} 중 최대 2개까지입니다. 3개를 모두 갖추려면 사람을 in-the-loop (개입)시키는 것이 필수적입니다.

실무에서의 사용법은 간단합니다. 설계 시에 이 3가지 조건 중 하나를 구조적으로 누락시키는 것입니다. 예를 들어 '외부의 수상한 입력을 읽는 처리'에는 '기밀 접근'이나 '송신로' 중 하나를 부여하지 않습니다. Read(.env)를 거부(deny)하면 기밀 접근이 누락되고, 네트워크 송신을 샌드박스(sandbox)에서 차단하면 송신로가 누락됩니다. 이것은 안전의 보증이 아니라 **가장 위험한 경로를 차단하는 리스크 저감(risk reduction)**입니다. 3가지 조건이 모두 갖춰진 상태가 인젝션(injection)에 가장 무방비하므로, 그 조합을 설계 단계에서 만들지 않는 것입니다. 런타임(runtime) 시 사람의 판단에 맡기지 않고, 경계로서 아예 박아 넣습니다.

계층 3: backup — 복구 측의 최종 방어선 (git으로는 되돌릴 수 없는 것)

예방(prevention, hook + sandbox)은 지금까지 살펴본 바와 같이 둘 다 우회(bypass) 사례가 존재합니다. 그렇다면 논리적인 귀결은 하나입니다. **결국은 백업(backup)**입니다. 예방을 뚫고 나온 파괴는 복구(recovery)로만 되돌릴 수 있습니다.

여기서 작용하는 것이 서두에 언급한 위협 지도(threat map)의 ③(git 외부의 운영 데이터)과 ④(이력 파괴)입니다.

  • git 외부의 운영 데이터(Sheets / DB / SaaS)는 git으로 되돌릴 수 없습니다. 그리고 간과하기 쉽지만, Google Workspace 자체가 데이터를 백업하지 않습니다. 휴지통(Trash)은 30일간 유지되며 그 이후에는 완전히 소멸합니다. 버전 기록(version history)이나 Vault도 백업은 아닙니다(보존과 복구의 보증이 다릅니다). "클라우드에 있으니 안심"이라는 생각은 오류입니다. git 이력의 파괴(force push / reset --hard)는 git으로 복구한다는 전제 자체를 무너뜨립니다.

실질적인 피해는 상상이 아닙니다. 2026년 4월에는 AI 에이전트가 기업의 DB를 9초 만에 전부 삭제한 사례가 보도되었습니다(2차 보도를 기반으로 하여 1차 확인까지는 되지 않았으나, "있을 수 없는 이야기"는 전혀 아니라는 온도감으로 전달합니다).

그래서 저는 업무 전체가 올라가는 단일 스프레드시트의 네이티브(Drive 상의 복제본) 일일 백업을 구축했습니다. 설계 포인트는 세 가지입니다.

  • 네이티브 복사(내보내기가 아닌 Drive 상의 복제)를 통해 수식과 서식을 완전히 유지합니다. 내보내기(export)는 용량 제한이 있고 수식도 유실됩니다.
  • **멱등성(idempotency)**을 갖춥니다(같은 날의 백업이 이미 있다면 만들지 않음). 무인으로 매일 실행되므로, 폭주하듯 복제본이 늘어나지 않는 설계가 필요합니다.
  • launchd로 무인 실행하며, 생성 후에 "네이티브 Sheet로 만들어졌는지"를 실제 mimeType으로 검증합니다(silent success를 허용하지 않음).

무인 실행의 골격은 이것뿐입니다(macOS launchd, 매일 23:00에 백업 스크립트 실행).

<!-- ~/Library/LaunchAgents/<label>.plist의 요점만 발췌 -->
<key>ProgramArguments</key>
<array><string>/path/to/uv</string><string>run</string><string>python</string>
...

백업 스크립트 본체(약 80줄 정도, 네이티브 복사 + 멱등성 + mimeType 검증)는 기사 말미의 범위를 벗어나지만, 핵심은 "날짜로 유일한 이름을 만들고, 동일한 이름이 없을 때만 복사하며, 생성 결과의 mimeType을 단언(assert)한다"는 것뿐입니다.

이상적으로는 3-2-1(3개의 복사본, 2개의 매체, 1개는 offsite/immutable)까지 가야 하며, 백업 자체도 불변(immutable) 상태여야 랜섬웨어(ransomware)나 에이전트에 의해 삭제되지 않습니다. 저의 현 상황은 동일 계정 내의 별도 폴더 복사(가장 빈번한 위협인 오삭제/파괴에 대응) 단계이며, 크로스 프로바이더(cross-provider) 보험은 고객 데이터 복제의 프라이버시 설계가 확정될 때까지 보류 중입니다. 이 부분은 "해냈다"라고 과장하지 않고, 도달한 단계까지 솔직하게 적어둡니다.

임의 코드는 "prompt"가 아니라 "상자에 가두어 자유 실행"으로

위협 지도의 ⑥(임의 코드 실행)로 돌아갑니다. python -c "..."이나 uv run은 내용에 따라 ①~④ 중 무엇이든 될 수 있는 입구입니다. 소박한 발상은 "위험하니까 매번 프롬프트(prompt)를 묻는다"이지만, 무인 운용에서는 프롬프트가 무력하며, 자리에 앉아 있더라도 승인 피로(approval fatigue)를 초래합니다.

발상을 바꿉니다. **임의 코드(arbitrary code)는 "위험하니까 멈춘다"가 아니라 "상자(sandbox)에 가두어 자유롭게 실행한다"**입니다. sandbox (계층 2) 안에서 실행하면, 내용물이 무엇이든 쓸 수 있는 장소와 연결할 대상은 OS가 제한합니다. 여기서 효과적인 것이 auto-allow입니다. sandbox.autoAllowBashIfSandboxed: true ( /sandbox 패널에서도 설정 가능)를 사용하면, sandbox 내에서 완결되는 Bash 명령어를 prompt 없이 통과시킵니다 ("상자가 경계를 보장하고 있으므로 자동 승인해도 된다"는 발상입니다. 프롬프트를 분류기(classifier)로 줄이는 auto mode와는 별개의 개념입니다). 이를 켜면 python -c를 friction(마찰) 제로 상태로 돌리면서도 봉쇄(containment)가 작동합니다.

단, "상자에 넣었다 = 안전하다"는 것은 앞서 언급한 "다층으로 보완한다" 절에서 설명했듯이 아닙니다. auto-allow를 사용하더라도 명시적 deny는 계속 작동하며, .envdenyRead로 별도로 막아야 하고, sandbox를 벗어난 파괴에는 backup이 필요합니다. 임의 코드를 자유롭게 실행해도 되는 것은, 그 다층 방어 체계가 갖춰졌을 때뿐입니다.

이용자 타입별 최소 구성

"모두에게 적용되는 정답"은 존재하지 않습니다. 위협의 구성이 다르면 최적의 구성도 다릅니다. 제가 정리한 내용은 다음과 같습니다.

이용자주요 위협권장 구성backup 기준
개인·로컬 개발⑤가역 중심·가끔 ⑥hook (위험 deny) + sandbox auto-allow. git으로 거의 복구 가능git으로 거의 충분 (git 외 데이터를 보유하지 않는다면)
업무 데이터를 다룸③git 외 데이터위 구성 + 운영 환경(production) write는 사람의 승인 게이트 + backup. sandbox가 API를 통한 공격을 막지 못한다는 점에 주의동일 계정의 멱등(idempotent) 복사가 최소한으로 필요
무인 운용①~④ 전부 (oracle 부재)(prompt를 0으로 만들면서 dontAsk mode + sandbox auto-allow, deny 안전망은 유지 · deny → 자기 수정으로 지속) + network allowlist + backup. skip-permissions는 사용하지 않음cross-provider / immutable 수준까지 필요
설정의 불철저managed settings (조직 배포)로 deny/sandbox를 강제. 개인에게 맡기지 않음조직의 backup 정책을 따름
초보자무엇이 위험한지 모름sandbox auto-allow만 On. 설치가 필요 없고, 우선 상자에 넣는 것부터 시작git 관리부터

무인 운용의 핵심은 skip-permissions가 아니라 dontAsk입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0