에이전트에게 전달하는 도구를 아무도 감사하지 않기에, MCP 서버를 위한 보안 린터(Security Linter)를 만들었습니다
요약
MCP(Model Context Protocol) 서버의 보안 취약점을 점검하기 위한 보안 린터인 'mcp-audit'을 소개합니다. 이 도구는 에이전트가 사용하는 도구와 리소스의 권한 설정, 데이터 유출 위험 등을 정적/동적 분석을 통해 검토합니다.
핵심 포인트
- MCP 서버의 도구, 리소스, 프롬프트에 대한 보안 규칙 카탈로그 실행
- CI/CD 환경에 통합 가능한 JSON 및 SARIF 2.1.0 출력 지원
- 서버를 실행하지 않고 매니페스트만 검사하는 static 모드 제공
- 결정론적(deterministic) 동작으로 모델 호출 없이 오프라인 실행 가능
우리는 우리가 배포하는 코드를 신뢰하지 않는 법을 배우는 데 수년을 보냈습니다. npm audit을 실행하고, 린터(linters)를 실행하며, 정적 분석(static analysis)을 통해 풀 리퀘스트(pull requests)를 제어합니다. 그러다 Model Context Protocol (MCP)이 등장했고, 우리는 언어 모델(language models)에게 실제 능력을 부여하기 시작했으며, 그 과정에서 그러한 규율의 대부분이 조용히 증발해 버렸습니다.
MCP 서버는 수동적인 데이터 소스가 아닙니다. 이는 모델에게 명령을 실행하고, 파일을 읽고, 내부 URL에 접속하며, 데이터베이스를 변경(mutate)할 수 있는 능력을 부여합니다. 권한이 과도하게 설정된 단 하나의 도구나, 리소스(resource)로 노출된 .env 파일은 유용한 에이전트를 원격 코드 실행(remote-code-execution) 또는 데이터 유출(data-exfiltration) 경로로 변질시킵니다. 하지만 대부분의 MCP 서버는 보안 검토(security review)가 전혀 이루어지지 않은 채 배포됩니다. 여러분이 에이전트에 연결하려는 그 접점(surface)을 위한 npm audit 같은 것은 존재하지 않습니다.
그래서 제가 직접 만들었습니다. 이름은 mcp-audit입니다.
핵심 아이디어
mcp-audit은 린터(linter)가 소스 파일을 다루는 방식과 동일하게 MCP 서버를 다룹니다. 서버에 연결하거나(또는 서버를 설명하는 매니페스트(manifest)를 읽거나) 하여, 서버가 광고하는 모든 도구(tool), 리소스(resource), 프롬프트(prompt)를 열거하고, 해당 접점에 대해 보안 규칙 카탈로그를 실행합니다. 그런 다음 고유 ID, 심각도(severity), 위치, 그리고 구체적인 해결 방법(remediation)과 함께 발견 사항을 보고합니다.
설계 목표는 의도적으로 좁게 설정되었습니다:
- 오프라인에서 실행되며 완전히 결정론적(deterministic)입니다. 모델 호출도 없고, 네트워크 휴리스틱(heuristics)도 없으며, 동일한 입력에는 항상 동일한 출력이 제공됩니다.
- CI(지속적 통합)에 바로 적용할 수 있습니다. JSON 출력과 SARIF 2.1.0 출력을 지원하므로, 발견 사항이 GitHub 코드 스캐닝(code scanning)의 주석(annotations)으로 나타납니다.
- 필요한 경우 빌드를 실패시킵니다. 발견된 사항이 설정 가능한 심각도 임계값(severity threshold)에 도달하면 프로세스가 0이 아닌 값으로 종료됩니다.
작동 방식
가장 빠르게 사용하는 방법은 stdio를 통해 생성한 서버를 가리키는 것입니다:
npx @royalpinto007/mcp-audit stdio "node my-mcp-server.js"
이 명령은 서버를 실행하고, MCP 핸드셰이크(handshake)를 수행하며, 도구와 리소스를 나열하도록 요청한 뒤, 반환된 내용을 감사(audit)합니다. 그 외 세 가지 진입점이 더 있습니다:
# HTTP를 통해 원격 서버를 감사하며, 베어러 토큰(bearer token)을 사용합니다
npx @royalpinto007/mcp-audit http https://mcp.example.com/mcp --token "$MCP_TOKEN"
...
static 모드는 보기보다 훨씬 중요합니다. 이 모드는 서버를 전혀 실행하지 않고도, 서버가 광고할 도구(tools), 리소스(resources), 프롬프트(prompts)의 JSON 매니페스트(manifest)를 린트(lint)합니다. 이는 서버를 신뢰할 수 없으며, 해당 서버가 사용자의 기기에서 실행되기 전에 검토하고자 할 때 정확히 필요로 하는 기능입니다.
현재 권한(permissions), 스키마(schema), 인젝션(injection), 비밀 정보(secrets), 전송(transport), 메타데이터(metadata), 위생(hygiene)과 같은 카테고리에 걸쳐 18개의 내장 규칙(built-in rules)이 있습니다. 각 규칙은 안정적인 MCPxxx ID를 가진 독립적인 모듈입니다. 이를 구체적으로 설명하기 위해, 파괴적 도구(destructive-tool) 규칙(MCP001)이 수행하는 작업의 대략적인 내용은 다음과 같습니다:
// MCP001 - 삭제를 암시하지만 확인 인자(confirmation argument)를 노출하지 않는 도구
const haystack = `${tool.name} ${tool.description ?? ""}`;
const hit = containsAny(haystack, DESTRUCTIVE_VERBS);
...
다른 규칙들도 동일한 형태를 따릅니다. MCP002는 이름이나 설명이 임의의 명령(command) 또는 쉘(shell) 실행을 암시하는 도구를 플래그(flag)합니다. MCP030은 읽기 가능한 리소스로 제공되는 .env 파일처럼 비밀 정보나 민감한 경로를 노출하는 리소스를 플래그합니다. MCP040은 인증이 없는 HTTP 전송(transports)을 플래그합니다. MCP041은 호출자가 제어하는 URL을 인자로 받는 도구를 플래그하며, 이는 전형적인 SSRF(Server-Side Request Forgery) 설정입니다. 또한 입력 스키마 누락 및 제약 없는 문자열 인자(unconstrained string arguments)에 대한 스키마 규칙, 설명에 심어진 프롬프트 인젝션(prompt-injection) 텍스트에 대한 인젝션 규칙, 그리고 중복된 도구 이름과 같은 위생(hygiene) 규칙도 존재합니다.
실행 결과는 다음과 같습니다:
CRITICAL (3)
MCP002 Arbitrary execution tool detected @ run_shell
Tool "run_shell" appears to execute commands or code (matched "shell").
...
모든 설정은 .mcpauditrc 파일을 통해 가능하며, mcp-audit은 작업 디렉토리로부터 상위 디렉토리로 탐색하며 이 파일을 찾아냅니다. 규칙을 비활성화하거나, 특정 규칙 세트만 실행하거나, 규칙의 심각도(severity)를 재매핑하거나, 위치 서브스트링(location substring)에 따라 탐지 결과를 무시하거나, 실패 임계값(fail-on threshold)을 설정할 수 있습니다. CI 레시피는 한 줄의 명령어와 SARIF 업로드로 구성됩니다:
npx @royalpinto007/mcp-audit static ./mcp-manifest.json \
--sarif --output mcp-audit.sarif --fail-on critical
전체 프로젝트는 TypeScript로 작성되었으며, 테스트 스위트에는 실제 모의 (mock) MCP 서버를 피스처 (fixture)로 포함한 47개의 테스트가 포함되어 있습니다. 이를 통해 테스트가 스텁 (stub)이 아닌 실제 stdio 전송 (transport) 방식을 실행하도록 합니다.
한 가지 솔직한 한계점
이 규칙들은 정적 (static)이며, 대부분 어휘적 (lexical)입니다. MCP001은 도구의 이름이나 설명에 파괴적인 동사가 포함되어 있고 스키마 (schema)에 확인 절차를 위한 파라미터 (parameter)가 없을 때 발생합니다. MCP002는 설명이 실행 용어와 일치할 때 발생합니다. 즉, 검사는 결정론적 (deterministic)이고 빠르지만, 도구가 실제로 무엇을 수행하는지가 아니라 도구가 자신을 어떻게 설명하는지를 바탕으로 추론합니다.
delete_everything이라는 정직한 이름을 가진 도구는 적발됩니다. 하지만 process_item이라는 평범한 이름을 가졌으면서 조용히 셸 명령 (shell out)을 실행하는 도구는 이름 및 스키마 휴리스틱 (heuristics)에 의해 플래그가 지정되지 않습니다. mcp-audit은 서버의 소스 코드를 볼 수 없기 때문입니다. 이 도구는 광고된 표면 (advertised surface)을 감사합니다. 광고된 표면은 모델이 실제로 보고 행동하는 영역이므로 이는 진정으로 유용하며, 광범위한 유형의 실제 실수들을 잡아낼 수 있습니다. 하지만 이것은 표면 린터 (surface linter)이지, 안전성에 대한 증명은 아닙니다. 깨끗한 보고서를 받았을 때 이를 "이 서버는 안전하다"가 아니라 "선언된 인터페이스 (interface)에 명백한 실수(footguns)가 없다"로 받아들이십시오.
직접 시도해보세요
MCP 서버를 구축하거나 도입하고 있다면, 에이전트가 사용하기 전에 먼저 실행해 보세요:
npx @royalpinto007/mcp-audit stdio "node my-mcp-server.js"
코드와 전체 규칙 카탈로그는 여기에서 확인할 수 있습니다: https://github.com/royalpinto007/mcp-audit
npm 패키지: https://www.npmjs.com/package/@royalpinto007/mcp-audit
새로운 규칙(rules)을 추가하는 것이 가장 유용한 기여입니다. 좋은 규칙이란 독립적으로 코딩되어 있고, 안정적인 ID를 가지며, 명확한 해결 방법(remediation)을 제공해야 합니다. 또한, 피스처(fixture)를 대상으로 해당 규칙을 실행하는 테스트와, 깨끗한 환경에서는 규칙이 작동하지 않음을 증명하는 테스트가 함께 배포되어야 합니다. 만약 계속해서 눈에 띄는 특정 유형의 MCP 실수(footgun)가 있다면, 그것은 추가할 가치가 있는 규칙입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기