MCP Tool Squatting · Rug Pull 차단: ETDI와 OAuth 서명 기반 무결성 검증 직접 구현하기
AI 에이전트가 외부 도구를 자유롭게 호출하는 시대가 열리면서, 그 연결 고리인 MCP(Model Context Protocol) 생태계가 새로운 공격 표면이 되고 있습니다. MCP는 Anthropic이 제안한 LLM-도구 통합 프로토콜로, 에이전트가 이메일 발송·파일 읽기 같은 외부 기능을 표준화된 방식으로 호출할 수 있게 해줍니다. 2026년 1월 AI 에이전트 게이트웨이 서비스인 Clawdbot에서 인증 없는 게이트웨이를 통해 2,000개 이상의 MCP 인스턴스에서 자격증명과 대화 내역이 유출됐고, 2025년 9월에는 npm의 정상 이메일 패키지가 15개 버전에 걸쳐 신뢰를 쌓은 뒤 모든 발신 이메일을 공격자 주소로 BCC하는 로직을 몰래 삽입하기도 했습니다. 이처럼 도구 정의를 사후에 조용히 변경하거나, 동일한 이름의 악성 도구를 에이전트에 슬쩍 끼워 넣는 공격은 기존 클라이언트 구조에서는 탐지조차 어렵습니다.
이 글은 ETDI(Enhanced Tool Definition Interface)와 OAuth 2.0 기반 도구 서명을 직접 구현해 Tool Squatting과 Rug Pull 공격을 런타임 단계에서 원천 차단하는 방법을 단계별로 소개합니다. 암호화 서명으로 도구의 출처와 무결성을 증명하고, 불변 버전 관리로 사후 변경을 감지하며, OAuth 스코프로 최소 권한을 선언하는 세 가지 원칙이 어떻게 맞물려 동작하는지 이해할 수 있습니다.
ETDI는 2025년 6월 arXiv(arXiv:2506.01333)에 공식 발표된 MCP 보안 확장 명세로, MCP Python SDK PR #845를 통해 통합이 논의 중입니다. STRIDE/DREAD 위협 모델링에서 도구 포이즈닝이 DREAD 46.5/50(Critical)을 기록하고 OWASP LLM Top 10 1위에 오른 현실을 감안하면, 지금부터 이 체계를 이해하고 직접 구현해두는 것은 매우 의미 있는 작업입니다.
핵심 개념
Tool Squatting과 Rug Pull — 에이전트를 겨냥한 두 가지 공격
MCP 생태계에서 에이전트는 여러 서버에 동시에 연결될 수 있고, 각 서버는 임의로 도구를 등록할 수 있습니다. Tool Squatting은 이 점을 악용해 악성 서버가 정상 서버의 도구와 동일한 이름(send_email, read_file 등)을 등록하는 공격입니다. LLM은 비결정론적으로 둘 중 하나를 호출하므로, 악성 도구가 실행될 확률이 생깁니다.
Rug Pull 공격은 더 교묘합니다. 사용자가 한 번 승인한 도구의 정의를 서버 측에서 사후에 몰래 변경하는 방식입니다. 대부분의 MCP 클라이언트는 설치 시점에만 도구를 검증하고 이후 변경을 추적하지 않아, 에이전트는 이미 무기화된 도구를 신뢰할 수 있는 도구라고 인식한 채 계속 호출하게 됩니다.
공격 파급력: 50개 에이전트가 동일한 손상된 도구를 사용한다면, 단 하나의 공급망 이벤트로 50개의 동시 감염이 발생합니다. 단일 패키지의 버전 업데이트 하나가 생태계 전체를 위협할 수 있습니다.
ETDI의 세 가지 핵심 원칙
ETDI는 세 가지 원칙을 조합해 위 두 가지 공격을 동시에 차단합니다.
| 원칙 | 해결하는 위협 | 메커니즘 |
|---|---|---|
| 암호화 신원 및 무결성 검증 | Tool Squatting | ECDSA 개인 키로 도구 정의에 서명, 공개 키로 출처 검증 |
| 불변 버전 관리 | Rug Pull | 정의 변경 시 새 버전 + 새 서명 필수, 재승인 강제 |
| OAuth 2.0 명시적 권한 선언 | 과잉 권한 | 스코프를 서명된 정의에 선언, 런타임 정책 엔진 연동 |
중요한 전제: 공개 키 자체는 출처를 완전히 증명하지 못합니다. ETDI가 완전한 효력을 발휘하려면
public_key_ref의 JWKS 엔드포인트가 신뢰할 수 있는 제공자의 것임을 별도로 확인하는 루트 오브 트러스트(예: 조직 내 신뢰 레지스트리, CA 인증서)가 필요합니다. 이 체계 없이는 공격자도 자신의 공개 키를public_key_ref에 올려 도구를 등록할 수 있습니다.
ETDI 도구 정의 구조
기존 MCP 도구 정의에 version, permissions, provider, signature, public_key_ref 필드가 추가됩니다.
{
"name": "send_email",
"version": "1.2.0",
"description": "지정된 수신자에게 이메일을 발송합니다.",
"schema": {
"input": {
"type": "object",
"properties": {
"to": { "type": "string" },
"subject": { "type": "string" },
"body": { "type": "string" }
},
"required": ["to", "subject", "body"]
},
"output": { "type": "object" }
},
"permissions": ["email:send"],
"provider": "trusted-email-service",
"signature": "eyJhbGciOiJFUzI1NiJ9.eyJuYW1lIjoic2VuZF9lbWFpbCIsInZlcnNpb24iOiIxLjIuMCJ9.MEUCIQD...",
"public_key_ref": "https://keys.trusted-email-service.com/.well-known/jwks.json"
}JWKS(JSON Web Key Set): 공개 키를 JSON 형식으로 표준화한 집합입니다. 클라이언트는
public_key_refURL에서 공개 키를 가져와signature값을 검증합니다. 키 순환(rotation) 시에도 URL은 동일하게 유지되므로 클라이언트 코드 변경이 불필요합니다.
base64url: 일반 Base64(
+,/,=포함)와 달리 URL과 HTTP 헤더에 안전한 인코딩 방식입니다(-,_사용, 패딩 생략). JWT의 헤더·페이로드·서명은 모두 base64url로 인코딩되며, 이 덕분에 URL 파라미터나 JSON 필드에 그대로 삽입할 수 있습니다.
서명 생성과 검증 흐름
sequenceDiagram
participant S as MCP 서버
participant JW as JWKS 엔드포인트
participant C as MCP 클라이언트
S->>S: 도구 정의 생성 + ES256 JWT 서명
S->>JW: 공개 키 게시(/.well-known/jwks.json)
C->>S: 도구 목록 조회
S->>C: 서명된 도구 정의 반환
C->>JW: 공개 키 조회 (TTL 캐싱)
JW->>C: JWKS 응답
C->>C: JWT 서명 검증
alt 검증 성공
C->>C: 버전·스코프 변경 확인
C->>S: 도구 호출
else 검증 실패
C->>C: 도구 호출 거부
end실전 적용
예시 1: Python으로 ETDI 도구 서명 서버 구현하기
cryptography와 PyJWT 라이브러리로 도구 정의에 ECDSA ES256 JWT 서명을 적용하는 서버를 구성할 수 있습니다. JWT 형식을 사용하면 Python·TypeScript·Java 등 모든 표준 JOSE 라이브러리로 상호 검증이 가능하고, 뒤에서 설명할 DER/P1363 인코딩 불일치 문제도 라이브러리가 내부에서 처리해줍니다.
# 의존성 설치
uv add cryptography PyJWT# etdi_server.py
import json
import base64
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
import jwt # PyJWT
class ETDIToolSigner:
"""도구 정의에 ECDSA P-256 JWT 서명을 생성하는 클래스"""
def __init__(self):
# 프로덕션에서는 HSM 또는 Secret Manager에서 키를 로드합니다
self._private_key = ec.generate_private_key(
ec.SECP256R1(), default_backend()
# SECP256R1 = P-256: NIST 표준 곡선으로 성능·보안·호환성의
# 균형이 잡혀 있으며 JOSE 생태계 전반에서 지원됩니다
)
self._public_key = self._private_key.public_key()
def sign_tool_definition(self, tool_def: dict) -> dict:
"""
도구 정의의 핵심 필드를 JWT 페이로드로 서명합니다.
서명 대상: name + version + schema + permissions + provider
"""
payload = {
"name": tool_def["name"],
"version": tool_def["version"],
"schema": tool_def["schema"],
# sorted()로 권한 목록 정렬 — 결정론적 직렬화가 핵심입니다
"permissions": sorted(tool_def.get("permissions", [])),
"provider": tool_def["provider"],
}
# PyJWT가 ES256(ECDSA + SHA-256) JWT를 생성합니다
# jose(TypeScript) 등 표준 JOSE 라이브러리로 교차 검증이 가능합니다
tool_def["signature"] = jwt.encode(
payload, self._private_key, algorithm="ES256"
)
return tool_def
def export_jwks(self) -> dict:
"""클라이언트가 서명 검증에 사용할 JWKS를 반환합니다."""
# _private_key.public_key()로 분리한 공개 키 객체에서 좌표를 추출합니다
pub_numbers = self._public_key.public_numbers()
x = base64.urlsafe_b64encode(
pub_numbers.x.to_bytes(32, 'big')
).rstrip(b'=').decode()
y = base64.urlsafe_b64encode(
pub_numbers.y.to_bytes(32, 'big')
).rstrip(b'=').decode()
return {
"keys": [{
"kty": "EC",
"crv": "P-256",
"use": "sig",
"alg": "ES256",
"kid": "etdi-key-v1",
"x": x,
"y": y,
}]
}
# 도구 정의 예시
signer = ETDIToolSigner()
send_email_tool = {
"name": "send_email",
"version": "1.2.0",
"description": "지정된 수신자에게 이메일을 발송합니다.",
"schema": {
"input": {
"type": "object",
"properties": {
"to": {"type": "string", "format": "email"},
"subject": {"type": "string"},
"body": {"type": "string"}
},
"required": ["to", "subject", "body"]
}
},
"permissions": ["email:send"],
"provider": "trusted-email-service",
"public_key_ref": "https://keys.trusted-email-service.com/.well-known/jwks.json"
}
signed_tool = signer.sign_tool_definition(send_email_tool)
print(json.dumps(signed_tool, indent=2, ensure_ascii=False))
jwks = signer.export_jwks()
print(json.dumps(jwks, indent=2))| 코드 포인트 | 설명 |
|---|---|
ec.SECP256R1() |
P-256 곡선 선택 — NIST 표준이며 JOSE 생태계 전반에서 지원됩니다 |
sorted(permissions) |
권한 목록 정렬로 직렬화 결과를 결정론적으로 만듭니다 |
jwt.encode(..., algorithm="ES256") |
JWT 형식 서명 — DER/P1363 인코딩 불일치 없이 교차 검증이 가능합니다 |
self._public_key.public_numbers() |
_private_key.public_key()로 미리 분리한 공개 키 객체를 사용합니다 |
export_jwks() |
/.well-known/jwks.json 엔드포인트에서 이 응답을 반환합니다 |
예시 2: TypeScript MCP 클라이언트에서 서명 검증하기
jose 라이브러리만으로 JWKS 조회와 JWT 검증을 깔끔하게 구현할 수 있습니다.
# 의존성 설치
pnpm add jose// etdi-verifier.ts
import { createRemoteJWKSet, jwtVerify } from 'jose';
interface ETDIToolDefinition {
name: string;
version: string;
schema: Record<string, unknown>;
permissions: string[];
provider: string;
signature: string; // ES256 JWT
public_key_ref: string; // JWKS URL
}
interface VerificationResult {
valid: boolean;
reason?: string;
}
// createRemoteJWKSet은 내부적으로 키를 캐싱합니다
// 추가 TTL 제어가 필요하면 cooldownThreshold 옵션을 사용합니다
const jwksCache = new Map<string, ReturnType<typeof createRemoteJWKSet>>();
function getJWKS(jwksUrl: string) {
if (!jwksCache.has(jwksUrl)) {
jwksCache.set(jwksUrl, createRemoteJWKSet(new URL(jwksUrl)));
}
return jwksCache.get(jwksUrl)!;
}
/**
* ETDI 도구 정의의 JWT 서명을 검증합니다.
* 검증 실패 시 도구 호출을 거부해야 합니다.
*/
async function verifyETDITool(tool: ETDIToolDefinition): Promise<VerificationResult> {
try {
const JWKS = getJWKS(tool.public_key_ref);
// jose가 JWKS 조회 + ES256 서명 검증을 한 번에 처리합니다
const { payload } = await jwtVerify(tool.signature, JWKS, {
algorithms: ['ES256'],
});
// JWT가 유효해도 서명된 내용과 실제 정의가 다를 수 있으므로
// 핵심 필드를 직접 비교합니다
const permissionsMatch =
JSON.stringify([...tool.permissions].sort()) ===
JSON.stringify([...((payload.permissions as string[]) ?? [])].sort());
if (
payload.name !== tool.name ||
payload.version !== tool.version ||
payload.provider !== tool.provider ||
!permissionsMatch
) {
return {
valid: false,
reason: '서명 페이로드와 도구 정의가 불일치합니다 — 변조 의심.',
};
}
return { valid: true };
} catch (error) {
return {
valid: false,
reason: `검증 오류: ${error instanceof Error ? error.message : '알 수 없는 오류'}`,
};
}
}
/**
* 버전 변경을 감지하여 재승인 여부를 판단합니다.
* 승인된 버전은 로컬 SQLite나 JSON 파일에 저장합니다.
*/
function hasVersionChanged(approvedVersion: string, currentVersion: string): boolean {
return approvedVersion !== currentVersion;
}
// 도구 발견 시 호출되는 핸들러
async function onToolDiscovered(tool: ETDIToolDefinition) {
const result = await verifyETDITool(tool);
if (!result.valid) {
console.error(`[ETDI] 도구 "${tool.name}" 차단: ${result.reason}`);
return; // 도구 등록 거부 — 에이전트에 노출되지 않습니다
}
const approvedVersion = getApprovedVersion(tool.name, tool.provider);
if (approvedVersion && hasVersionChanged(approvedVersion, tool.version)) {
console.warn(
`[ETDI] 도구 "${tool.name}" 버전 변경 감지: ${approvedVersion} → ${tool.version}`
);
await promptUserReapproval(tool);
return;
}
console.log(`[ETDI] 도구 "${tool.name}" v${tool.version} 서명 검증 완료`);
registerTool(tool);
}
// 실제 구현에서는 별도 모듈에 위치합니다
declare function getApprovedVersion(name: string, provider: string): string | null;
declare function promptUserReapproval(tool: ETDIToolDefinition): Promise<void>;
declare function registerTool(tool: ETDIToolDefinition): void;| 코드 포인트 | 설명 |
|---|---|
createRemoteJWKSet |
JWKS 조회와 키 캐싱을 내부에서 처리합니다 |
jwtVerify |
서명 검증 + 알고리즘 화이트리스트 확인을 한 번에 처리합니다 |
| 페이로드 필드 비교 | 유효한 서명이어도 서명된 내용과 실제 정의가 다를 수 있으므로 직접 비교합니다 |
hasVersionChanged() |
SemVer 파싱으로 확장하면 패치/마이너/메이저 변경을 구분할 수 있습니다 |
예시 3: OAuth 2.0 스코프 기반 런타임 접근 제어
도구 정의에 선언된 permissions와 실제 OAuth 토큰의 스코프를 런타임에 비교하는 미들웨어 패턴입니다. 토큰 서명 검증과 스코프 확인을 반드시 모두 수행합니다.
# 의존성 설치
uv add cryptography PyJWT requests# oauth_scope_guard.py
from functools import wraps
from typing import Callable, Any
import jwt
from jwt.algorithms import ECAlgorithm
import requests
class OAuthScopeGuard:
"""
도구 호출 전 OAuth 토큰의 서명을 JWKS 공개 키로 검증하고,
ETDI 도구 정의의 permissions를 충족하는지 확인합니다.
"""
def __init__(self, jwks_url: str):
self.jwks_url = jwks_url
self._cached_public_key = None
def _fetch_public_key(self) -> Any:
"""JWKS 엔드포인트에서 EC 공개 키를 가져옵니다 (인메모리 캐싱)."""
if self._cached_public_key:
return self._cached_public_key
resp = requests.get(self.jwks_url, timeout=5)
resp.raise_for_status()
keys = resp.json().get("keys", [])
ec_key = next((k for k in keys if k.get("kty") == "EC"), None)
if not ec_key:
raise ValueError("JWKS에 EC 키가 없습니다.")
# PyJWT 내장 변환 함수로 JWKS EC 키를 cryptography 공개 키 객체로 변환합니다
self._cached_public_key = ECAlgorithm.from_jwk(ec_key)
return self._cached_public_key
def require_scopes(self, required_scopes: list[str]):
"""도구 핸들러에 적용하는 데코레이터"""
def decorator(func: Callable) -> Callable:
@wraps(func)
async def wrapper(*args, request_context: dict, **kwargs) -> Any:
token = request_context.get("oauth_token")
if not token:
raise PermissionError("OAuth 토큰이 없습니다. 인증이 필요합니다.")
# 1단계: JWKS 공개 키로 토큰 서명을 검증합니다
# verify_signature: False는 절대 사용하지 않습니다
try:
public_key = self._fetch_public_key()
claims = jwt.decode(
token,
public_key,
algorithms=["ES256"],
)
except jwt.ExpiredSignatureError:
raise PermissionError("토큰이 만료됐습니다.")
except jwt.InvalidTokenError as e:
raise PermissionError(f"유효하지 않은 토큰: {e}")
# 2단계: 토큰 스코프와 도구 정의 permissions를 비교합니다
token_scopes = set(claims.get("scope", "").split())
missing_scopes = set(required_scopes) - token_scopes
if missing_scopes:
raise PermissionError(
f"권한 부족. 필요한 스코프: {missing_scopes}. "
f"보유한 스코프: {token_scopes}"
)
return await func(*args, request_context=request_context, **kwargs)
return wrapper
return decorator
# MCP 도구 핸들러에 적용 예시
guard = OAuthScopeGuard(jwks_url="https://auth.example.com/.well-known/jwks.json")
@guard.require_scopes(["email:send"])
async def handle_send_email(
to: str,
subject: str,
body: str,
*,
request_context: dict, # 키워드 전용 인자로 위치 인자와의 혼동을 방지합니다
) -> dict:
"""
ETDI 정의의 permissions: ["email:send"]와
런타임 OAuth 토큰의 scope가 일치해야만 실행됩니다.
"""
return {"status": "sent", "to": to}| 코드 포인트 | 설명 |
|---|---|
_fetch_public_key() |
JWKS에서 EC 공개 키를 가져와 인메모리 캐싱합니다. 키 순환 시 캐시를 초기화해야 합니다 |
ECAlgorithm.from_jwk() |
PyJWT 내장 변환 함수로 JWKS EC 키를 cryptography 객체로 변환합니다 |
jwt.decode(..., algorithms=["ES256"]) |
서명을 검증한 뒤 클레임을 추출합니다. verify_signature: False는 사용하지 않습니다 |
*, request_context: dict |
키워드 전용 인자로 선언해 핸들러 시그니처를 명확히 합니다 |
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 공급망 공격 차단 | 서명 검증으로 도구 정의 위변조를 런타임에 즉시 감지합니다 |
| Tool Squatting 원천 차단 | 제공자 공개 키로 도구 발급 주체를 증명하여 동명 도구 간 신뢰 근거를 제공합니다 |
| Rug Pull 방지 | 불변 버전 관리로 사후 변경을 탐지하고 변경 시 재승인을 강제합니다 |
| 세밀한 권한 제어 | OAuth 스코프 + 정책 엔진으로 최소 권한 원칙을 구현합니다 |
| 감사 추적 | 모든 도구 정의와 버전 히스토리가 검증 가능한 형태로 유지됩니다 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 키 관리 인프라 요구 | JWKS 엔드포인트, 키 순환, 폐기 인프라가 필요합니다 | Auth0, WorkOS 같은 관리형 서비스를 활용하면 운영 부담을 줄일 수 있습니다 |
| 생태계 전체 채택 필요 | 일부만 채택 시 미적용 서버의 취약점은 그대로 남습니다 | 자체 운영 서버부터 적용하고, 외부 서버는 ETDI 지원 여부를 필터링 기준으로 삼습니다 |
| 프로덕션 전 채택 단계 | 아직 MCP 주 명세에 포함되지 않아 SDK 통합이 제한됩니다 | MCP SDK PR #845를 추적하고, 자체 미들웨어로 먼저 구현해볼 수 있습니다 |
| 성능 오버헤드 | 매 요청마다 서명 검증이 필요합니다 | JWKS를 메모리에 캐싱(TTL 1시간 권장)하고, 서명 검증은 도구 등록 시 1회만 수행합니다 |
| 루트 오브 트러스트 부재 | 공개 키만으로는 출처를 완전히 증명하지 못합니다 | 신뢰 레지스트리 또는 CA 인증서로 JWKS 엔드포인트의 신뢰성을 보강합니다 |
키 순환(Key Rotation): 암호화 키를 주기적으로 교체하는 보안 관행입니다. ETDI에서는 키를 교체할 때 새 키로 모든 도구 정의를 재서명하고 JWKS 엔드포인트를 업데이트해야 합니다. 클라이언트가 JWKS URL을 캐싱하는 경우 캐시 TTL 이후 자동으로 새 키를 가져옵니다.
실무에서 가장 흔한 실수
- 직렬화 불일치: 서버에서
permissions를sorted()로 정렬해 서명했는데, 클라이언트에서 원래 순서로 페이로드를 재구성하면 유효한 서명도 실패합니다. 서명 대상 직렬화 방식을 문서화하고 양측이 동일하게 구현하는 것이 중요합니다. 예시처럼 JWT 기반 서명을 사용하면 페이로드 직렬화를 표준 라이브러리에 위임할 수 있어 실수를 줄일 수 있습니다. - DER ↔ IEEE P1363 인코딩 불일치: Python
cryptography라이브러리의 raw ECDSA 서명은 DER 인코딩을 사용하지만, 브라우저의 Web Crypto API(crypto.subtle)는 IEEE P1363(rawr ∥ s) 형식을 기대합니다. Python 서버로 서명하고 브라우저에서 직접 검증할 때 가장 흔히 마주치는 장벽입니다.PyJWT로 ES256 JWT를 생성하면 이 변환이 라이브러리 내부에서 처리됩니다. raw ECDSA 서명을 직접 교환해야 한다면 서버에서 P1363 형식으로 변환(r과s각각 32바이트 빅엔디언 연결)한 뒤 전달해야 합니다. verify_signature: False사용: 토큰에서 스코프만 빠르게 추출하려다 서명 검증을 건너뛰면, 공격자가 임의의 스코프를 가진 위조 토큰으로 모든 권한을 얻을 수 있습니다. 반드시 JWKS 공개 키로 서명을 검증한 뒤 클레임을 신뢰해야 합니다.- 버전만 바꾸고 재서명 생략: 도구 정의의 어떤 필드라도 변경 시 반드시 새 서명을 생성해야 합니다. CI/CD 파이프라인에 서명 생성 단계를 포함시켜 자동화하면 이 실수를 예방할 수 있습니다.
마치며
서명 검증이 없는 MCP 클라이언트는 HTTPS 없는 웹 서버와 같습니다. 연결 자체는 동작하지만, 상대방이 누구인지, 전달된 내용이 변조됐는지 알 방법이 없습니다. ETDI는 MCP 생태계에 그 첫 번째 암호화 신뢰 레이어를 도입하는 시도이며, 지금 직접 구현해두면 생태계가 성숙하는 시점에 선제적 보안 기반을 갖출 수 있습니다.
지금 바로 시작해볼 수 있는 3단계:
- 자신의 MCP 서버에 ETDI 서명 추가해보기:
uv add cryptography PyJWT로 의존성을 설치하고, 예시 코드의ETDIToolSigner클래스를 참고해 기존 도구 정의에sign_tool_definition()을 적용해볼 수 있습니다./.well-known/jwks.json엔드포인트도 함께 노출합니다. 동작 확인: 생성된 JWT를jwt.io에 붙여넣고export_jwks()가 반환한x,y값으로 직접 서명을 검증해보시면 결과를 확인할 수 있습니다. - 클라이언트 측 검증 미들웨어 작성하기:
pnpm add jose후 TypeScript 예시의verifyETDITool()함수를 기반으로, 도구 등록 시점(onToolDiscovered)에 서명 검증과 버전 변경 감지를 통합합니다. 승인된 버전을 로컬 SQLite나 JSON 파일에 저장해두면 Rug Pull 탐지를 즉시 시작할 수 있습니다. 서명된 도구와 의도적으로version필드를 수정한 도구를 각각 검증해 거부/통과 결과 차이를 확인해보시면 좋습니다. - Trail of Bits의
mcp-context-protector병행 적용 검토하기: ETDI가 키 관리 인프라를 요구하는 근본적 해결책이라면,mcp-context-protector는 별도 인프라 없이 Trust-on-first-use 핀닝과 프롬프트 인젝션 스캔을 즉시 제공하는 상호보완적 도구입니다. 두 가지를 함께 적용하면 방어 깊이가 높아집니다.
다음 글: OPA(Open Policy Agent)와 ETDI를 연동해 런타임 컨텍스트를 반영하는 동적 MCP 도구 접근 제어 정책 엔진 구축하기
참고 자료
본문에서 인용한 자료
- ETDI 논문 — Mitigating Tool Squatting and Rug Pull Attacks | arXiv
- ETDI 논문 HTML 전문 | arXiv
- MCP Python SDK PR #845 — ETDI 통합 | GitHub
- vineethsai/MCP-ETDI-docs — ETDI 공식 문서 | GitHub
- We Built the Security Layer MCP Always Needed | Trail of Bits Blog
- trailofbits/mcp-context-protector | GitHub
- MCP 공급망 공격 실사례 | Securelist
- MCP Rug Pull 공격 상세 설명 | Waxell
심화 학습 자료
- ETDI 보안 프레임워크 | vulnerablemcp.info
- MCP 공식 보안 모범 사례 | modelcontextprotocol.io
- MCP 인증/인가 공식 튜토리얼 | modelcontextprotocol.io
- MCP 도구 공격 벡터 및 방어 | Elastic Security Labs
- MCP 보안 취약점 타임라인 | authzed
- OAuth와 JWT로 MCP 도구 보호 | cloudnativedeepdive
- 2025년 6월 MCP 명세 업데이트 분석 | Auth0
- MCP 서버 OAuth 2.1 구현 가이드 | Scalekit
- MCPSecBench — MCP 보안 벤치마크 | arXiv
- MCP 위협 모델링 연구 | arXiv