Enterprise MCP를 위한 Zero-Trust 인가와 도구 탐색 (PayPal)
Zero-Trust Authorization and Discovery for Enterprise MCP
TL;DR Highlight
도구 목록 필터와 호출 시 권한 검사를 같이 쓰자 금지된 도구 시도가 21.1%에서 0%로 줄었다
Who Should Read
사내 MCP 서버에 사람(SSO)과 자동화(서비스 계정)가 함께 붙는데 프롬프트 인젝션이 걱정인 백엔드 개발자
Core Mechanics
- 모델 거절에 보안을 맡기면 위험하다
- 인젝션 프롬프트에서 금지 도구를 부르려는 시도가 모델별로 0%에서 50%까지 갈렸다.
- 모델 거절은 제공사가 정하고 버전마다 바뀌므로 인가 경계는 서버가 직접 쥐어야 한다.
- list_tools 필터링만으로는 부족하다
- 필터만 쓴 서버는 스크립트로 tools/call을 직접 보내면 금지 도구가 실행된다.
- 모델은 목록에 없는 도구 이름을 최대 94%까지 그대로 언급했다.
- 그래서 호출 시점 검사(데코레이터)가 따로 필요하다.
- 권한 선언 하나로 목록과 호출을 같이 막는다
- @require_permissions 한 줄이 list_tools 필터와 호출 검사에 함께 쓰인다.
- 이 선언은 사전 인증 메타데이터에도 쓰여 세 곳이 어긋나지 않는다.
- 사람과 에이전트를 한 서버에서 받는다
- Authorization 헤더의 SSO 토큰과 다른 헤더의 서비스 계정 토큰을 각각 검증한다.
- 하나라도 통과하면 요청을 받고 권한은 합집합으로 계산하며, 전부 실패하면 오류를 낸다.
- 토큰 검증 캐시로 지연을 줄였다
- 불투명 토큰(JWT가 아니라 API로 물어봐야 하는 토큰)의 검증 결과를 기본 300초 TTL로 캐시한다.
Evidence
- 인젝션 공격 시도율 21.1%에서 0%
- 4개 LLM, 2160회 시도 중 baseline(도구 본문 안에서만 검사)은 152/720(21.1%)였다.
- filter-only와 with-extensions는 모두 0/720이었다.
- 모델별 baseline 시도율 차이
- claude-sonnet-4-6은 0/180, claude-opus-4-7은 3/180, gpt-5는 59/180이었다.
- gemini-2.5-pro는 90/180(50%)이었다.
- 필터만 쓰면 직접 호출이 뚫린다
- 비LLM 스크립트로 restart_pod를 직접 호출하면 filter-only에서는 실행되었다.
- baseline과 with-extensions에서는 차단되었다.
- 프로덕션 7일 트레이스에서 캐시 효과
- 검증 요청의 96.1%를 로컬 메모리에서 처리했다.
- p50이 9.9ms로, 캐시 미스 296ms 대비 약 30배 빨랐다.
How to Apply
- 도구 본문 안의 권한 검사를 점검하기
- 오늘 MCP 서버의 관리자용 도구가 호출 시점에 권한을 확인하는지 코드로 확인한다.
- 확인이 없는 도구는 데코레이터나 함수 첫 줄에 권한 검사를 넣는다.
- list_tools를 호출자 권한으로 필터링하기
- 역할별로 보여줄 도구를 나누고 viewer에게는 관리자 도구를 목록에서 뺀다.
- 필터와 호출 검사가 같은 권한 선언을 읽도록 하나로 묶는다.
- 선언 없는 도구를 전수 조사하기
- 이 구현은 권한 어노테이션이 없는 도구를 모든 인증 사용자에게 열어 둔다.
- 등록된 도구 목록에서 선언이 빠진 것을 찾아 필요한 권한을 붙인다.
- 토큰 캐시 TTL과 폐기 요구를 맞추기
- 폐기 즉시 반영이 필요하면 TTL을 0으로 두거나 폐기 알림을 따로 붙인다.
- 캐시 키는 토큰 원문이 아닌 SHA-256 해시로 쓴다.
Try It
# Claude Code 또는 ChatGPT에 붙여넣기 (논문 방법의 간이 버전: 코드 리뷰 프롬프트)
아래는 우리 MCP 서버 코드다. Zero-trust 관점에서 점검해 줘.
{{MCP 서버 코드}}
다음을 표로 정리해 줘.
1. 도구별로 list_tools 노출 여부가 호출자 권한에 따라 달라지는가
2. 도구별로 호출 시점에 권한 검사가 있는가 (목록에 안 보여도 tools/call로 직접 호출하면 실행되는지)
3. 권한 선언이 없어서 모든 인증 사용자에게 열려 있는 도구
4. 인증 토큰 캐시 TTL과 폐기 후 통과 가능 시간
문제가 있는 도구마다 목록 필터와 호출 검사를 같은 권한 선언 하나로 묶는 수정안을 제시해 줘.
이어서 해 볼 것: 수정안을 적용한 뒤 viewer 토큰으로 tools/call을 직접 보내 403이 나오는지 확인한다.Terminology
관련 논문
같은 시뮬레이션, 다른 근거: 도구 출력을 파일로 넘기면 LLM 에이전트 정확도가 어디서 깎이나
긴 툴 출력을 파일로 넘기자 GPT-5.1은 정확도 11.3%p 하락, 정식 출력 지정하니 회복
가짜 뉴스가 진짜 폭락을: LLM 트레이딩 에이전트 대상 정보 주입 공격
가짜 헤드라인 한 줄로 LLM 트레이딩 에이전트 62%를 조종했다(프롬프트 주입은 14%)
Harness Engineering 101: LLM은 뇌, harness는 몸
에이전트는 JSON 배열을 관리하는 루프이고, 16개 챕터로 직접 만들어 본다
Harness²: 에이전트 harness를 스스로 고쳐 쓰는 재귀 최적화 프레임워크
모델은 고정하고 harness를 재귀로 고쳤더니 벤치마크 점수가 최대 11.8%p 올랐다
해고된 Savannah 경찰관, ICE 요원 남편에게 Flock 계정 공유
경찰관이 Flock 계정을 남편(ICE 요원)에게 공유했고, 감사에서 983건 검색이 드러나 해고됐다
tmux와 Claude Code: 세션, send-keys, 그리고 한계선
tmux로 Claude Code 여러 개를 돌리면 편하지만 에이전트 4개쯤에서 한계가 온다
Hybrid Reasoning Agents: 잠재 공간에서 생각하고 텍스트로 행동하는 LLM 에이전트
잠재 공간 추론과 텍스트 도구 호출을 섞었더니 성능 10.4%↑, 토큰 최대 54.7%↓
Original Abstract (Expand)
LLM agents translate natural-language context, which may include attacker-controlled text, into privileged tool calls, so authorization must remain effective even when an agent is prompt-injected or adversarially steered. The Model Context Protocol (MCP) has become a widely adopted interface for this boundary, yet its official SDKs' authentication and authorization primitives fall short of enterprise zero-trust requirements, most acutely a dual-persona model in which one server must serve human users (corporate SSO) and automated agents (service-account credentials on a different header). We conduct a systematic gap analysis of six surveyed MCP SDKs (Python, TypeScript, Go, Rust, C#, Swift) and identify three structural shortcomings: credential extraction bound to a single Authorization header, complicating dual-persona deployment without custom middleware; the absence of pre-authentication tool discovery; and the lack of fine-grained per-tool authorization in the base SDKs. We close these gaps with composable extensions to FastMCP: cross-header credential normalization for enterprise deployments serving both human and service-account callers, cached token verification across heterogeneous IdPs, an unauthenticated metadata endpoint for credential-free registry discovery, and permission-filtered tool visibility kept consistent with per-tool invocation enforcement by a single declarative annotation, all without modifying the protocol or SDK internals. Across four frontier LLMs over 2160 attempts, an in-body-check-only server still exposes forbidden tools (152/720, 21.1%), whereas permission-aware visibility drives the rate to 0/720; visibility-only filtering remained bypassable by scripted clients, while models referenced the hidden tool by name in up to 94% of settings when inferable from the prompt, confirming that discovery controls cannot replace invocation-time enforcement.