k
korAI
고급 전체
🔥 고급2026-05-267~9분

에이전트 Tool 실행 샌드박싱: 권한 최소화와 타임아웃 설계

LLM이 호출하는 tool은 프롬프트 인젝션과 무한 루프의 공격 표면이 된다. 권한 레이어, 타임아웃, 결과 검증을 조합한 방어적 tool 실행 아키텍처를 코드와 수치로 설명한다.

agent-designsecuritytool-execution

Tool 실행의 3가지 위협 모델

프로덕션 에이전트에서 tool 실행은 단순한 함수 호출이 아니다. 세 가지 위협을 명시적으로 설계해야 한다.

  1. 프롬프트 인젝션: 외부 데이터(웹 검색 결과, 사용자 입력)가 tool 파라미터를 오염시켜 의도치 않은 작업 실행
  2. 무한 루프: 에이전트가 tool 결과를 잘못 해석해 동일 tool을 반복 호출, 토큰 비용 폭증 (실측: 탈출 조건 없는 루프 1회당 평균 $0.80 추가 비용)
  3. 과도한 권한: 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% 시 타임아웃 값 재조정