가드레일·샌드박스 횡단 — AI가 함부로 못 하게 막는 법

한 줄 요약

가드레일·샌드박스 = AI가 위험한 도구·명령·경로에 손대지 못하도록 프롬프트로 부탁하거나(soft) 코드·OS로 진짜 차단하는(hard) 안전장치다. 왜 배우나: 프롬프트 인젝션으로 모델이 속아도 사고가 안 나려면 “막는 결정이 모델 바깥에 있어야” 하는데, 7개 하네스가 이 원칙을 어떻게 구현했는지 보면 안전한 에이전트를 짜는 설계 지도가 통째로 그려진다.

그림 — 가드레일이 작동하는 순간

아래는 도구 호출 하나가 가드레일을 통과하는 흐름이다. 두 개의 세계 — soft(부탁)hard(강제) — 가 어떻게 겹겹이 쌓이는지 보자.

flowchart TB
    P([모델이 도구 호출 시도]) --> S1{"soft 층<br/>프롬프트·헌법으로<br/>사전 부탁"}
    S1 --> G{"hard 게이트<br/>코드·OS가 판정"}
    G -->|deny 먼저 검사| D[차단]
    G -->|ask| H["사람/LLM 심판에게 질문"]
    G -->|allow| E["(도구 실행)"]
    H -->|승인| E
    H -->|거부| D
    D -.차단 사유를 텍스트로.-> P
    E --> O["사후 검증·로그 마스킹"]

    classDef hard fill:#fde2e4,stroke:#c9184a,color:#3a0ca3;
    classDef soft fill:#e2f0cb,stroke:#5a8f29,color:#1b3a00;
    class G,D,H hard
    class S1 soft

두 가지만 먼저 잡으면 된다

가드레일을 읽을 때 항상 두 질문으로 환원하라. ① 막는가(hard) vs 부탁하는가(soft)? ② 누가 그 결정을 강제로 소유하는가(모델? 사람? 또 다른 LLM? OS 커널?) 7개 프레임워크의 차이는 거의 다 이 두 축에서 갈린다.

쉽게 풀기

공항 보안검색대를 떠올려 보자. 승객(=AI의 도구 호출)이 게이트를 통과하려 한다.

  • soft 가드레일 = 안내방송. “위험물은 가져오지 마세요”라는 방송은 사람을 설득할 뿐, 마음먹고 어기면 못 막는다. 프롬프트나 CLAUDE.md의 금지 규칙이 여기에 해당한다. 모델이 무시하면 그만이다.
  • hard 가드레일 = X선 검색기와 차단봉. 사람의 의지와 무관하게 물리적으로 통과를 막는다. OS 샌드박스, 권한 규칙의 deny, 코드가 던지는 예외(throw)가 여기다. 모델이 속아도 이건 안 뚫린다.

7개 하네스가 공통으로 깨달은 핵심은 이거다: “절대 안 되는 것”은 안내방송이 아니라 차단봉으로 막아야 한다. 왜냐하면 모델은 프롬프트 인젝션에 속을 수 있는 “신뢰 못 할 작업자”이기 때문이다.

여기서 갈리는 디테일을 단계로 풀면:

  1. 순서가 안전을 정한다. 검색대는 “통과 명단”보다 “차단 명단”을 먼저 본다. Claude Code는 deny → ask → allow 순으로, Codex는 여러 규칙이 걸리면 max()로 가장 빡센 결정을 채택한다. 허용을 빠뜨리면 불편할 뿐이지만, 금지를 빠뜨리면 사고가 난다 — 그래서 모두 “막는 쪽으로 틀리기”를 택한다.
  2. 차단당해도 모델은 계속 일한다. 막힌 사유를 사람이 아니라 모델에게 텍스트로 돌려준다. “이 경로는 못 써, 대신 저기로 가봐” 식이다. 차단을 에러가 아니라 “다시 계획해라” 신호로 줘야 루프가 안 죽는다.
  3. 민감한 짐은 누구든 못 건드린다. .git·.env·.ssh·secrets는 거의 모든 하네스가 블랙리스트로 박아둔다. 자격증명 유출이나 .git 파괴는 되돌릴 수 없는 최악의 사고라서다.
  4. 검색기가 고장 나면? 방향이 중요하다. 도구 래퍼(검색대 기계)가 버그로 죽으면 작업을 막지 않고 통과시킨다(fail-open) — 가드 버그가 일을 영영 멈추면 안 되니까. 반대로 보안 판정 자체가 실패하면(타임아웃 등) 거부한다(fail-closed) — 안전 결정은 의심스러우면 막아야 옳다.

