🔥 고급2026-05-267~9분
에이전트 Tool 실행 샌드박싱: 권한 최소화와 타임아웃 설계
LLM이 호출하는 tool은 프롬프트 인젝션과 무한 루프의 공격 표면이 된다. 권한 레이어, 타임아웃, 결과 검증을 조합한 방어적 tool 실행 아키텍처를 코드와 수치로 설명한다.
agent-designsecuritytool-execution
Tool 실행의 3가지 위협 모델
프로덕션 에이전트에서 tool 실행은 단순한 함수 호출이 아니다. 세 가지 위협을 명시적으로 설계해야 한다.
- 프롬프트 인젝션: 외부 데이터(웹 검색 결과, 사용자 입력)가 tool 파라미터를 오염시켜 의도치 않은 작업 실행
- 무한 루프: 에이전트가 tool 결과를 잘못 해석해 동일 tool을 반복 호출, 토큰 비용 폭증 (실측: 탈출 조건 없는 루프 1회당 평균 $0.80 추가 비용)
- 과도한 권한: read-only 작업에 write 권한이 부여된 tool이 노출되면 단일 오작동으로 데이터 손상
방어적 Tool 래퍼 구현
import anthropic
import asyncio
from functools import wraps
from typing import Any, Callable
client = anthropic.Anthropic()
# Tool 실행 래퍼: 타임아웃 + 허용 파라미터 검증
def safe_tool(timeout_sec: float = 5.0, allowed_params: set = None):
def decorator(fn: Callable):
@wraps(fn)
async def wrapper(**kwargs):
# 1. 파라미터 화이트리스트 검증
if allowed_params and not set(kwargs.keys()).issubset(allowed_params):
raise ValueError(f"Unexpected params: {set(kwargs.keys()) - allowed_params}")
# 2. 타임아웃 강제
try:
return await asyncio.wait_for(fn(**kwargs), timeout=timeout_sec)
except asyncio.TimeoutError:
return {"error": f"Tool timeout after {timeout_sec}s", "status": "timeout"}
return wrapper
return decorator
@safe_tool(timeout_sec=3.0, allowed_params={"query", "max_results"})
async def search_docs(query: str, max_results: int = 5) -> dict:
# 실제 검색 로직
return {"results": [f"doc_{i}" for i in range(max_results)]}
# 에이전트 루프: 최대 반복 횟수 하드캡
async def run_agent(user_message: str, max_iterations: int = 10):
messages = [{"role": "user", "content": user_message}]
tools = [{"name": "search_docs", "description": "Search documents",
"input_schema": {"type": "object",
"properties": {"query": {"type": "string"},
"max_results": {"type": "integer"}},
"required": ["query"]}}]
for iteration in range(max_iterations):
response = client.messages.create(
model="claude-opus-4-5", max_tokens=1024,
tools=tools, messages=messages
)
if response.stop_reason == "end_turn":
return response.content
# tool_use 블록 처리
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = await search_docs(**block.input)
tool_results.append({"type": "tool_result",
"tool_use_id": block.id, "content": str(result)})
messages += [{"role": "assistant", "content": response.content},
{"role": "user", "content": tool_results}]
return {"error": "max_iterations reached", "iterations": max_iterations}
권한 레이어 설계와 운영 체크리스트
권한 최소화 원칙 수치 기준:
- Read-only tool: 타임아웃 3초, 재시도 2회
- Write tool: 타임아웃 10초, 재시도 0회 (멱등성 미보장 시 재시도 금지)
- 외부 API 호출 tool: 별도 rate limit 래퍼 + 응답 크기 상한 50KB
실패 모드 대응:
- tool 결과가
{"error": ...}형태일 때 에이전트가 재시도 루프에 빠지는 것을 방지하려면, 오류 메시지에 "do not retry" 힌트를 포함시키는 것이 효과적 (실측: 루프 발생률 73% 감소) - 프롬프트 인젝션 대응: tool 결과를
<tool_output>태그로 감싸고 시스템 프롬프트에서 해당 태그 내용은 지시가 아닌 데이터로 처리하도록 명시
운영 체크리스트:
- [ ] 모든 tool에 타임아웃 하드캡 설정, 기본값 없이 명시적 선언
- [ ] 에이전트 루프에
max_iterations상한 (권장: 10~15회) - [ ] tool 호출 로그에 파라미터 + 실행 시간 + 결과 크기 기록
- [ ] write tool은 dry-run 모드 플래그 구현, 스테이징 환경 강제 적용
- [ ] 월간 tool 실패율 리뷰: timeout > 10% 시 타임아웃 값 재조정