본문 바로가기
C.W.K.
Stream
Lesson 06 of 06 · published

도구에는 API와 같은 권한 심사가 필요해

~14 min · security, permissions, side-effects

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

도구 설명도 외부로 전송돼

description은 해당 도구가 포함된 요청마다 Anthropic으로 나가. 내부 호스트명과 경로, 테이블 이름, 비밀을 짐작하게 하는 문구를 적으면 프롬프트 데이터가 돼 버려. 설명은 공개 계약처럼 검토하고 배포 전에 민감한 정보를 걷어 내.

쓰기는 사람의 확인을 받아

검색·조회·읽기 도구는 대개 자동 실행해도 피해가 제한적이야. 생성·수정·삭제 도구는 실행 전에 명시적인 승인을 받을 가치가 있어. Agent SDK는 권한 프롬프트를 제공하고 Client SDK 기반 루프에서는 직접 만들어야 해. 어떤 경우에도 모델의 한 문장만으로 파괴적 부수 효과를 확정하지 마.

코드와 셸은 격리해서 실행해

명령 실행이나 파일 접근 도구에는 제한된 환경변수, 작업 디렉터리 허용 목록, 시간 예산, 출력 크기 상한을 둬. Anthropic의 code_execution이 샌드박스에서 도는 것처럼 커스텀 도구도 피해 범위를 구조적으로 줄여야 해.

원칙: 새 도구 하나는 신뢰하지 않은 사용자에게 연 API 하나와 같아. 권한과 입력, 부수 효과를 같은 눈으로 감사해.

Code

Write tool 주변 permission gate·python
import os, json

DESTRUCTIVE = {"delete_file", "drop_table", "send_email"}

async def gated_invoke(block, ask_user):
    args_str = json.dumps(block.input, indent=2)
    if block.name in DESTRUCTIVE:
        approved = await ask_user(f"Allow {block.name} with args:\n{args_str}")
        if not approved:
            return {"type": "tool_result", "tool_use_id": block.id,
                    "is_error": True, "content": "user denied permission"}
    handler = HANDLERS[block.name]
    out = await handler(**block.input)
    return {"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(out)}
시간 예산 가진 sandbox 셸 도구·python
import asyncio, shlex

ALLOWED = {"ls", "cat", "grep", "head", "tail"}

async def safe_shell(command: str, timeout: float = 5.0):
    parts = shlex.split(command)
    if not parts or parts[0] not in ALLOWED:
        raise PermissionError(f"command not in allowlist: {parts[0] if parts else ''}")
    proc = await asyncio.create_subprocess_exec(
        *parts,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
        cwd="/sandbox",  # 화이트리스트 작업 디렉토리
        env={"PATH": "/usr/bin:/bin"},  # minimal env
    )
    try:
        out, err = await asyncio.wait_for(proc.communicate(), timeout=timeout)
    except asyncio.TimeoutError:
        proc.kill()
        raise TimeoutError("command exceeded time budget")
    return {"stdout": out.decode()[:8192], "stderr": err.decode()[:2048], "returncode": proc.returncode}

External links

Exercise

부수 효과가 있는 도구 하나에 권한 문과 샌드박스 경계, 감사 기록을 추가해. 승인 없이는 거부되는 시험으로 세 경계를 증명해.
Hint
확인 없이 DB 테이블을 지울 수 있다면 운영 사고를 기다리고 있는 거야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.