Безопасность MCP (Model Context Protocol) почти никогда не попадает в фокус на старте проекта: разработчики подключают сервер в development-режиме через claude mcp add, видят, что tool-вызовы работают, и идут дальше. К моменту, когда AI-агенты выходят в production, дефолтная MCP конфигурация превращается в дыру — wildcard-tools, plaintext-credentials в .mcp.json, неограниченные scopes и процесс с полным доступом к $HOME. Эти MCP best practices собраны как инженерный чек-лист из десяти настроек hardening'а: каждую можно применить за один деплой и проверить одной командой. Это не каталог векторов атак (он есть вотдельной статье про утечку credentials), а конкретный список того, что включить, ограничить и залогировать.
Краткий ответ
Что вы узнаете
- Почему дефолтный MCP-сервер не годится для production и что именно закрывать.
- 10 практик hardening'а: scopes, allowlist, sandbox, eBPF, vault, аудит, policy, бюджет, provenance, таймауты.
- Готовый
.mcp.json, systemd-unit и Rego-политика для копирования. - Чек-маркер для каждой практики — команда проверки, что настройка реально включена.
10
Почему MCP требует hardening
Model Context Protocol стандартизирует, как LLM вызывает внешние инструменты: клиент (Cursor, Claude Code, Copilot) общается с MCP-сервером по stdio или HTTP/SSE, а сервер превращает запрос модели в реальное действие — SQL-запрос, файловую операцию, HTTP-вызов. Архитектура элегантная, но у неё есть три свойства, которые делают дефолт опасным.
Первое — сервер работает с привилегиями пользователя, который его запустил: студенческий MCP-сервер для чтения ./docs имеет тот же доступ к файловой системе, что и ваш SSH-ключ. Второе — инструмент активируется по текстовой инструкции модели, а значит prompt injection напрямую управляет surface area атаки. Третье— гранулярность доступа задаётся реализацией конкретного сервера, и в большинстве из неё нет: либо «всё разрешено», либо «никаких tools».
Это сочетание подтверждается OWASP Top 10 for LLM Applications (LLM06 — чрезмерное агентское действие) и подробно разбирается в гайде по production-деплою Claude Code. Десять MCP security-практик ниже систематически закрывают каждый зазор: от scopes на уровне протокола до eBPF-мониторинга на уровне ядра.
Практика 1: Минимизация scopes
Практика. Каждому MCP-серверу назначать минимальный набор scopes — ровно те ресурсы, которые нужны для его задачи, и ни одним больше.
Почему важно. Scope определяет, к чему tool-сервер может дотянуться: файлы, базы, API-эндпоинты, конкретные таблицы. Чем шире scope, тем больше blast radius при compromise или prompt injection. Сервер, которому нужен только read-only доступ к одной таблицеdocs, не должен видеть pg_shadow или /etc.
# .mcp.json — scopes вместо wildcard{ "mcpServers": { "knowledge-base": { "command": "npx", "args": ["-y", "@flowlink/mcp-pg"], "env": { "DATABASE_URL": "flowlink://ref/docs-db-readonly" }, "scopes": [ "db:read:public.docs", "db:read:public.snippets" ], "denyScopes": ["db:write:*", "fs:*", "shell:*"] } }}Заметьте паттерн: явные denyScopes поверх позитивного списка. Это защита от будущего — если новая версия сервера запросит расширенные права, они не пройдут молча.
Чек-маркер: ☐ в .mcp.json нет строки "scopes": ["*"] ни у одного сервера.
Практика 2: Allowlist tools вместо wildcard
Практика. Разрешать на уровне хоста только именованные tools, а не весь каталог, который публикует сервер.
Почему важно. Один MCP-сервер может экспортировать десятки tools:read_file, write_file, exec, fetch_url. Дефолтное поведение большинства клиентов — регистрировать все. Allowlist превращает «что умеет сервер» в «что разрешено вызывать модели», и это две разные вещи.
# Хост-конфиг (flowlink.config.yaml)tools: allow: - knowledge-base.search_docs - knowledge-base.get_snippet deny: - "*.exec" - "*.shell" - "*.write_*" requireApproval: - "*.delete_*" - "*.drop_*"Здесь же — правило requireApproval: деструктивные tools блокируются до явного подтверждения человеком. Это продолжение темызащиты от деструктивных команд в Cursor, применённое уже к MCP-интерфейсу.
Чек-маркер: ☐ flowlink tools list показывает только те tools, что перечислены в allow.
Практика 3: Изоляция процесса через systemd sandbox
Практика. Запускать каждый MCP-сервер как изолированный systemd-unit с ограничениями на файловую систему, сеть и системные вызовы.
Почему важно. Scopes и allowlist ограничивают логический слой протокола. Но если в сервере RCE-уязвимость, атака уходит ниже — на уровень ОС. systemd даёт declaraтивный sandbox, который работает независимо от корректности приложения.
# /etc/systemd/system/mcp-knowledge.service[Service]Type=simpleExecStart=/usr/bin/npx -y @flowlink/mcp-pgUser=mcp-svcGroup=mcp-svc# Файловая система — только своё и только на чтение остальноеProtectSystem=strictProtectHome=truePrivateTmp=trueReadWritePaths=/var/lib/mcp-knowledge# Сеть — только нужный хостIPAddressAllow=10.0.1.20RestrictAddressFamilies=AF_INET AF_UNIX# Системные вызовы — минимальный whitelistSystemCallFilter=@system-serviceSystemCallFilter=~@privileged @resources @mountNoNewPrivileges=trueRestrictSUIDSGID=trueCapabilityBoundingSet=[Install]WantedBy=multi-user.targetЭтот же приём рекомендует NIST SP 800-190 (контейнерная безопасность) для любого сервиса, обрабатывающего недоверенный ввод. Для MCP-сервера недоверенный ввод — это, по сути, весь трафик от модели.
Чек-маркер: ☐ systemd-analyze security mcp-knowledge.service показывает оценку ≤ 3.0 (из 10, где меньше — безопаснее).
Практика 4: eBPF shield для системных вызовов
Практика. Поверх systemd-seccomp поднять eBPF-политику, которая перехватывает системные вызовы и сетевые соединения на уровне ядра, в runtime.
Почему важно. systemd фильтрует по имени syscall, а eBPF — по контексту: кто, когда, к какому хосту, с каким payload-размером. Это позволяет, например, разрешитьconnect() к базе, но заблокировать исходящие на *.amazonaws.com или любые non-TLS соединения. seccomp статичен; eBPF — runtime-observability и enforcement в одном.
# flowlink shield policy (YAML)shield: enabled: true target: mcp-knowledge.service syscalls: deny: [ptrace, keyctl, bpf, unshare] network: egress: allow: - cidr: 10.0.1.20/32 port: 5432 deny: - cidr: 0.0.0.0/0 port: 443 # блокируем exfiltration через HTTPS on_violation: block_and_alertFlowLink eBPF Shield работает по модели allow-list egress: по умолчанию исходящий трафик запрещён, и вы явно открываете только нужные destination'ы. Это убивает целый класс атак, где скомпрометированный server exfiltrate'ит данные на внешний endpoint.
Чек-маркер: ☐ flowlink shield status mcp-knowledge показывает violations: 0 и egress policy: enforce.
Практика 5: Credential vault вместо plaintext-окружения
Практика. Никогда не класть секреты в env-блок .mcp.jsonнапрямую. Использовать ссылку на vault, которая резолвится только в момент вызова.
Почему важно. Файл .mcp.json попадает в git, в бэкапы, в логи деплоя и в контекст самой модели, если она читает конфиг. Plaintext-токен в env — самая частая причина инцидентов с MCP, и единственная, которую можно устранить за пять минут.
# Плохо"env": { "DATABASE_URL": "postgres://admin:s3cr3t@db:5432/prod" }# Хорошо — ссылка на FlowLink Credential Vault"env": { "DATABASE_URL": "flowlink://ref/docs-db-readonly"},"vault": { "provider": "flowlink", "ttlSeconds": 3600, "leakProtection": true}Vault резолвит flowlink://ref/... в реальный DSN только внутри процесса MCP-сервера, ротирует ключи по TTL и помечает их в gitleaks-сканере как known-good, чтобы случайно не поднять ложную тревогу. Подробнее о схеме работы vault — в статье пропять векторов утечки credentials в MCP.
Чек-маркер: ☐ grep -rEn "(password|secret|token|AKIA)" .mcp.json возвращает 0 строк.
Практика 6: Аудит-журнал всех tool-вызовов
Практика. Записывать каждый tool-call в append-only журнал: who, what, when, what-args (без секретов), какой результат, сколько времени.
Почему важно. Без журнала инцидент с MCP невозможно расследовать вообще. Вы не узнаете, какой tool вызвала модель, какие аргументы передала, какой файл она в итоге прочитала. Аудит-журнал — это и форензика, и база для алёртов (практика 8), и compliance по ISO 27001 A.12.4 / SOC 2 CC7.2.
# flowlink audit configaudit: sink: postgres table: mcp_tool_calls fields: - ts - session_id - principal # SPIFFE SVID, не "user" - tool - args_hash # sha256, без plaintext секретов - args_redacted # секреты замаскированы как ***REDACTED*** - result_status - duration_ms - policy_decision # allow | deny | prompt tamperProtection: forward-chaining-hmacЗапись идёт в mcp_tool_calls с HMAC forward-chaining — каждая строка подписана ключом предыдущей, поэтому удалить или изменить запись незаметно нельзя. Это та же техника, что в блокчейне, и она проходит аудит на tamper-evidence. Как агрегировать эти данные в дашборды и алёрты, описано в материале промониторинг активности AI-агентов на сервере.
Чек-маркер: ☐ SELECT count(*) FROM mcp_tool_calls WHERE ts > now()-interval '1 hour' > 0 после тестового вызова.
Практика 7: Policy engine на OPA/Rego
Практика. Выносить решения «разрешить/запретить» в декларативную policy на Rego, а не раскидывать if-проверки по коду сервера.
Почему важно. Policy в коде невозможно аудировать, версионировать и быстро менять. OPA (Open Policy Agent) с языком Rego — индустриальный стандарт (CNCF graduated), который отделяет enforcement от бизнес-логики. Вы пишете правило один раз и применяете его и к MCP, и к Kubernetes, и к API-gateway.
# /etc/flowlink/policy/mcp.regopackage flowlink.mcpdefault allow := false# Разрешить только read-доступ к docs в рабочее времяallow if { input.tool in {"knowledge-base.search_docs", "knowledge-base.get_snippet"} input.action == "read" time.weekday(input.ts) in workdays time.parse_rfc3339(input.ts) >= time.add(now(), -duration("1h")).__val__ not sensitive_table(input.args)}workdays := {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"}sensitive_table(args) if { re_match("(?i)\\b(users|billing|secrets|pg_shadow)\\b", args.query)}# Деструктивные операции — только с явным approvaldeny[msg] if { input.action == "write" not input.approval msg := sprintf("write requires approval: %v", [input.tool])}Тот же Rego-файл ходит в unit-тесты (opa test mcp.rego mcp_test.rego), в CI и в runtime enforcement. Изменение политики проходит code-review, а не деплой новой версии MCP-сервера.
Чек-маркер: ☐ opa check /etc/flowlink/policy выходит без ошибок; flowlink policy test зелёный.
Практика 8: Бюджетный governor для tool-вызовов
Практика. Ставить rate-limit и стоимостный потолок на tool-вызовы, не только на LLM-токены.
Почему важно. Две группы атак на MCP экономят не токены, а ваши API-лимиты и кошелёк: (1) feedback-loop, где модель рекурсивно вызывает дорогой tool (внешний платный API, тяжёлый SQL), (2) resource exhaustion — SELECT * на таблице в 50 млн строк кладёт базу. Governor ставит hard-cap и на то, и на другое.
# flowlink budget governorbudget: window: 1h per_session: max_tool_calls: 200 max_cost_usd: 5.00 # внешние API-вызосы per_tool: "knowledge-base.search_docs": max_calls_per_minute: 30 max_result_rows: 1000 on_exceed: action: throttle alert: "#sec-ai-agents"Это прямой родственник бюджетного governor'а для LLM-вызовов изсоответствующей статьи — та же механика (скользящее окно, hard/soft limit, throttling вместо молчаливого провала), но применённая к tool-layer.
Чек-маркер: ☐ в Prometheus метрика flowlink_tool_calls_total{status="throttled"} существует (значение может быть 0, метрика должна быть определена).
Практика 9: Подпись и пиннинг MCP-серверов (provenance)
Практика. Пиннинг конкретной версии MCP-сервера и проверка его подписи (SLSA-провенанс) перед запуском. Никаких @latest в production.
Почему важно. Экосистема MCP-серверов — это npm-пакеты и Docker-образы от десятков авторов. Supply-chain-атака (compromised upstream, typosquatting, malicious PR) — реальный вектор: достаточно один раз обновить @example/mcp-db и получить backdoor на всех, кто запускал npx. SLSA + cosign закрывают именно это.
# Пиннинг по digest и проверка подписи{ "mcpServers": { "knowledge-base": { "command": "npx", "args": [ "-y", "@flowlink/mcp-pg@1.4.2", "--integrity", "sha512-L0gq...8w==" ], "provenance": { "verify": true, "sigstore": true, "expectedIssuer": "https://token.actions.githubusercontent.com", "expectedSource": "github.com/flowlink/mcp-pg", "expectedDigest": "sha256:a1b2c3..." } } }}При каждом запуске FlowLink проверяет Sigstore-подпись образа, сравнивает source-uri с ожидаемым и сверяет digest. Любое несовпадение — server не стартует, в audit-журнал летит событие provenance_mismatch. Это уровень защиты, рекомендованный SLSA Build Level 3.
Чек-маркер: ☐ flowlink verify --all показывает verified для каждого сервера; ни одного @latest в конфиге.
Практика 10: Таймауты и circuit breaker
Практика. На каждый tool навесить жёсткий таймаут и circuit breaker, который размыкается после N ошибок подряд.
Почему важно. Зависший tool — это не просто медленный ответ. В синхронной модели это заблокированный session, в асинхронной — накапливающиеся connection'ы и падение по OOM. Circuit breaker переводит каскадный сбой в graceful degradation: несколько таймаутов подряд → tool временно отключается → модель получает понятное сообщение вместо зависания.
# flowlink resilience configresilience: default: timeout_ms: 5000 retry: { max_attempts: 2, backoff: exponential } circuitBreaker: enabled: true failure_threshold: 5 # 5 ошибок подряд failure_window: 60s open_duration: 120s # 2 минуты в открытом состоянии half_open_probes: 1 per_tool: "knowledge-base.search_docs": timeout_ms: 3000 failure_threshold: 3 "external.weather_api": timeout_ms: 2000 circuitBreaker: open_duration: 600s # внешний — дольше "отдыхает"Таймаут и breaker решают и проблему безопасности: длинный зависший запрос — частый признак SSRF-попытки или эксплуатации time-based SQL injection. Быстрый fail = меньше окно атаки.
Чек-маркер: ☐ метрика flowlink_circuit_breaker_state{tool="..."} присутствует; тест с искусственным таймаутом переводит state в open.
Готовый .mcp.json
Ниже — собранный воедино .mcp.json, в котором применены все десять практик. Это отправная точка для production-деплоя: замените имена серверов и resource-refs на свои и прогоните чек-маркеры сверху вниз.
{ "$schema": "https://flowlink.dev/schema/mcp.json", "mcpServers": { "knowledge-base": { "command": "npx", "args": ["-y", "@flowlink/mcp-pg@1.4.2", "--integrity", "sha512-L0gq...8w=="], "env": { "DATABASE_URL": "flowlink://ref/docs-db-readonly", "LOG_LEVEL": "warn" }, "scopes": ["db:read:public.docs", "db:read:public.snippets"], "denyScopes": ["db:write:*", "fs:*", "shell:*"], "vault": { "provider": "flowlink", "ttlSeconds": 3600 }, "provenance": { "verify": true, "sigstore": true, "expectedSource": "github.com/flowlink/mcp-pg" }, "tools": { "allow": ["search_docs", "get_snippet"] }, "resilience": { "timeout_ms": 3000, "circuitBreaker": { "failure_threshold": 3, "open_duration": 120 } } } }, "policy": { "engine": "opa", "path": "/etc/flowlink/policy/mcp.rego" }, "budget": { "window": "1h", "per_session": { "max_tool_calls": 200, "max_cost_usd": 5.00 } }, "audit": { "sink": "postgres", "tamperProtection": "forward-chaining-hmac" }, "shield": { "enabled": true, "egress": "allow-list" }}Сводный чек-лист: 10 практик MCP security
| # | Практика | Что закрывает | Проверка |
|---|---|---|---|
| 1 | Минимизация scopes | Чрезмерный доступ к данным | Нет "*" в scopes |
| 2 | Allowlist tools | Неавторизованные действия модели | flowlink tools list = allow |
| 3 | systemd sandbox | RCE на уровне ОС | security score ≤ 3.0 |
| 4 | eBPF shield | Exfiltration и lateral movement | shield status: enforce |
| 5 | Credential vault | Plaintext-утечки секретов | grep по конфигу пуст |
| 6 | Аудит-журнал | Невозможность форензики | Строки в mcp_tool_calls |
| 7 | OPA/Rego policy | Логика разрешений в коде | opa check чист |
| 8 | Бюджетный governor | Resource exhaustion, cost-bomb | Метрика throttled существует |
| 9 | Provenance / pinning | Supply-chain-атаки | flowlink verify --all = ok |
| 10 | Таймауты + breaker | Каскадные сбои, SSRF-window | state ∈ open при тесте |
- Протокол
- Ядро ОС
- Данные
- Логика
- Инфраструктура
Десять настроек покрывают стек целиком: протокол (scopes, allowlist) → ядро ОС (systemd, eBPF) → данные (vault, аудит) → логика (policy, budget) → инфраструктура (provenance, resilience). Применяйте их сверху вниз — так каждая следующая MCP конфигурацияопирается на уже включённую предыдущую, и к концу прохода вы получаете воспроизводимый, аудируемый деплой по этим MCP best practices, а не набор разрозненных твиков.
Порядок внедрения 10 практик MCP security
Реалистичный четырёхфазный план — от быстрых побед на уровне протокола до supply-chain и устойчивости.
Фаза 1 — Протокол (практики 1–2)
Начните с
.mcp.jsonи хост-конфига: пропишите минимальныеscopes/denyScopesи allowlist tools вместо wildcard. Это быстрая победа, которая сужает blast radius ещё до любого изменения инфраструктуры.Фаза 2 — Изоляция и ядро ОС (практики 3–4)
Переведите MCP-серверы в выделенные systemd-unit'ы с sandbox-директивами и поднимите FlowLink eBPF shield с allow-list egress. Теперь даже RCE в сервере не даёт доступа за пределы sandbox.
Фаза 3 — Данные и логика (практики 5–8)
Подключите credential vault, включите append-only аудит-журнал с HMAC forward-chaining, вынесите разрешения в OPA/Rego и поставьте бюджетный governor на tool-вызовы. Эти четыре настройки дают форензику, compliance и защиту от cost-bomb.
Фаза 4 — Целостность и устойчивость (практики 9–10)
Завершите hardening пиннингом версий с SLSA/Sigstore provenance и навесьте таймауты с circuit breaker на каждый tool. Теперь деплой защищён и от supply-chain-атак, и от каскадных сбоев.
Чек-лист hardening'а: стоит ли внедрять все 10 практик
Плюсы
- Покрытие всего стека: от протокола (scopes) до ядра ОС (eBPF) и инфраструктуры (provenance)
- Каждая настройка проверяется одной командой — воспроизводимый, аудируемый деплой
- Слои независимы: sandbox работает, даже если в приложении найдётся RCE
- Аудит-журнал и OPA/Rego дают форензику и compliance (ISO 27001 A.12.4, SOC 2 CC7.2)
Минусы
- Эксплуатационная сложность: systemd, eBPF, OPA и vault нужно поддерживать вместе
- Требуется экспертиза в Linux-security и supply-chain (SLSA, Sigstore, cosign)
- Часть практик (eBPF shield, provenance-верификация) добавляет накладные расходы на запуск
- Начальный порог входа выше, чем у дефолтного npx-запуска без ограничений
Для собственных MCP-серверов
Часто задаваемые вопросы
Что такое MCP security и зачем нужен hardening Model Context Protocol?
MCP (Model Context Protocol) стандартизует, как LLM-клиент (Cursor, Claude Code, Copilot) вызывает внешние инструменты через MCP-сервер. Дефолтная конфигурация даёт серверу привилегии запустившего пользователя, wildcard-tools и plaintext-секреты в .mcp.json, поэтому hardening сужает blast radius: от минимизации scopes на уровне протокола до eBPF-мониторинга на уровне ядра.
Какие 10 практик входят в чек-лист MCP security?
(1) минимизация scopes, (2) allowlist tools, (3) systemd-sandbox, (4) eBPF shield, (5) credential vault, (6) аудит-журнал tool-вызовов, (7) OPA/Rego policy, (8) бюджетный governor, (9) подпись и пиннинг серверов (SLSA-provenance), (10) таймауты и circuit breaker. Они покрывают пять слоёв: протокол → ядро ОС → данные → логику → инфраструктуру.
Чем scopes отличаются от allowlist tools?
Это два разных слоя. Scopes описывают, к каким ресурсам сервер может дотянуться (db:read:public.docs, fs:*), а allowlist tools — какие инструменты модели разрешено вызывать независимо от того, что публикует сервер. Scopes ограничивают сервер, allowlist ограничивает модель.
Обязательны ли все 10 практик для production?
Минимальный набор — scopes, allowlist, credential vault, аудит-журнал и таймауты: они закрывают самые частые инциденты за один деплой. eBPF shield, systemd-sandbox, OPA/Rego, бюджетный governor и SLSA-provenance рекомендованы для зрелых деплоев, где есть compliance-требования (ISO 27001, SOC 2) и реальный риск supply-chain-атак.
Даёт ли статья готовый конфиг для копирования?
Да. В статье есть готовый .mcp.json со всеми десятью настройками, отдельный systemd-unit mcp-knowledge.service с sandbox-директивами и Rego-политика mcp.rego для OPA. Замените имена серверов и resource-refs на свои и прогоните чек-маркеры.
Как проверить, что каждая настройка реально включена?
У каждой практики есть чек-маркер — одна команда. Например, flowlink tools list для allowlist, systemd-analyze security для sandbox (цель ≤ 3.0), opa check для политики, flowlink verify --allдля provenance. Если все чек-маркеры проходят — деплой соответствует чек-листу.
Готовы защитить свои AI-агенты?
Начните с бесплатного аудита безопасности ваших AI-инструментов.
Получить бесплатный аудит