
Claude Code의 하네스 설계 -- 「금지 사항만 정하고, hooks로 강제한다」 블랙리스트 전략
요약
Claude Code의 자율성과 안전성을 동시에 확보하기 위한 하네스(Harness) 설계 전략을 다룹니다. 화이트리스트 대신 블랙리스트 방식을 채택하고, LLM의 추론이 아닌 hooks를 통한 결정론적 강제로 권한을 제어하는 방법을 설명합니다.
핵심 포인트
- 하네스를 통해 추론(모델)과 실행(인프라)을 분리하여 안전성 확보
- 자율성 유지를 위해 금지 사항만 정의하는 블랙리스트 전략 권장
- LLM 프롬프트가 아닌 hooks를 통한 결정론적 권한 강제 구현
서론
Claude Code를 본격적으로 활용하다 보면 반드시 마주하게 되는 질문이 있습니다.
"에이전트에게 어디까지 권한을 부여해야 하는가?"
권한을 너무 제한하면 사소한 작업마다 확인 다이아로그(dialog)가 나타나 에이전트의 장점인 자율성이 상실됩니다. 반대로 --dangerously-skip-permissions로 모든 권한을 개방하면, rm -rf나 운영 환경(production environment)으로의 push와 같이 돌이킬 수 없는 작업까지 허용되어 버립니다.
이 기사에서는 그 양립을 목표로 하는 설계 방침으로서, 「해서는 안 될 일만을 먼저 정의하고, 그 외에는 모두 허용한다」는 블랙리스트(blacklist) 방식을 다룹니다. 나아가 그 금지 체크를 LLM의 프롬프트(prompt)에 맡기지 않고, **hooks를 통한 결정론적인 강제(deterministic enforcement)**로 가져가는 이유와 방법을 구현 예시와 함께 해설합니다.
하네스(Harness)란 무엇인가 -- 「생각하는 측」과 「실행하는 측」의 분리
먼저 전제로 Claude Code의 「하네스(harness)」라는 용어를 정리해 두겠습니다.
하네스란 LLM 본체를 둘러싸고, 이를 제어 가능한 에이전트로 변환하는 인프라(infrastructure) 계층을 말합니다. Claude Code에서는 역할이 깔끔하게 이분되어 있습니다.
모델 (Claude): "다음에 무엇을 해야 할지"를 추론하고, 도구 호출(tool_use)을 출력한다 -
하네스: 그 출력을 받아 권한(permission)을 검사하고, 도구를 실제로 실행하며, 결과를 회수한다
중요한 점은, 모델은 스스로 파일 시스템을 건드리거나, 셸(shell)을 실행하거나, 네트워크에 접속하지 않는다는 점입니다. 모델은 어디까지나 "이렇게 하고 싶다"라는 의도를 출력할 뿐이며, 실행 권한을 쥐고 있는 것은 하네스 측입니다.
이 분리가 안전성 설계상 매우 효과적입니다. 추론(reasoning)과 강제(enforcement)가 서로 다른 코드 경로(code path)를 타기 때문에, 설령 모델이 적대적인 프롬프트(adversarial prompt)에 속아 폭주하더라도 하네스 측의 권한 검사나 샌드박스(sandbox)를 덮어쓸 수는 없습니다. "생각하는 측"을 아무리 속여도 "실행하는 측"의 게이트는 별개의 논리로 닫혀 있다는 구조입니다.
블랙리스트 방식도 hooks도 이 "하네스 측에 강제력을 둔다"라는 발상의 연장선에 있습니다.
왜 화이트리스트가 아니라 블랙리스트인가
권한 제어에는 크게 두 가지 접근 방식이 있습니다.
| 방식 | 사고방식 | 성질 |
|---|---|---|
| 화이트리스트 (allow 리스트) | 허용할 것만 나열하고 그 외에는 모두 금지 | 안전 측에 치우치지만 경직됨 |
| 블랙리스트 (deny 리스트) | 금지할 것만 나열하고 그 외에는 모두 허용 | 유연하지만 나열 누락에 취약함 |
화이트리스트 방식은 견고합니다. 하지만 에이전트가 수행하기를 바라는 작업은 무한합니다. "npm test를 허용", "git status를 허용", "ls를 허용"……라고 계속 나열하다 보면 허용 리스트가 따라잡지 못해 결국 확인 다이아로그의 폭풍을 맞게 됩니다. 에이전트의 최대 가치인 자율성이 깎여 나가는 것입니다.
그래서 본 기사에서 권장하는 것이 블랙리스트 방식을 기조로 삼는 발상입니다. 해서는 안 될 일을 먼저 골라내어 deny 해두면, 안 되는 것 이외에는 모두 가능해집니다.
개발 작업에서 "절대로 해서는 안 될 일"은 사실 그리 많지 않습니다.
- 돌이킬 수 없는 파괴 작업 (
rm -rf /, 보호 브랜치로의git push --force등) - 운영 환경 및 운영 데이터베이스에 대한 직접 조작
- 인증 정보 (
.env, 비밀키,~/.aws/credentials등)의 읽기 및 외부 전송 - 과금이나 외부 공개를 동반하는 조작 (부주의한 배포, 리소스 생성)
이러한 것들은 수십 개 항목 정도면 대략 커버할 수 있습니다. "무한히 존재하는 '해도 되는 일'을 나열하는" 것이 아니라, "유한한 '해서는 안 될 일'을 나열하는" 편이 현실적이고 유지보수하기 쉽습니다.
그리고 그 유한한 금지 리스트만 지켜진다면, 나머지 광대한 영역은 에이전트가 자유롭게 움직일 수 있습니다. 이것이 블랙리스트 방식의 목표입니다.
Claude Code의 권한은 원래 deny 우선
이 발상은 Claude Code의 설계와도 맞닿아 있습니다. Claude Code의 권한 규칙은 allow / ask...
/ deny
의 3가지 종류가 있으며, 평가 순서는 deny → ask → allow 입니다. 가장 먼저 매칭된 규칙이 승리하며,
deny
는 항상 최우선으로 평가됩니다.
{
"permissions": {
"deny": [
...
deny
에 나열한 것만이 확실하게 차단되며, 그 외에는 모드에 따라 동작합니다. 그야말로 블랙리스트의 토대가 표준으로 준비되어 있는 셈입니다.
블랙리스트의 약점 -- LLM의 판단은 긴 컨텍스트에서 흔들린다
블랙리스트 방식의 약점은 명확합니다. "나열한 금지 사항이 정말로 매번 체크되는가"라는 한 가지 점에 귀결됩니다.
여기서 소박한 구현 방식으로서, 금지 사항을 CLAUDE.md나 시스템 프롬프트(System Prompt)에 자연어로 적어두고, "이러한 조작은 하지 마세요"라고 모델에게 지시하는 방법을 생각할 수 있습니다. 실제로 가벼운 운용에서는 이것만으로도 충분히 기능합니다.
하지만 이 "LLM에게 금지 사항의 판단을 맡기는" 방식에는, 컨텍스트(Context)가 불어나면서 효과가 없어지는 구조적인 문제가 있습니다.
Lost in the Middle -- 길어질수록 중간의 지시가 희미해진다
LLM에는 컨텍스트가 길어질수록 성능이 저하되는 컨텍스트 저하 (context degradation) 현상이 알려져 있습니다. 그중에서도 유명한 것이 **"lost in the middle"**입니다.
이는 입력 컨텍스트의 앞부분이나 뒷부분에 놓인 정보는 높은 정밀도로 다룰 수 있는 반면, 중간에 놓인 정보의 추출 정밀도가 현저히 떨어지는 주의(attention)의 편향을 말합니다. 디코더형 트랜스포머(Decoder-type Transformer)의 인과적 마스킹(causal masking)에 기인한 선두 토큰에 대한 과도한 주의(attention sink) 등이 원인으로 꼽힙니다.
연구에 따르면, 모델은 긴 컨텍스트 하에서 "답이 무엇인가(what)"라는 의미 이해는 유지할 수 있어도, "답이 어디에 있는가(where)"라는 공간적인 파악은 잃어버린다고 설명됩니다.
이를 에이전트(Agent)의 문맥으로 바꾸어 말하면 다음과 같습니다.
- 세션 시작 직후,
CLAUDE.md의 금지 사항은 컨텍스트의 앞부분 근처에 있어 잘 작동한다. - 하지만 작업이 진행되어 도구 실행 결과나 파일 내용으로 컨텍스트가 수만 토큰으로 불어나면, 금지 사항은 저 멀리 상단의 "중간"으로 밀려난다. - 그 상태에서 모델이 금지 사항을 성실하게 떠올려 체크해 줄 것이라는 보장은 더 이상 없다.
긴 컨텍스트에서는 거부 동작 자체가 불안정해진다
더 깊이 들어간 연구에서는, 긴 컨텍스트의 LLM 에이전트에서는 거부 (refusal) 동작 자체가 불안정해진다는 점이 지적되고 있습니다. 프롬프트가 길어지면 초기 단계에서의 위협 탐지가 둔해지고, 모델은 "작업을 일부 진행해 버린 뒤에" 발동하는 후반부 안전 기제에 의존할 수밖에 없게 됩니다. 즉, 긴 컨텍스트의 에이전트에게는 입력 시점의 거부에만 의존할 것이 아니라, 중간의 각 액션(Action)을 감시하는 가드레일(Guardrail)이 별도로 필요하다는 뜻입니다.
여기서 도출되는 결론은 심플합니다. 금지 사항 체크를 확률적으로 흔들리는 LLM의 판단에 의존해서는 안 됩니다. 컨텍스트의 길이와 관계없이 매번 확실하게 발동하는 결정론적인 메커니즘으로 교체해야 합니다.
그 결정론적인 메커니즘은 사실 두 가지가 있습니다. settings.json의 deny 규칙과, hooks (PreToolUse)입니다. 둘 다 하네스(Harness)가 강제하기 때문에 컨텍스트 길이에 좌우되지 않습니다. 우선 두 가지의 용도 구분부터 정리하겠습니다.
deny로 쓸 수 있는 것은 deny로
여기서 강조하고 싶은 점은, 금지 사항 전부를 훅(Hook)으로 작성할 필요는 없다는 것입니다. 앞서 언급했듯이 Claude Code의 deny 규칙도 "하네스가 강제한다 = 모델의 추론 경로 밖에 있다"에 위치하며, hooks와 마찬가지로 결정론적으로 작동합니다. lost in the middle의 영향을 받지 않는 것은 deny 규칙도 마찬가지입니다.
분기점은 "글로브 패턴(Glob pattern)으로 표현할 수 있는 정적인 금지인가, 실행 시에만 알 수 있는 문맥 의존적인 금지인가"라는 점입니다.
settings.json의 deny 규칙 | PreToolUse 훅 | | |---|---|---| | 결정론적인가 | ✅ (하네스가 강제) | ✅ (반드시 발화) | | 작성 방식 | 선언적 (JSON으로 한 줄) | 셸 스크립트 | | ... | Bash(rm -rf *), Bash(git push --force*), Read(./.env) |
규칙(Glob pattern)으로는 다 표현할 수 없는, 문맥 의존적인 복잡한 조건도 작성할 수 있습니다. 예를 들어 "현재 브랜치가 main일 때만 push를 중단한다", "명령어에 운영 환경 호스트 이름이 포함되어 있을 때만 확인 단계를 거친다"와 같은 판단입니다.
중요 -- hooks는 LLM의 상태에 좌우되지 않는다
그리고 이것이 최대의 이점입니다.
PreToolUse 후크는, 컨텍스트가 아무리 길더라도, 모델이 아무리 "잊어버리고" 있더라도, 도구가 실행되기 직전에 반드시·매번 발화합니다. 판단하는 것은 LLM이 아니라, 정해진 셸 스크립트(Shell script)입니다. Lost in the middle 현상도, 긴 컨텍스트에서의 거부 불안정성도, 여기에는 전혀 관계가 없습니다.
게다가 공식 문서에 따르면, deny / ask 규칙은 PreToolUse 후크의 반환값과는 독립적으로 평가되며, --dangerously-skip-permissions가 생략하는 것은 대화형 확인 다이얼로그일 뿐, 후크 자체는 스킵되지 않습니다. 즉, 후크에 배치한 금지 체크는 권한 모드를 완화하더라도 살아남습니다.
이것이 "금지 사항 체크를 컨텍스트에 좌우되지 않는 결정론적인 계층(deny 규칙과 후크)으로 옮긴다"라는 방침의 핵심입니다. 유연함(블랙리스트로 넓게 허용)과 견고함(결정론적으로 확실하게 차단)을 동시에 얻는 것입니다.
구현 예시 -- 금지 사항 체크 후크 작성하기
실제로 금지 사항을 모아서 체크하는 PreToolUse 후크를 작성해 보겠습니다.
스크립트 본체
.claude/hooks/guard.sh를 준비합니다. jq로 stdin을 읽고, 금지 패턴에 걸리면 deny를 반환합니다.
#!/bin/bash
# .claude/hooks/guard.sh -- 금지 사항을 모아서 체크하는 PreToolUse 후크
INPUT=$(cat)
...
포인트는 끝부분입니다. 금지 패턴에 해당하지 않는 경우에는 아무것도 출력하지 않고 exit 0을 수행할 뿐입니다. 이를 통해 defer(통상적인 흐름) 취급되어, 에이전트는 자유롭게 작업을 계속할 수 있습니다. 블랙리스트의 "안 되는 것 이외에는 전부 허용"이 그대로 코드에 반영되어 있습니다.
settings.json에 등록하기
이 스크립트를 PreToolUse 후크로 등록합니다. matcher를 비워두거나(또는 모든 도구 대상) 하여, 모든 도구 호출을 한 번 후크를 거치게 합니다.
{
"hooks": {
"PreToolUse": [
...
]
}
}
실행 권한 부여(chmod +x .claude/hooks/guard.sh)를 잊지 마세요. 이렇게 하면 Claude가 어떤 도구를 호출하려 해도 실행 전에 반드시 guard.sh를 통과하게 됩니다.
보충 -- exit code 2로 차단하는 방법
JSON을 반환하는 대신, exit code 2로 종료하고 stderr에 메시지를 출력하는 방식으로도 차단할 수 있습니다(stderr의 내용이 Claude에게 전달되며, 도구는 실행되지 않습니다). 정중하게 이유를 반환하고 싶다면 JSON 방식을, 빠르게 중단하고 싶을 뿐이라면 exit 2 방식을 사용하는 식으로 구분하여 사용할 수 있습니다.
if echo "$CMD" | grep -Eq 'rm[[:space:]]+-[a-z]*r[a-z]*f'; then
echo "파괴적인 rm -rf는 차단되어 있습니다" >&2
exit 2
fi
...
패턴으로 막는 계층의 한계 -- "매번 발화한다"와 "뚫리지 않는다"는 별개
여기서 후크에 과도한 신뢰를 두기 전에 냉정하게 짚고 넘어가야 할 점이 있습니다. "결정론적으로 매번 발화한다"는 것과 "뚫리지 않는다"는 것은 완전히 별개의 문제라는 점입니다.
PreToolUse 후크는 확실히 매번 반드시 발화합니다. 하지만 그 판단 로직이 앞서 살펴본 grep -Eq 'rm[[:space:]]+-[a-z]*r[a-z]*f'와 같은 **명령어 문자열의 패턴 매칭(Pattern matching)**인 이상, 그 본질은 역시 블랙리스트이며, 나열 누락에 취약하다는 숙명에서 벗어날 수 없습니다. 예를 들어 다음의 경우들은 모두 해당 정규 표현식을 통과합니다.
rm --recursive --force ./important
(플래그 철자 오류) -
find . -delete
/: > production.db`
(rm을 사용하지 않는 파괴) -
r\m -rf ./x
/$(echo rm) -rf ./x`
(셸을 통한 난독화) - 파괴 명령어를 스크립트에 작성하고
bash script.sh
로 실행
이는 기사 전반부에서 deny에 대해 언급한 "인자(argument)의 내용을 검사하는 용도로는 다 지켜낼 수 없다"라는 약점과 완전히 동일한 구조입니다. deny 규칙도 후크(hook)의 정규 표현식도, 결국은 "명령어 문자열을 실행 전에 읽어서 차단하는" 방식이며, 문자열을 얼마든지 변형할 수 있는 상대에게는 원리적으로 뚫립니다. 파일 읽기도 마찬가지로, 공식 문서에서는 Read/Edit의 deny 규칙에 대해 "Claude의 내장 도구 및 인식된 Bash 명령어(cat, sed 등)에는 효과가 있지만, Python이나 Node 스크립트가 자체적으로 파일을 여는 작업에는 효과가 없다"라고 명시하고 있습니다. 즉, 정말로 돌이킬 수 없는 작업 -- 자격 증명(credentials) 읽기 및 외부 전송, 홈 디렉토리 파괴 등 -- 을 확실히 막고 싶다면, "문자열을 검사하여 차단하는" 층 위에 "능력 그 자체를 박탈하는" 층을 겹쳐야 합니다.
지금까지 본 기사에서 살펴본 금지 수단들을 다시 정리하면 다음과 같습니다.
- 제1층:
CLAUDE.md의 자연어 규칙 (모델에 대한 방침 공유. 확률적) - 제2층:
settings.json의deny규칙 (정적인 글로브(glob) 패턴) - 제3층:
PreToolUse후크 (문맥 의존적 판정)
제2층과 제3층은 결정론적으로 발화하지만, 방금 보았듯이 둘 다 "명령어 문자열을 읽어서 차단하는" 방식이기에 문자열 변형에는 취약합니다. 이에 반해, 문자열을 전혀 보지 않고 능력 자체를 박탈하는 것이 다음에 설명할 **제4층 -- OS 레벨의 샌드박스(sandbox)**입니다.
제4층 -- OS 레벨의 샌드박스로 "능력 자체를" 박탈하기
Claude Code에는 Bash 명령어를 OS의 기제로 가두는 샌드박스가 내장되어 있습니다 (/sandbox로 활성화, 또는 settings.json의 sandbox.enabled). 이것이 블랙리스트 전략의 최후의 보루가 됩니다.
작동 방식이 본질적으로 다릅니다. deny 규칙이나 후크가 "앞으로 실행될 명령어 문자열"을 읽고 판단하는 반면, 샌드박스는 실행 중인 프로세스 그 자체를 OS의 primitives (Linux는 bubblewrap, macOS는 Seatbelt)로 격리합니다. 공식 문서의 표현이 핵심을 정확히 짚고 있습니다 -- "제한은 실행 중인 프로세스에 대해 강제되므로, 모델이 무엇을 실행하려 하든, 또한 허가된 명령어가 이름 이상의 행동을 하려 하든 경계는 유지된다". 애초에 패턴을 맞추려 시도하지 않기 때문에, 플래그의 철자 오류도, 난독화도, 자식 프로세스도 상관없습니다. 프롬프트 인젝션(prompt injection)으로 모델의 판단이 탈취되더라도, OS의 경계는 별개의 논리로 닫힌 상태를 유지합니다.
샌드박스가 강제하는 것은 두 가지 경계입니다.
파일 시스템 격리
기본적으로 쓰기 작업은 작업 디렉토리 하위로만 제한됩니다. ~/.bashrc나 시스템 바이너리 수정에는 닿지 않습니다.
다만 주의할 점이 있는데, 읽기는 기본적으로 넓게 허용되어 있어 ~/.aws/credentials나 ~/.ssh/도 읽을 수 있습니다. 자격 증명을 보호하려면 명시적으로 denyRead로 막아야 합니다.
{
"sandbox": {
"enabled": true,
...
이것이 효과적인 이유는, 앞 절에서 본 "후크나 Read의 deny로는 잡아낼 수 없는, 스크립트가 자체적으로 여는 읽기"에 대해서도 차단이 걸리기 때문입니다. OS 레벨이므로 Claude가 실행하는 모든 프로세스에 일률적으로 적용됩니다.
네트워크 격리 (egress allowlist)
그리고 이 부분이 블랙리스트 전략의 미결 과제에 대한 해답입니다. 기사 서두에서 금지 사항으로 언급한 "인증 정보의 외부 전송"은 사실 deny 규칙이나 후크로는 실질적으로 막을 수 없습니다. curl을 막아도 wget이 있고, wget을 막아도 스크립트에서 임의의 소켓을 열 수 있기 때문입니다.
이것을 실제로 막을 수 있는 유일한 계층이 샌드박스 (Sandbox)의 네트워크 격리입니다. 샌드박스 내의 통신은 외부 프록시 (Proxy)를 경유하며, 허가된 도메인 이외에는 도달할 수 없습니다 (기본적으로 사전 허가된 도메인은 0개이며, allowedDomains로 열거하고 deniedDomains로 개별 차단합니다). 송신처를 출구에서 제한하기 때문에, 설령 파일을 읽을 수 있게 되더라도 그것을 외부로 운반할 경로가 없는 형태로 만들 수 있습니다.
{
"sandbox": {
"enabled": true,
...
단, 여기서도 과신은 금물입니다. 이 내장 프록시는 TLS를 복호화하지 않으며, 클라이언트가 신고한 호스트 이름만으로 판정합니다. 따라서 github.com과 같이 넓은 도메인을 허용하면, 도메인 프런팅 (Domain Fronting) 등을 통해 허가 목록 외의 호스트에 도달할 여지가 남는다고 공식 문서에 명시되어 있습니다. 더 강력한 보장이 필요하다면, TLS를 종단(Terminate)하는 독자적인 프록시를 중간에 배치하는 상급자용 우회로도 마련되어 있습니다. 샌드박스 또한 "완전한 격리 경계는 아닙".
조직에서 강제하기 -- managed settings
개인의 settings.json은 본인이 언제든 완화할 수 있습니다. 팀이나 조직에서 "이 금지 사항만큼은 해제할 수 없게 하겠다"를 실현하려면, managed settings (MDM 등으로 배포하는 상위 스코프 설정)에 두어야 합니다. deny 규칙도 샌드박스도 managed settings에 두면 사용자 또는 프로젝트 설정으로는 덮어쓸 수 없습니다. sandbox.failIfUnavailable을 통해 샌드박스를 구동할 수 없는 환경에서는 Claude Code 자체를 구동시키지 않거나, allowManagedDomainsOnly로 송신처를 조직의 허가 목록으로 고정하는 등의 조치도 가능합니다. 이와 함께 CLAUDE_CODE_SUBPROCESS_ENV_SCRUB을 사용하여 자식 프로세스로부터 API 키 등의 환경 변수를 제거해 두면, 외부 송신 대책이 더욱 견고해집니다.
다층 방어로서 조합하기
지금까지의 내용을 정리하면, 실무에서는 다음 4개 계층을 조합하는 것이 견실합니다. 위로 갈수록 유연하고 (똑똑하지만 불확실함), 아래로 갈수록 딱딱해집니다 (융통성은 없지만 확실함).
| 계층 | 역할 | 작동 방식 |
|---|---|---|
CLAUDE.md의 자연어 규칙 | 모델에 대한 "방침" 공유. 불필요한 시도를 줄임 | 확률적 (긴 컨텍스트에서 저하될 수 있음) |
settings.json의 deny 규칙 | 정적인 글로브 패턴 (Glob pattern)을 통한 금지 | 결정론적으로 발화 (단, 문자열 변형에는 취약함) |
PreToolUse 훅 (Hook) | 문맥 의존적인 복잡한 금지 판단 | 결정론적으로 발화 (단, 문자열 변형에는 취약함) |
| OS 샌드박스 | 파일·네트워크 능력 박탈 | OS가 강제 (패턴에 의존하지 않으며, 누락에 강함) |
위의 2~3번 계층은 "명령어 문자열을 읽고 차단하는" 계층으로, 결정론적으로 발화하는 반면 문자열 변형에는 뚫릴 수 있습니다. 최하층인 샌드박스만이 "능력 그 자체를 박탈하는" 계층이며, 돌이킬 수 없는 조작에 대한 마지막 보루가 됩니다.
CLAUDE.md의 규칙은 무의미하지 않습니다. 모델이 처음부터 금지된 조작을 피한다면, 훅이나 deny에 의해 차단되어 시행착오를 겪는 낭비를 줄일 수 있습니다. 다만, 그것을 "마지막 보루"로 삼아서는 안 된다는 것이 핵심입니다. 마지막 보루는 컨텍스트 상태에 좌우되지 않는 결정론적인 계층 -- deny 규칙과 훅으로 패턴을 차단하고, 그 아래에서 능력 자체를 박탈하는 샌드박스 -- 에 두어야 합니다. 그리고 훅에 의한 deny는 단순한 "정지"가 아니라, 라우팅 신호로서 작동합니다. 차단된 모델은 permissionDecisionReason을 전달받아 "아, 이 경로는 안 되는구나"라고 이해하고, 다음 루프에서 다른 안전한 접근 방식을 시도합니다. 금지는 막다른 길이 아니라, 에이전트의 행동을 안전한 방향으로 정형화하는 메커니즘으로서 기능합니다.
요약
Claude Code의 하네스 (Harness) 설계에 있어, 유연성과 안전성을 양립시키는 현실적인 전략을 정리했습니다.
- 하네스 (Harness)는 「생각하는 측 (모델)」과 「실행하는 측」을 분리하고 있다. 강제력은 실행 측에 둘 수 있으므로, 모델이 폭주하더라도 효과가 있다.
- 화이트리스트 (Whitelist)는 경직되기 쉽다. "해도 되는 일"은 무한하며, 이를 열거하는 속도가 따라가지 못한다.
- 블랙리스트 (Blacklist) 방식이라면, 유한한 금지 사항만 정하면 된다. 그 외의 모든 것은 허용할 수 있어 에이전트 (Agent)의 자율성을 살릴 수 있다.
- 금지 체크를 LLM의 판단에 맡기면, 긴 컨텍스트 (Long Context)에서 성능이 저하된다. Lost in the middle 현상이나 거부 동작의 불안정성으로 인해, 중간에 밀려난 금지 사항은 확실하게 작동하지 않는다.
- 따라서 금지 사항은 결정론적인 (Deterministic) 계층에서 강제한다. 정적인 패턴은
settings.json의deny규칙으로, 글로브 (Glob)로 작성할 수 없는 문맥 의존적 판단은PreToolUse후크 (Hook)로 처리한다. 두 방식 모두 컨텍스트 길이에 상관없이 작동하며,--dangerously-skip-permissions옵션을 사용하더라도 후크는 스킵되지 않는다. - 단, 문자열을 검사하는 계층에는 한계가 있다.
deny와 후크 모두 "명령어 문자열을 읽고 차단하는" 방식이기에, 난독화나 다른 명령어로의 우회에는 뚫릴 수 있다. 정말로 막아야 하는 조작은 **OS 샌드박스 (Sandbox)**를 통해 능력 자체를 박탈해야 한다. 특히 "인증 정보의 외부 전송"은 송신 경로를 제한하는 egress allowlist로만 실질적으로 막을 수 있다. - 다층 방어 (Defense in Depth)로 구성한다.
CLAUDE.md는 불필요한 시도를 줄이기 위한 방침 공유,deny규칙과 후크로 패턴을 차단하며, 최후의 보루는 능력을 박탈하는 OS 샌드박스다.
"안 되는 것만 정하고, 그것을 기계적으로 지키게 한다. 나머지는 자유롭게 하게 둔다" --- 이 단순한 절충안이 에이전트를 안심하고 실행하기 위한 실용적인 설계의 타협점이라고 생각합니다.
참고 기사·데이터
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기