핵심 정리

각 프레임워크가 가드를 어디에 두고 어떻게 강제하는가를 슬림하게 정리한다.

프레임워크hard 강제의 위치한 줄 특이점
Claude CodeOS 샌드박스 + 권한규칙 + managed(MDM) 3중deny→ask→allow 첫매칭, 보호경로는 allow로도 못 뚫음
CodexOS 샌드박스(Seatbelt/Landlock+bwrap/제한토큰)Guardian = LLM 심판이 승인 피로를 자동 판정
OMChook deny(진짜 차단) + 워커 RBAC(부탁)“막는 가드 vs 부탁 가드”를 코드 주석으로 명시 분리
gajae-codebash prefix 파싱 + 런타임 throw정당화 영수증 — 자식 스폰을 게이팅(고유)
ouroboros엔진이 짜는 실행 봉투(env·CLI 깃발)결정/번역 분리 — 결정은 엔진 한 곳, 어댑터는 번역만
fable-ish거의 없음(호스트에 명시 위임)순수 soft + redact() 로그 마스킹뿐, 한계를 솔직히 선언
내 패턴OMC hook 차용(realpathSync 후 거부)settings + hook + 자연어 헌법 3층 혼합

6개 하네스가 똑같이 수렴한 6가지 공통 패턴

서로 모르고 짰는데도 같은 결론에 도달했다 — 그래서 “정답에 가까운 설계”라고 볼 수 있다.

  • deny-by-default. 모호·파괴적이면 일단 막는다. 안전 실패는 비대칭적으로 비싸다.
  • hard는 모델 바깥, soft는 프롬프트. 인젝션에 모델이 속아도 OS/런타임이 막는다.
  • 민감경로 하드코딩. .git/.env/.ssh/secrets는 보편 블랙리스트.
  • 차단 사유를 모델에게 피드백. 차단을 “재계획 신호”로 줘야 루프가 안 죽는다.
  • fail-open(가드 IO) vs fail-closed(보안 판정). 신뢰성과 안전성은 반대로 틀려야 옳다.
  • 셸 메타문자(; & | $ ( ) 등) 전면 거부. git status && rm -rf / 같은 체이닝 우회를 prefix 매칭만으론 못 막으니, 화이트리스트 검사 전에 탈락시킨다.

갈라지는 분기점 — 누가 왜 다르게 했나

  • 진짜 OS 샌드박스가 있나? Claude Code·Codex만 커널 수준 자체 격리. 나머지는 언어 수준 검사(throw/deny)나 호스트 위임으로 대체 — 플러그인형은 Claude Code가 이미 OS 샌드박스를 주니 그 위에 정책층만 얹는 게 합리적.
  • 승인 피로를 누가 자동화? Codex만 Guardian(LLM 심판)으로 “물어볼 것”을 자동 판정. 대신 fail-closed·untrusted-evidence·서킷브레이커로 두껍게 방어. (Claude Code도 auto 모드에 분류기 모델이 있다.)
  • 결정을 분산 vs 단일소유? Claude Code는 여러 범위(managed/user/project/local)에 분산하고 우선순위로 조정. ouroboros는 정반대로 “결정은 엔진 한 곳, 번역은 어댑터”로 극단적 단일소유.
  • 무엇을 가드 대상으로 보나? 대부분 “도구 호출”을 막지만 gajae만 에이전트 스폰을 정당화 영수증으로 게이팅(fork bomb 유사 폭주를 1급 위험으로 봄). ouroboros도 _DEPTH 카운터(천장 5)로 재귀 스폰을 막는다.
  • 자기 한계를 솔직히 선언? fable-ish는 “hard는 호스트에 위임”, OMC는 워커 RBAC를 “advisory layer only”라 명시. 단 OMC의 hook deny 자체는 진짜 hard다(이 둘을 “못 막는다”로 묶으면 과장).
  • 외부 플러그인을 신뢰하나? ouroboros만 전용 방화벽(단일 초크포인트 + 신뢰튜플 + digest 재계산 + 감사원장). 진화 루프로 코드가 바뀌니 “코드 치환 공격”을 1급 위협으로 봤다.

