
Fail-Closed 하네스: 안전한 에이전트의 기본값이 거부인 이유
요약
안전한 AI 에이전트 구축을 위한 'Fail-Closed 하네스' 개념을 소개합니다. 정책 검증이 불가능하거나 불충분할 경우 시스템이 기본적으로 행동을 거부하도록 설계하여 보안 사고를 방지하는 가드레일 구축 전략을 다룹니다.
핵심 포인트
- Fail-Closed 하네스: 검증 실패 시 기본적으로 행동을 거부하는 설계 원칙
- 단순 프롬프트 기반 가드레일의 한계 지적
- 파일 생성, 세션 종료, 출력 스트림 관리 등 구체적인 통제 사례 제시
- 의도적이고 명시적인 행동을 통해서만 통과 가능한 '과속 방지턱' 설계 권장
이 가이드를 작성하는 동안, 저의 하네스(harness)가 저를 계속 거부했습니다.
첫 번째 거부는 제가 파일을 생성하려고 했을 때 발생했습니다. 게이트(gate)가 쓰기 작업을 가로챘고, 무엇이 파일을 가져왔는지, 영향 범위(blast radius)는 무엇인지, 어떤 데이터에 접근하는지, 그리고 운영자(operator)가 실제로 무엇을 요청했는지를 기록으로 명시할 때까지 통과시켜 주지 않았습니다. 저는 이 네 가지 사실을 작성했습니다. 그러자 재시도가 통과되었습니다. 이 현상은 이 가이드의 연구 내용을 담고 있는 파일들을 포함하여, 세션이 건드리는 모든 새로운 파일에서 발생했습니다.
두 번째 게이트는 세션이 종료되는 것을 거부했습니다. 저는 브랜치(branch)를 푸시하고 중단하고 싶었지만, 리뷰 파이프라인(review pipeline)을 실행하거나 왜 이 변경 사항에 리뷰가 필요하지 않은지를 서면으로 명시할 때까지 중단이 차단되었습니다. 세 번째의 더 조용한 게이트는, 제가 결과값에 따라 분기(branching)를 수행하는 명령에서 두 출력 스트림(output streams)을 모두 숨겼다는 점을 지적했습니다. 이는 "탐색 결과 아무것도 발견되지 않음"과 "탐색 도구가 고장 남"을 동일한 답변으로 뭉뚱그려 버리는 결과를 초래합니다.
이 중 어느 것도 벽(wall)은 아닙니다. 모든 것은 의도적이고 명시적인 행동을 통해 통과할 수 있는 과속 방지턱(speed bump)입니다. 그것이 핵심이며, 대부분의 가드레일(guardrail) 작성이 양방향 모두에서 잘못되는 지점이기도 합니다. 즉, 구축할 수 없는 벽을 약속하거나, 모델이 말로 빠져나갈 수 있는 프롬프트(prompt) 수준에 안주해 버립니다.
대상 독자: 개념 설명을 읽고, 다이어그램에서 가드레일 박스를 확인했으며, 이제 그 가드레일 박스가 실제로 제 역할을 하기를 원하는 에이전트 하네스(agent harness) 구축 또는 운영 엔지니어.
이를 Fail-Closed 하네스라고 부르고 정확하게 정의하십시오. 이 개념을 느슨하게 적용하는 것은 무용지물보다 더 나쁩니다.
Fail-Closed 하네스는 필수적인 정책 검사(policy check)나 권한 부여(authorization)를 평가할 수 없거나 충족할 수 없을 때, 중대한 결과가 따르는 행동을 거부합니다. 파괴적 명령 필터(destructive-command filter)에서 일치하는 항목이 없다는 것이 해당 명령이 안전하다는 증거는 아닙니다.
사람들이 건너뛰는 부분은 바로 그 두 번째 문장입니다. 이 문장을 꼭 기억하십시오.
"하네스 엔지니어링 (Harness engineering)"은 이제 하나의 독립된 분야가 되었으며, 2026년 중반까지는 그 주인이 정해질 것입니다. LangChain, Mitchell Hashimoto, Martin Fowler의 팀, 그리고 Lilian Weng은 모두 모델을 감싸는 소프트웨어를 지칭하는 용어로 이 단어를 선점했습니다. 가장 깔끔하고 짧은 정의는 LangChain의 Vivek Trivedy로부터 나옵니다:
하네스 (Harness)란 모델 그 자체를 제외한 모든 코드, 설정, 그리고 실행 로직을 의미한다.
이러한 글들의 대부분은 능력 (capability)에 초점을 맞춥니다: 더 나은 도구, 더 긴 메모리, 더 똑똑한 루프 (loop) 같은 것들 말입니다. 거의 모든 사람이 언급하고는 바로 건너뛰는 부분은 가드레일 (guardrails)입니다. 이 가이드는 바로 그 건너뛰어지는 부분에 관한 것이며, 한 가지 주장, 즉 가드레일은 나중에 덧붙이는 기능 (feature)이 아니라는 점에 관한 것입니다. 그것은 한 번 선택하여 모든 곳에 적용하는 기본값 (default)입니다.
하네스에서 'Fail-Closed'는 실제로 무엇을 의미하는가?
2026년 7월 23일, 세 개의 별도 게이트 (gate)가 하나의 글쓰기 세션을 방해했습니다. 그리고 그중 어느 것도 벽이 아니었습니다. 각각은 기본적으로 차단되어 있었고, 필요한 사항을 정확히 말했으며, 제가 요구 사항을 충족하자 통과되었습니다. Fail-closed (실패 시 차단)는 그러한 기본값을 일컫는 말입니다: 필수적인 확인 절차를 충족할 수 없을 때, 진행하는 대신 중단하는 것입니다.
이 가이드 전체를 관통하는 구분은 프롬프트 (prompt) 대 속성 (property)입니다. 권한 프롬프트는 요청 (request)입니다. 그것은 모델이나 지친 운영자에게 '예'라고 말해달라고 요청합니다. 신뢰할 수 없는 콘텐츠를 입력받은 모델은 '예'라고 말하도록 유도될 수 있으며, 당신 또한 그 유도에 휘말릴 수 있습니다. 왜냐하면 계획을 오염시킨 것과 동일한 컨텍스트 (context)가 프롬프트에 대한 근거를 작성하기 때문입니다. 에이전트에게 "제안만 하고, 절대 실행하지 마라"라고 말하는 것은 동일한 종류의 요청입니다: 시스템 프롬프트 (system prompt)에 포함된 문장일 뿐이며, 문장은 인터록 (interlock, 연동 장치)이 아닙니다.
어떤 속성은 모델 외부의 무언가에 의해 강제되며, 모델은 무엇이 통과(passing)로 간주될지를 재정의할 수 없습니다. 그 주장은 처음 들리는 것보다 더 좁은 의미를 담고 있으며, 도입부가 그 이유를 보여줍니다. 제가 한 단락을 작성한 후 사실 게이트(fact gate)가 통과되었으므로, 단락 하나가 결과적으로 영향을 미쳤음은 분명합니다. 하지만 그 단락이 할 수 없었던 것은 조건(condition) 자체를 바꾸는 것이었습니다. 게이트는 무엇이 언제 진술되어야 하는지를 결정했고, 진술이 이루어졌음을 기록했습니다. 게이트는 제가 작성한 내용이 사실인지 결코 검증하지 않았습니다.
이러한 구분은 혼동하기 쉬운 두 가지 종류의 가드레일 (guardrails)로 나뉩니다. 선언 게이트 (declaration gate)는 무언가를 기록에 강제하며, 단지 그것이 기록되었다는 사실만을 증명합니다. 증거 게이트 (evidence gate)는 주장을 독립적으로 검증합니다. 예를 들어, 비밀 스캔 (secret scan)은 디프 (diff) 자체를 읽고, 테스트 스위트 (test suite)는 코드를 실행합니다. 선언 게이트는 숙고 (deliberation)와 감사 추적 (audit trail)을 제공하며, 이는 실제적인 가치가 있습니다. 하지만 선언 게이트가 진실을 보장하지는 않으며, 하나를 다른 하나로 취급하는 것 자체가 그 자체로 하나의 실패 모드 (failure mode)가 됩니다. 신뢰할 수 있는 에이전트로 인프라 구축하기에 관한 저희 동반 가이드는 이 사례의 빌드 타임 (build-time) 버전을 다루며, 이 가이드는 런타임 (runtime) 버전을 다룹니다.
하네스 (harness)는 언제 거부해야 하는가?
2025-10-31, Meta는 규칙으로서 선을 그었습니다: 에이전트 세션은 동시에 최대 세 가지 속성 중 두 가지만 충족해야 합니다. 세 가지 속성은 신뢰할 수 없는 입력 (untrusted input), 민감한 접근 (sensitive access), 그리고 대부분의 실제 피해를 야기하는 결과적 행동 (consequential action)입니다. 이 세 가지를 모두 보유한다면, 단 한 번의 프롬프트 인젝션 (prompt injection)만으로도 나쁜 결과에 도달하게 됩니다.
원칙은 행동하기 전에 한 축을 포기하거나, 포기할 수 없을 때는 감독 (supervision)을 요구하는 것입니다. Meta는 세 가지 속성을 다음과 같이 직접 명시하고 있습니다:
에이전트는 세션 내에서 다음 세 가지 속성 중 두 가지 이상을 충족해서는 안 됩니다: [A] 에이전트가 신뢰할 수 없는 입력을 처리할 수 있음; [B] 에이전트가 민감한 시스템이나 개인 데이터에 접근할 수 있음; [C] 에이전트가 상태를 변경하거나 외부와 통신할 수 있음.
이틀 후, Simon Willison은 이를 오늘날 보안이 강화된 LLM 기반 에이전트 시스템을 구축하기 위한 가장 실용적인 조언이라고 불렀습니다. 그가 2025년 6월에 제시했던 이전 프레임워크인 치명적인 삼각관계 (the lethal trifecta)에서도 동일한 세 가지 요소를 명시하고 있습니다:
치명적인 삼각관계의 역량은 다음과 같습니다: 개인 데이터에 대한 접근 (Access to your private data) ... 신뢰할 수 없는 콘텐츠에 대한 노출 (Exposure to untrusted content) ... 데이터를 훔치는 데 사용될 수 있는 방식으로 외부와 통신할 수 있는 능력 (The ability to externally communicate in a way that could be used to steal your data)
두 프레임워크는 한 가지 측면에서 차이가 있습니다. Willison의 삼각관계는 세 번째 역량을 데이터를 외부로 전송하는 능력으로 좁게 정의합니다. 그는 이 삼각관계가 "데이터 유출 (data exfiltration)의 위험만을 다룬다"고 명시했습니다. 반면 Meta의 측면 (C)는 이미 "상태를 변경하거나 외부와 통신"하는 것으로 더 넓게 정의되어 있으며, 이는 겉보기에 데이터 유출이 전혀 없는 파괴 행위까지 포함합니다. 예를 들어 히스토리를 새로 쓰는 force-push, DROP TABLE, 볼륨을 삭제하는 prune 등이 이에 해당합니다. 외부로 나가는 것은 아무것도 없지만, 내부의 무언가가 파괴되는 것입니다.
이 내용을 너무 단순하게 해석하지 않도록 하는 두 가지 명확한 점이 있습니다. 첫째, 측면 (B)는 범위가 넓습니다. 중요한 저장소(repository)에 대한 force-push는 이미 민감한 시스템에 대한 접근이므로, 이러한 예시는 보통 깔끔하게 두 가지 측면만 해당하는 것이 아니라 (A), (B), (C)가 함께 연루되는 경우가 많습니다. (A)와 (C)의 조합이 진정으로 낮은 위험이 되는 경우는 오직 대상이 소모적인(disposable) 경우뿐입니다. 둘째, 이 규칙은 세 가지 중 두 가지만 해당하면 안전하다는 뜻이 아니며, Willison은 정확히 그러한 해석에 반대했습니다. 두 가지 측면이 충족된다는 것은 폭발 반경 (blast radius)이 작아진다는 의미이지, 면역이 생긴다는 뜻이 아닙니다. 세 가지 측면이 모두 충족된다는 것은 행동을 취하기 전에 감독이나 신뢰할 수 있는 검증 단계가 필요함을 의미하며, 이것이 항상 즉각적인 거부를 의미하는 것은 아닙니다.
어떻게 거부를 프롬프트(prompt)가 아닌 속성(property)으로 만들 것인가?
2026-07-23에 가장 자주 작동했던 게이트(gate)는 들리는 것보다 훨씬 더 좁은 범위의 일을 수행했습니다. 그것은 동작이 실행되기 전, 제가 하려는 일의 형태를 작은 규칙 세트(rule set)와 대조하여 명시적인 결정(explicit decision)을 내렸습니다. 그것은 동작이 안전한지 여부를 판단한 것이 아니었습니다. 그것은 규칙이 일치하는지 여부를 판단했을 뿐이며, 이는 전혀 다르고 훨씬 더 약한 질문입니다.
이러한 구분은 지나친 결벽증이 아니라, 정직한 설명의 전부입니다. 알려진 파괴적 패턴을 찾지 못한 매처(matcher)는 정확히 한 가지만 입증했을 뿐입니다: 알려진 패턴이 일치하지 않는다는 사실 말입니다. 그것은 명령이 안전하다는 것을 입증한 것이 아닙니다. 따라서 올바른 정신적 모델(mental model)은 벽(wall)이 아니라 인계철선(tripwire)이며, 올바른 역할은 심층 방어(defense-in-depth)입니다. 즉, 실제 제어 장치(controls)를 대신하는 것이 아니라, 그 아래에 위치하여 명백한 사례들을 요란하게 잡아내는 저렴한 계층(layer)이 되는 것입니다.
무엇이 통과될 수 있는지에 대해 구체적으로 생각하십시오. 이것은 실행 전 명령 텍스트를 대조하므로, 샌드박스(sandbox)가 아닌 명령 전 필터(pre-command filter)입니다. 셸 확장(Shell expansion), 별칭(alias), eval, 소싱된 래퍼 스크립트(sourced wrapper script), git 별칭, 또는 다른 경로를 통해 도달하는 동일한 바이너리는 모두 문자열 매칭(string match)을 피해 파괴적인 동작을 수행할 수 있습니다. 이 목록을 카나리 세트(canary set)로 취급하고, 정기적으로 게이트에서 각각을 테스트하십시오. 동작을 단순히 권장하지 않는 수준이 아니라 실제로 방지해야 하는 경우, 강제 집행(enforcement)은 실행 경계(execution boundary), 즉 명령을 실행하는 계층에서 이루어져야 하며, 더 나아가 세션에서 해당 기능 자체를 제거하는 방식으로 이루어져야 합니다.
실패 모드(failure mode)는 나머지 절반입니다. 게이트가 페일 클로즈(fail-closed)가 되려면, 게이트 자체가 고장 나는 것이 곧 차단으로 이어져야 합니다. Claude Code에서 PreToolUse 훅(hook)은 두 가지 방식으로 호출을 중단할 수 있습니다: 차단 후 stderr를 모델에 다시 전달하는 종료 코드 2(exit code 2), 또는 deny라는 구조화된 permissionDecision을 전달하며 깔끔하게 종료하는 방식입니다. PermissionRequest 훅 또한 거부할 수 있습니다. PostToolUse 훅은 도구가 이미 실행되었기 때문에 아무것도 중단할 수 없습니다.
처리되지 않은 충돌 (unhandled crash)을 포함하여 0이 아닌 다른 모든 종료는 비차단 오류 (non-blocking error)로 취급되며 동작은 계속 진행됩니다. 해당 실패는 조용히 지나가지 않습니다. 트랜스크립트 (transcript)에 훅 오류 (hook-error) 알림이 포함됩니다. 하지만 사후에 전달되는 알림은 차단 (block)이 아니며, 명령이 이미 실행된 후에 도착합니다. 따라서 예상하지 못한 입력에 대해 예외를 던지는 게이트 (gate)는, 공격일 가능성이 가장 높은 기이한 입력에 대해 정확히 '페일 오픈 (fail open)' 상태가 됩니다. 게이트 자체의 오류 경로가 차단 상태로 종료되도록 작성하고, 단순히 정상적인 경로 (happy path)뿐만 아니라 그 오류 경로도 테스트하십시오.
다음으로 오버라이드 (override)가 있는데, 여기서 저는 미사여구를 붙이기보다 정확하게 말씀드리겠습니다. 현재 제 설정에서 차단을 해제하는 것은 운영자가 설정하는 환경 변수 (environment variable)입니다. 이는 들리는 것보다 더 취약한 방식입니다. 환경 변수는 인증 (authentication)이 아니며, 특정 명령에 결합되어 있지 않고, 일회용이 아니며, 감사 추적 (audit trail)을 남기지 않습니다. 오버라이드가 갖추어야 할 속성은 바로 이 네 가지이며, 간극을 암시하기보다는 그 간극이 존재함을 명시하는 것이 더 유용합니다. 모델이 스스로 설정할 수 있는 오버라이드는 결국 다른 형태의 권한 프롬프트 (permission prompt)일 뿐입니다.
게이트 자체가 죽으면 어떻게 될까요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

