본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 18. 00:28

에이전트를 신뢰하지 마세요: 도구 호출 승인을 정확한 호출에 바인딩하기

요약

에이전트 시스템에서 불리언(boolean) 기반의 승인 방식이 가진 보안 취약점을 분석하고, 이를 해결하기 위한 도구 호출 바인딩 방안을 제시합니다. 승인을 실행 상태가 아닌 특정 호출에 대한 증거로 모델링하여 프롬프트 인젝션과 인자 변조를 방지하는 방법을 다룹니다.

핵심 포인트

  • 불리언 승인 방식은 반전, 재생, 인자 드리프트 공격에 취약함
  • 승인은 실행 속성이 아닌 특정 호출에 대한 증거(evidence)여야 함
  • 도구 호출 ID, 인자 다이제스트, 주체, 만료 시간을 포함한 태그 발행 권장
  • JSON 정규화(RFC 8785)를 통한 일관된 해시 검증 필요

에이전트 시스템(Agentic systems)은 파일 쓰기, 자금 이동, 배포와 같은 위험한 도구 호출(tool calls)을 "승인(approval)", 즉 인간 참여(human-in-the-loop) 클릭이나 정책 확인 뒤에 격리합니다. 이러한 승인이 보통 어떻게 표현되는지 살펴보면, 실행/세션 상태(run/session state)에 approved: true와 같은 불리언(boolean) 값이 자리 잡고 있는 것을 자주 발견하게 될 것입니다.

불리언은 잘못된 기본 자료형(primitive)이며, 프롬프트 인젝션(prompt injection)이 악용하기 딱 좋은 세 가지 방식으로 실패합니다.

승인 불리언이 깨지는 세 가지 방식

  1. 반전 (Flip). 실행 상태를 쓸 수 있는 모든 것 — 프로세스/내구 실행(durable-execution) 경계를 넘나드는 직렬화된 컨텍스트, 혼동된 대리인(confused-deputy) 코드 경로, 상태를 조종하는 인젝션 — 이 falsetrue로 바꿉니다.
  2. 재생 (Replay). 당신은 "report.csv 읽기"를 승인했습니다. 승인은 단지 true일 뿐이므로, 동일한 플래그가 다음 도구 호출인 "prod.db 삭제"에도 적용됩니다. 불리언은 자신이 어떤 호출을 승인했는지 알지 못합니다.
  3. 인자 드리프트 (Argument drift). 당신은 "alice에게 $10 송금"을 승인했습니다. 승인과 실행 사이에 인자(args)가 $10,000로 변질됩니다. 불리언은 여전히 approved라고 말합니다.

세 가지 경우 모두 근본 원인은 동일합니다. 승인이 **특정 호출에 대한 증거(evidence)**여야 함에도 불구하고, **실행의 속성(property of the run)**으로 모델링되어 있다는 점입니다.

승인을 호출에 바인딩하기

승인이 부여될 때, 변경되어서는 안 되는 요소들—도구 호출 ID(tool-call id), 정형 인자(canonical arguments)의 다이제스트(digest), 주체(principal), 그리고 만료 시간(expiry)—에 대해 태그를 발행(mint)하세요. 실행(dispatch) 시점에 실행별 비밀키(per-run secret)를 사용하여 이를 검증하십시오.

import hmac, hashlib, json, time

def canon(args: dict) -> bytes:
...

이에 대해 세 가지 공격(및 주체 교체와 위조된 태그)을 실행해 보십시오:

KEY = b"per-run-secret-not-a-global-one"
tok = mint(KEY, "call-1", {"amount": 10, "to": "alice"}, "user:42")   # alice에게 $10 송금 승인

...

이제 플래그를 반전시킬 수 없으며(유효한 태그가 없음), 재생할 수 없고(call-id가 MAC에 포함됨), 드리프트될 수도 없습니다(args 다이제스트가 MAC에 포함됨). 전송되는 상태를 완전히 제어하는 공격자라도 키 없이는 토큰을 만들어낼 수 없습니다.