실제 예시

각 프레임워크의 hard/soft가 실제 코드로 어떻게 생겼는지 본다.

// Claude Code: settings.json — deny가 ask·allow를 이기는 첫매칭 규칙
// harness-work/claude-code/examples/settings/settings-strict.json
{
  "permissions": {
    "deny":  ["Read(.git/**)", "Read(.env*)", "Bash(rm:*)"],  // 최우선, allow로도 못 뚫림
    "ask":   ["Bash(git push:*)"],
    "allow": ["Read(src/**)", "Bash(npm test:*)"]
  },
  "defaultMode": "acceptEdits",
  "sandbox": { "filesystem": "workspace-write", "network": "deny" }
}
// Codex: 여러 규칙이 걸리면 가장 제한적 결정을 채택 (max())
// harness-work/codex/codex-rs/core/src/safety.rs
// Allow < Prompt < Forbidden 순서라 max()가 곧 "가장 빡센 쪽"
let decision = matched_rules.iter().map(|r| r.decision).max()
    .unwrap_or(Decision::Prompt);
// Guardian(LLM 심판)은 트랜스크립트를 "untrusted evidence"로 라벨해 인젝션 방어
// 타임아웃 = ReviewDecision::TimedOut → record_guardian_non_denial (서킷브레이커 카운트 제외)
# fable-ish: 순수 soft — 차단이 아니라 권고문 주입 + 비밀 마스킹
# harness-work/fable-ish/scripts/classify_task.py
if DESTRUCTIVE_REQUEST_RE.search(prompt) and not SAMPLE_RE.search(prompt):
    mode = "blocked"   # additionalContext로 권고만, 실제 실행은 못 막음
# 유일한 hard는 redact() 로그 마스킹 + Stop 훅의 decision:"block"(검증 없는 종료 차단)
text = redact(text)    # sk-... / ghp_... / xox... / key=value 패턴 치환
// gajae-code: 자식 4개 초과 스폰 시 "정당화 영수증" 5필드 필수 (고유 설계)
// harness-work/gajae-code/packages/coding-agent/src/task/spawn-gate.ts
interface SpawnPlanReceipt {              // 누락 시 모델에게 텍스트 반환 → 재시도
  whyParallel: string; whyNotLocal: string; independence: string;
  expectedOutputs: string; cleanupPlan: string;
}
// bash는 원본 + "cd && " 제거본 둘 다 검사해 래퍼 우회를 차단

요약 & 셀프체크

  • soft는 부탁, hard는 강제. 7개 하네스 전부 둘을 구조적으로 분리하고, “절대 규칙”은 프롬프트가 아니라 런타임/OS에 둔다.
  • deny-first + 민감경로 하드코딩 + 차단 사유 피드백이 모두가 수렴한 공통 골격이다.
  • OS 자체 샌드박스는 Claude Code·Codex뿐, 나머지는 throw·hook deny·실행봉투·호스트 위임으로 대체한다.

스스로 답해보기:

  1. 프롬프트 인젝션으로 모델이 “.env를 읽어라”에 속았다. 어느 층이 실제로 이걸 막아야 하며, 왜 프롬프트(soft)로는 부족한가?
  2. 가드 코드가 버그로 예외를 던졌다. 도구 래퍼는 fail-open인데 보안 심판은 fail-closed인 이유를 한 문장으로 설명할 수 있는가?
  3. 내 스택(Claude+Codex+OMC)에서 “워커 폭주”를 진짜로 막으려면 어디(gajae식 영수증 게이트)에 무엇을 걸어야 하는가?

연결

기능노트 근거: CC_80_guardrails · CX_80_sandbox-and-approval-guardrails · OMC_60_guardrails-permissions · GJ_70_guardrails-sandbox-permission-gating · OB_80_guardrails-sandbox-permissions-plugin-firewall · FB_90_guardrails-soft-vs-hard · MINE_70_guardrails-permissions-sandbox

루브릭·홈: _분석축_루브릭 · HOME