MCP 서버를 연결하는 것은 JSON 블록 하나와 재시작만으로 끝납니다. 하지만 방금 무엇을 연결했는지 이해하는 데는 더 많은 시간이 걸립니다. 이 글에서는 주요 MCP 서버 보안 위험들을 위협 모델(threat model) 관점에서 살펴보겠습니다. 각 위험이 무엇인지, 실제 설정에서 어떻게 나타나는지, 그리고 실제로 이를 해결할 수 있는 완화책(mitigation)은 무엇인지 다룹니다.
(만약 짧은 운영 체크리스트를 원하신다면, 저는 도구 정의 드리프트(tool-definition drift), 파일 시스템 범위(filesystem scope), 환경 비밀(environment secrets), 이그레스 및 로깅에 대해 이전 게시물에서 다루었습니다. 이번 글은 _왜_라는 측면에서 더 넓게 다룹니다.)
MCP 서버가 실제로 무엇인지
MCP 서버는 이름, 설명 및 입력 스키마를 가진 **도구(tools)**를 광고하는 프로세스(또는 원격 엔드포인트)입니다. 클라이언트가 이 설명을 모델에 전달하고, 모델이 도구를 호출하기로 결정하면, 서버가 이를 실행합니다. 보통은 사용자의 권한과 환경 변수에 넣은 토큰을 사용하여 실행됩니다.
따라서 모든 서버는 에이전트의 세션에 세 가지 것을 기여합니다: 모델이 읽는 텍스트 (설명 및 결과), 기능(capabilities) (도구가 할 수 있는 것), 그리고 자격 증명(credentials) (무엇으로 인증하는지). 아래 나열된 각 위험은 이 중 하나를 공격합니다.
도구 설명은 인간을 위한 문서가 아니라 모델을 위한 프롬프트 텍스트입니다. 악의적이거나 손상된 서버는 설명에 지침을 넣을 수 있습니다(
동일한 문제가 tool _outputs_를 통해서도 발생합니다. GitHub 서버가 이슈 본문을 반환하고, 웹-fetch 서버가 페이지를 반환하며, 데이터베이스 서버가 다른 사람이 작성한 행(row)을 반환할 수 있습니다. 이 모든 것이 모델의 컨텍스트에 다른 모든 것과 동일한 권위로 들어옵니다.
완화 방안: 자연어는 신뢰성 있게 정제할 수 없습니다. 모든 결과를 신뢰할 수 없는 입력으로 취급하고, 세션이 이후에 수행할 수 있는 작업을 제한해야 합니다. 예를 들어, 세션이 비밀(secret) 형태의 자료를 읽은 후에는 나머지 시간 동안 외부 네트워크 이그레스(egress)를 거부하는 식입니다.
3. 범위가 너무 넓은 서버 (Over-scoped servers)
전형적인 사례는 파일 시스템 서버를 루트 디렉터리(/)나 홈 디렉터리(~)로 시작하여 '간단하게' 만드는 경우입니다. 이제 경로를 읽는 모든 도구 호출이 ~/.aws/credentials, ~/.kube/config 및 기계의 다른 모든 저장소에 접근할 수 있게 됩니다.
완화 방안: 프로젝트 디렉터리에서 루트 파일 시스템 서버를 운영해야 합니다. 서버가 제공하는 경우 읽기 전용 도구를 선호하십시오. 광범위한 서버는 서로 다른 프로젝트에 대해 다른 범위를 필요로 한다면 좁은 여러 개의 서버로 분할하십시오.
4. 클라이언트 설정 내 인라인 비밀 (Inline secrets in client config)
많은 설정 가이드가 토큰을 서버의 env 블록에 직접 붙여넣는 것을 보여줍니다. 이 파일은 디스크 상의 일반 JSON이며, 종종 홈 디렉터리에 있고, 기기 간 복사되거나 dotfile 저장소 및 화면 공유를 통해 전송됩니다.
완화 방안: 토큰을 시작 시 비밀 관리자(secret manager)나 OS 키체인에서 로드하고, 서버가 지원하는 가장 좁은 범위의 토큰 범위를 사용하며, 인라인으로 존재했던 모든 것은 주기적으로 교체해야 합니다.
5. 혼란스러운 대리인 (The confused deputy)
하나의 강력한 토큰을 가진 서버는 모델이 보내는 모든 요청을 처리합니다. 만약 모델이 사용자가 의도하지 않은 무언가(다른 저장소에 PR 열기, 비공개 프로젝트 읽기, 채널에 게시하기 등)를 요청하도록 유도된다면, 서버는 완전한 권한으로 이를 수행합니다. 이 서버는 자신이 실제로 누구를 위해 일하고 있는지 알 수 없는 대리인입니다.
완화 방안 (Mitigation): 작업별(per-task), 최소 권한 토큰 사용 (특정 리포지토리에 제한된 세분화된 GitHub 토큰, 읽기 전용 데이터베이스 역할). 원격 서버의 경우, 직접 장기간 사용하는 토큰을 전달하기보다 적절한 OAuth 플로우를 사용해야 합니다.
6. 공급망(Supply chain): npx -y와 변동하는 대상
"command": "npx", "args": ["-y", "some-mcp-server"]는 클라이언트가 시작할 때마다 최신으로 게시된 버전을 다운로드하고 실행합니다. 유지 관리자 계정이 손상되거나 오타로 인해 잘못 사용되는 이름(typo-squatted name)은 귀하의 권한으로 코드 실행을 유발할 수 있습니다.
완화 방안: 정확한 버전을 고정(pin exact versions)하고, 잠금 파일(lockfile)에서 설치하며, 모든 의존성 업그레이드 시와 마찬가지로 변경 사항을 검토해야 합니다. 원격 서버 역시 누가 운영하는지에 대한 동일한 수준의 심사가 필요합니다.
7. 설정 분산 및 그림자 서버 (Config sprawl and shadow servers)
Claude Code, Cursor, VS Code, CLI 에이전트 등에서 개별적으로 구성된 같은 서버가 있습니다. 각각은 자체 토큰과 범위(scope)를 가지고 있으며, 연결된 것을 한 곳에서 볼 수 없습니다. 하나를 취소하거나 범위를 축소해도 다른 것들은 계속 실행됩니다.
완화 방안: 먼저 재고 조사부터 해야 합니다. 어떤 클라이언트가 기기에 존재하는지, 각각이 어떤 서버를 시작하는지, 그리고 중복되는 부분이 어디에 있는지 알아야 합니다.
서버 앞에 정책을 배치하기
공통적인 핵심은 다음과 같습니다: 서버가 모델에 공급하는 텍스트를 전적으로 신뢰할 수 없으므로, _실제로 무엇이 실행될지_에 대한 결정은 모델이 논쟁할 수 없는 어딘가에서 이루어져야 합니다. 이것이 바로 클라이언트와 서버 사이에 위치한 권한 부여 계층(authorization layer)의 역할입니다.
Cirvix AgentControl는 이러한 오픈 소스 옵션 중 하나입니다. 두 부분이 위에서 언급된 위험에 대응합니다.
재고 조사 (Inventory) (위험 3, 4, 7). cirvix scan은 로컬의 알려진 클라이언트 구성(Claude Code, Cursor, Windsurf, Cline, Roo Code, Codex CLI, Gemini CLI, VS Code 등)을 검사하여 mcp-broad-scope, mcp-inline-secrets (키 이름만 보고 값은 아님), 그리고 mcp-duplicated와 같은 발견 사항을 보고합니다:
npx @cirvix_ai/agent-control scan
npx @cirvix_ai/agent-control scan --deep --sarif mcp-scan.sarif
이는 실행 중인 에이전트가 무엇을 하는지에 대한 증명이 아니라 휴리스틱(heuristic) 구성 검사이며, 홈 디렉터리 설정을 읽으므로 권한이 부여된 곳에서만 실행해야 합니다.
Gateway (위험 1, 2, 5). cirvix gateway는 클라이언트의 유일한 MCP 진입점이 되며 실제 서버를 별도의 파일에서 구동합니다. 라우팅되는 모든 tools/call은 전달되기 전에 정책에 따라 평가됩니다. 규칙은 상위(upstream) 서버 이름을 지정할 수 있으므로, 도구 호출은 설명이 약속한 것이 아니라 어디로 가고 무엇을 하는지에 의해 판단됩니다:
require_approval:
name = hold-github-calls
server = github
...
도구 이름은 휴리스틱하게 정규화된 동작에 매핑됩니다(예: create_issue라는 도구가 예상하는 액션 클래스에 배치된다는 보장이 없습니다). 따라서 이렇게 광범위하게 시작한 다음, 더 좁은 개별 도구 규칙을 작성하기 전에 cirvix logs에서 정규화된 동작을 확인하십시오.
게이트웨이를 유일한 진입점으로 사용하는 클라이언트 구성:
{
"mcpServers": {
"cirvix": {
...
명확하게 밝히자면, 게이트웨이는 자신이 라우팅하는 MCP 호출만 관리합니다. 만약 클라이언트 구성에 직접적인 서버 항목이 남아 있거나, 에이전트가 편집기 내장 도구를 사용하거나 서브 프로세스를 생성하는 경우, 해당 호출은 그 경계 밖에 있습니다. 이는 프롬프트 주입(prompt injection) 텍스트를 감지하거나 차단하지 않으며, 이후의 동작을 제약합니다.
추가하는 모든 서버에 대한 5분 검토
- 모든 도구 설명을 읽어보십시오. 모델이 이 모든 것을 준수하는 것에 불편함을 느끼시겠습니까?
- 파일 시스템 또는 API 범위는 무엇입니까? 범위를 좁히세요.
- 토큰은 어디에 존재하며, 그 토큰으로 무엇을 할 수 있습니까?
- 버전이 고정(pinned)되어 있습니까?
- 이 장치의 다른 곳에서 구성되어 있습니까?