Tool Poisoning은 AI 에이전트가 도구를 선택·사용할 때 참조하는 도구 설명·인자 스키마(메타데이터) 안에 숨은 지시문을 심어, 에이전트의 행동을 공격자가 조종하는 공격이다.
에이전트는 도구의 설명 텍스트를 신뢰해 동작하므로, 악성 MCP 서버나 오염된 도구 카탈로그 하나만으로 자격증명 유출·파일 접근 같은 위험 행동이 유도될 수 있다.
이 글은 자주 발생하는 실수 패턴, 표준 구현 코드, 30초 자가진단, 24시간 패치 순서를 정리했다.
한눈에 보는 핵심
Q. 일반 프롬프트 인젝션과 뭐가 다른가요? A. 사용자 입력이 아니라 '도구 설명' 자체에 명령이 숨는다는 점이 다릅니다. 사용자는 정상 요청을 해도 에이전트가 오염된 도구 메타데이터를 읽고 조종당합니다. Q. 서드파티 MCP 도구는 어떻게 믿나요? A. 도구 정의(이름·설명·스키마)를 해시로 핀 고정하고, 변경 시 재승인하도록 합니다. 검증되지 않은 출처의 도구는 자동 로딩하지 않는 것이 원칙입니다. Q. 완전 자동 실행이 위험한가요? A. 위험합니다. 파일 쓰기·네트워크·자격증명 접근 같은 고위험 도구는 사람이 승인하는 게이트를 두어야 조종당한 실행을 차단할 수 있습니다.자주 발생하는 실수 패턴
| # | 패턴 | 위험도 |
|---|---|---|
| 1 | 검증 없는 서드파티 도구·MCP 서버 자동 로딩 | 치명 |
| 2 | 도구 설명·스키마 변경을 감지·재승인 안 함 | 치명 |
| 3 | 고위험 도구(파일·쉘·자격증명)에 승인 게이트 없음 | 치명 |
| 4 | 도구 설명 텍스트를 정제 없이 프롬프트에 주입 | 높음 |
| 5 | 에이전트 행동 로깅·감사 부재 | 중간 |
표준 구현 코드
import hashlib, json
# 신뢰하는 도구 정의를 해시로 핀 고정
TRUSTED = {
"read_file": "9f2c...", # sha256(도구 정의 JSON)
}
HIGH_RISK = {"write_file", "run_shell", "read_secret"}
def load_tool(defn: dict):
name = defn["name"]
digest = hashlib.sha256(
json.dumps(defn, sort_keys=True).encode()
).hexdigest()
if TRUSTED.get(name) != digest: # 정의 변조·미승인 차단
raise ValueError(f"미승인 도구 정의: {name}")
return defn
def before_invoke(name: str):
if name in HIGH_RISK: # 고위험은 사람 승인
require_human_approval(name)
핵심은 "동작하는 코드"와 "안전한 코드"가 다르다는 점이다.
AI 코딩 도구는 기능 충족 코드를 우선 출력하므로 보안 분기는 별도로 강제해야 한다.
미들웨어·정책·CI 게이트로 모든 경로에 일괄 적용하는 것이 표준이다.
30초 자가진단
# 1. 로딩된 도구 목록·출처 점검 — 알 수 없는 서버의 도구가 있는지
# (에이전트 설정/MCP 서버 목록 확인)
# 2. 도구 설명에 은닉 지시가 있는지(명령형 문장·'무시하고'·'대신')
grep -rEn "무시하고|이전 지시|instead|ignore (all|previous)" ./tools/ ./mcp/
# 3. 고위험 도구가 승인 게이트 없이 자동 실행되는지 설정 확인
위 시그널이 발견되면 결함이다. OWASP LLM Top 10를 함께 참조하라.
24시간 패치 절차
| 순위 | 조치 |
|---|---|
| 1 | 도구 정의(이름·설명·스키마)를 해시로 핀 고정 |
| 2 | 정의 변경 시 재승인, 미승인 도구 로딩 차단 |
| 3 | 고위험 도구에 인간 승인 게이트 적용 |
| 4 | 도구 설명 텍스트를 프롬프트 주입 전 정제·격리 |
| 5 | 에이전트 행동 전체 감사 로깅 |
지금 해야 할 것
실무자: CodeScan 무료 스캔으로 30초 점검 후 우선순위대로 패치.
CISO·임원: 조직 전체 점검·규제 대응은 SENTRIX 30분 1:1 상담으로.
결함은 발견 즉시, 늦어도 24시간 안에 패치하는 것이 표준이다.