실제로 유효한지를 결정하는 세 가지 세부 사항

  • 정규화 (Canonicalization). 양측 모두 동일한 바이트를 해시해야 합니다. 키를 정렬하고 숫자를 정규화하십시오 (10 vs 10.0 vs 1e1이 일치해야 함). RFC 8785 (JSON Canonicalization Scheme)가 즉시 적용 가능한 해답입니다. 두 측이 규칙에 대해 암묵적으로 의견이 일치하지 않는 상황을 방지하기 위해, 정규화 레시피 ID를 해시된 바이트 내부에 포함하십시오.
  • 타입화된 (typed) 결과를 통한 폐쇄형 실패 (Fail closed). 부재 / 만료 / 불일치 ⇒ 일반적인 도구 페이로드(payload)가 아닌, 명확히 구분되는 "승인되지 않음" 결과를 반환해야 합니다. 그렇지 않으면 다운스트림(downstream)에서 "승인 누락"이 "도구가 실행되었으나 거짓(falsy) 값을 반환함"과 구별되지 않으며, 호출자는 승인을 다시 요청해야 할지 판단할 수 없습니다.
  • 단일 강제 체크포인트, 기본 거부 (deny-by-default). 이는 디스패치(dispatch) 직전의 단일 지점에 위치해야 합니다. Semantic Kernel의 AUTO_FUNCTION_INVOCATION 필터( next를 호출하지 않으면 호출이 건너뛰어짐), ADK의 before_tool 콜백, 또는 MCP 도구 호출 경계(boundary)가 이에 해당합니다. 승인이 필요한 도구는 그렇게 분류되어야 하며, 분류되지 않은 모든 것은 허용되지 않고 거부되어야 합니다.

운영 환경에서 문제를 일으키는 함정: 재생 (replay)

만약 에이전트가 재생 기반의 내구 실행 엔진 (Temporal 및 유사 엔진들) 위에서 실행된다면, 실행 시마다 생성되는 비밀값은 재생 시에도 반드시 유지되어야 합니다. 워크플로(Workflow) 코드는 복구 시 히스토리로부터 재실행되므로, 비결정론적(non-deterministic) 호출로 생성된 키는 히스토리에 이미 존재하는 토큰과 일치하지 않게 됩니다. 이로 인해 개발 환경에서는 승인이 잘 작동하다가, 첫 번째 워커(worker) 재시작 직후에 폐쇄형 실패(fail closed)가 발생하게 되며, 이는 이를 발견하기에 최악의 타이밍입니다. 키를 결정론적으로 유도하거나 (HKDF(server_secret, run_id)), 기록된 부수 효과(side-effect)를 통해 한 번 설정하십시오. 또한 만료 시간 역시 워크플로 코드 내부에서 실제 시계(wall-clock)를 읽는 대신 결정론적으로 설정하십시오.

요점

에이전트 시스템에서의 권한 부여 (Authorization)는 실행 과정과 함께 이동하는 주변적이고 가변적인 상태 (ambient, mutable state)여서도 안 됩니다. 그것은 단일 호출 봉투 (single call envelope)에 결합된 증거 — 즉, 이 원칙, 이 도구, 이 정확한 인자 (arguments), 그리고 이 시간까지 — 여야 하며, 실행자 (executor)는 실행 (dispatch) 시점에 이를 재검증해야 합니다. 불리언 (boolean) 값은 이를 단순화한 것이 아니라, 바로 그 버그입니다.

저는 AI 및 수치 시스템의 신뢰성 (reliability)과 검증 (verification) — 에이전트 권한 부여 (agent authorization), 결정론 (determinism), 그리고 "권한이 있다고 주장하는 대상이 실제로 권한을 가졌음을 증명하는 것" — 을 연구합니다. 위의 코드 스니펫 (snippet)은 그대로 실행 가능합니다. 에이전트의 도구 경계 (tool boundary)를 강화하고 계신다면 기꺼이 의견을 나누고 싶습니다 — GitHub.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0