← Cloudflare One / cloudflare-one / tutorials
Обнаружение трафика MCP в журналах Gateway
Организациям может не хватать видимости трафика Model Context Protocol (MCP), из-за чего сотрудники могут подключаться к удалённым MCP-серверам в обход контроля ИТ-отдела. Такие подключения создают риск утечки конфиденциальных внутренних данных и учётных данных, атак с внедрением инструкций, а также рисков для цепочки поставок программного обеспечения.
Как ИТ-администратор, вы хотите выявлять теневой трафик MCP, чтобы предотвратить несанкционированную утечку данных, не блокируя при этом легитимные сценарии использования. В этом руководстве вы будете использовать Cloudflare GraphQL Analytics API для сканирования HTTP-журналов Gateway на предмет паттернов трафика MCP, создавать профили DLP, обнаруживающие методы MCP JSON-RPC, и классифицировать трафик, чтобы отличать авторизованный трафик, направляемый на порталы MCP-серверов, от трафика, отправляемого на «теневые» удалённые MCP-серверы.
Предварительные требования
- Аккаунт Cloudflare с организация Zero Trust
- Gateway с включённой HTTP-фильтрацией и активным проксированием трафика пользователей
- Одна API-токен со следующими разрешениями:
- На уровне аккаунта
Zero Trust: Read - На уровне аккаунта
DLP: Write - На уровне аккаунта
Gateway: Write
- На уровне аккаунта
- Идентификатор вашего аккаунта Cloudflare (доступен в Панель управления Cloudflare ↗ в разделе Account Home)
- Знакомство с GraphQL Analytics API запросы
- Практические знания TypeScript и REST API
1. Просмотрите набор данных Gateway HTTP
gatewayHttpRequestsAdaptiveGroups dataset в GraphQL Analytics API предоставляет агрегированные данные журналов HTTP Gateway. Используйте этот dataset для запроса шаблонов трафика, связанных с MCP:
- Измерения:
httpHost,httpRequestURI,action,users,dlpProfiles - Диапазон времени: До 30 дней исторических данных
- Группировка: Группирует результаты по значениям измерений
- Фильтрация: Поддерживает
OR,AND, а такжеlikeоператоры
2. Создайте запрос обнаружения MCP
Трафик MCP можно определить по трём признакам:
- Шаблоны доменов: Имена хостов, содержащие
mcp(например,mcp.datadog.com) - URL paths: Стандартные конечные точки MCP, такие как
/mcp,/mcp/sse, а также/sse - Совпадения DLP: Методы JSON-RPC в теле запроса (рассматриваются в одном из следующих шагов)
Следующий запрос GraphQL сканирует журналы Gateway на наличие первых двух сигналов:
const query = `
query MCPTrafficScan($accountTag: string, $since: string, $until: string) {
viewer {
accounts(filter: { accountTag: $accountTag }) {
gatewayHttpRequestsAdaptiveGroups(
filter: {
datetime_geq: $since
datetime_leq: $until
OR: [
{ httpHost_like: "%mcp%" }
{ httpRequestURI_like: "%/mcp%" }
{ httpRequestURI_like: "%/sse%" }
]
}
limit: 10000
) {
dimensions {
httpHost
action
users
}
count
}
}
}
}
`;
const variables = {
accountTag: "<YOUR_ACCOUNT_ID>",
since: "<START_DATE>", // ISO-8601 format, for example 2025-03-08T00:00:00Z
until: "<END_DATE>", // Up to 30 days after start date
};
const response = await fetch("https://api.cloudflare.com/client/v4/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query, variables }),
});
const data = await response.json();
const groups =
data.data?.viewer?.accounts?.[0]?.gatewayHttpRequestsAdaptiveGroups || [];const query = `
query MCPTrafficScan($accountTag: string, $since: string, $until: string) {
viewer {
accounts(filter: { accountTag: $accountTag }) {
gatewayHttpRequestsAdaptiveGroups(
filter: {
datetime_geq: $since
datetime_leq: $until
OR: [
{ httpHost_like: "%mcp%" }
{ httpRequestURI_like: "%/mcp%" }
{ httpRequestURI_like: "%/sse%" }
]
}
limit: 10000
) {
dimensions {
httpHost
action
users
}
count
}
}
}
}
`;
const variables = {
accountTag: "<YOUR_ACCOUNT_ID>",
since: "<START_DATE>", // ISO-8601 format, for example 2025-03-08T00:00:00Z
until: "<END_DATE>", // Up to 30 days after start date
};
const response = await fetch("https://api.cloudflare.com/client/v4/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query, variables }),
});
const data = await response.json();
const groups =
data.data?.viewer?.accounts?.[0]?.gatewayHttpRequestsAdaptiveGroups || [];Замените <YOUR_ACCOUNT_ID> с ID вашего аккаунта Cloudflare. Замените <START_DATE> и <END_DATE> с временными метками в формате ISO-8601, охватывающими нужный вам диапазон времени (до 30 дней).
3. Обработайте результаты запроса
Каждая группа в ответе представляет агрегированный трафик для определённого httpHost и action комбинация. Разберите результаты, чтобы определить незаблокированные подключения MCP:
const hits = groups.map((group) => ({
domain: group.dimensions.httpHost,
requestCount: group.count,
users: group.dimensions.users || [],
actions: {
allowed: group.dimensions.action === "allow" ? group.count : 0,
blocked: group.dimensions.action === "block" ? group.count : 0,
},
}));
const totalMCPRequests = hits.reduce((sum, h) => sum + h.requestCount, 0);
const unblockedHits = hits.filter((h) => h.actions.allowed > 0);
console.log(`Found ${totalMCPRequests} MCP requests`);
console.log(`${unblockedHits.length} destinations are unblocked`);interface MCPTrafficHit {
domain: string;
requestCount: number;
users: string[];
actions: {
allowed: number;
blocked: number;
};
}
const hits: MCPTrafficHit[] = groups.map((group: any) => ({
domain: group.dimensions.httpHost,
requestCount: group.count,
users: group.dimensions.users || [],
actions: {
allowed: group.dimensions.action === "allow" ? group.count : 0,
blocked: group.dimensions.action === "block" ? group.count : 0,
},
}));
const totalMCPRequests = hits.reduce((sum, h) => sum + h.requestCount, 0);
const unblockedHits = hits.filter((h) => h.actions.allowed > 0);
console.log(`Found ${totalMCPRequests} MCP requests`);
console.log(`${unblockedHits.length} destinations are unblocked`);Ключевые выводы по данным:
- Разблокированный трафик (
action=allow) - Активные подключения MCP, которые требуют проверки или блокировки - Заблокированный трафик (
action=block) - ваши существующие политики работают - Атрибуция пользователя: Показывает, какие сотрудники подключаются к серверам MCP
4. Создайте профили DLP для обнаружения MCP JSON-RPC
Политики HTTP Gateway могут сопоставлять домены и пути URL, но не могут проверять тела запросов. Профили DLP сканируют POST содержимого тела на предмет шаблонов, что полезно для выявления теневых MCP, поскольку MCP использует JSON-RPC поверх HTTP и имеет ряд характерных признаков.
Каждый запрос MCP содержит "method" поле:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "read_file", "arguments": { "path": "/etc/passwd" } }
}Злоумышленник может запустить сервер MCP на нестандартном домене (например, internal-tools.company.com/api/assistant) без срабатывания правил на основе домена или пути. Вы можете использовать DLP-сканирование POST тела для "method": "tools/call" и другие специфичные для MCP шаблоны для более надежной защиты трафика MCP.
Проверить ограничения DLP
Прежде чем создавать шаблоны обнаружения, учтите следующие ограничения DLP:
- Синтаксис регулярных выражений : Регулярные выражения Rust (немного отличаются от JavaScript и PCRE)
- Глубина сканирования : Только первые 1,024 байт тела запроса
- Только POST : DLP сканирует только
POSTзапросы - Производительность : Шаблоны регулярных выражений должны быть эффективными, чтобы избежать катастрофического бэктрекинга
Создание шаблонов обнаружения MCP
Индикаторы MCP можно найти в полях метода JSON-RPC. Следующие регулярные выражения покрывают основные методы протокола MCP:
const DLP_REGEX_PATTERNS = [
{
name: "MCP Initialize Method",
regex: '"method"\\s{0,5}:\\s{0,5}"initialize"',
},
{
name: "MCP Tools Call",
regex: '"method"\\s{0,5}:\\s{0,5}"tools/call"',
},
{
name: "MCP Tools List",
regex: '"method"\\s{0,5}:\\s{0,5}"tools/list"',
},
{
name: "MCP Resources Read",
regex: '"method"\\s{0,5}:\\s{0,5}"resources/read"',
},
{
name: "MCP Resources List",
regex: '"method"\\s{0,5}:\\s{0,5}"resources/list"',
},
{
name: "MCP Prompts List",
regex: '"method"\\s{0,5}:\\s{0,5}"prompts/(list|get)"',
},
{
name: "MCP Sampling Create Message",
regex: '"method"\\s{0,5}:\\s{0,5}"sampling/createMessage"',
},
{
name: "MCP Protocol Version",
regex: '"protocolVersion"\\s{0,5}:\\s{0,5}"202[4-9]',
},
{
name: "MCP Notifications Initialized",
regex: '"method"\\s{0,5}:\\s{0,5}"notifications/initialized"',
},
{
name: "MCP Roots List",
regex: '"method"\\s{0,5}:\\s{0,5}"roots/list"',
},
];const DLP_REGEX_PATTERNS = [
{
name: "MCP Initialize Method",
regex: '"method"\\s{0,5}:\\s{0,5}"initialize"',
},
{
name: "MCP Tools Call",
regex: '"method"\\s{0,5}:\\s{0,5}"tools/call"',
},
{
name: "MCP Tools List",
regex: '"method"\\s{0,5}:\\s{0,5}"tools/list"',
},
{
name: "MCP Resources Read",
regex: '"method"\\s{0,5}:\\s{0,5}"resources/read"',
},
{
name: "MCP Resources List",
regex: '"method"\\s{0,5}:\\s{0,5}"resources/list"',
},
{
name: "MCP Prompts List",
regex: '"method"\\s{0,5}:\\s{0,5}"prompts/(list|get)"',
},
{
name: "MCP Sampling Create Message",
regex: '"method"\\s{0,5}:\\s{0,5}"sampling/createMessage"',
},
{
name: "MCP Protocol Version",
regex: '"protocolVersion"\\s{0,5}:\\s{0,5}"202[4-9]',
},
{
name: "MCP Notifications Initialized",
regex: '"method"\\s{0,5}:\\s{0,5}"notifications/initialized"',
},
{
name: "MCP Roots List",
regex: '"method"\\s{0,5}:\\s{0,5}"roots/list"',
},
];Описание шаблона:
\\s{0,5}: Допускает от нуля до пяти пробельных символов для обработки как минифицированного, так и отформатированного JSON"method": Двойные кавычки указываются буквально, так как этого требует JSON"tools/call": Точно соответствует имени метода MCP202[4-9]: Соответствует версиям протокола MCP с 2024 по 2029
Создать профиль DLP через API
Отправьте POST запрос на создание пользовательского профиля DLP, содержащего все шаблоны обнаружения:
const dlpProfile = {
name: "MCP-Shield: MCP JSON-RPC Detection",
description: "Detects MCP protocol JSON-RPC methods in HTTP request bodies.",
type: "custom",
entries: DLP_REGEX_PATTERNS.map((p) => ({
name: p.name,
enabled: true,
pattern: {
regex: p.regex,
validation: "luhn",
},
})),
};
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${accountId}/gateway/rules`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(dlpRule),
},
);
const data = await response.json();
if (data.success) {
console.log(`Created DLP profile: ${data.result.id}`);
}const dlpProfile = {
name: "MCP-Shield: MCP JSON-RPC Detection",
description: "Detects MCP protocol JSON-RPC methods in HTTP request bodies.",
type: "custom",
entries: DLP_REGEX_PATTERNS.map((p) => ({
name: p.name,
enabled: true,
pattern: {
regex: p.regex,
validation: "luhn",
},
})),
};
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${accountId}/gateway/rules`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(dlpRule),
},
);
const data = await response.json();
if (data.success) {
console.log(`Created DLP profile: ${data.result.id}`);
}Замените ${accountId} с ID вашего аккаунта Cloudflare и ${apiToken} с вашим токеном API.
Укажите профиль DLP в правиле Gateway
Когда профиль DLP создан, создайте HTTP-политику Gateway, которая блокирует запросы, соответствующие этому профилю:
const dlpRule = {
name: "MCP-Shield: Block MCP JSON-RPC via DLP",
description: "Blocks requests with MCP JSON-RPC patterns detected by DLP",
precedence: 85,
enabled: true,
action: "block",
filters: ["http"],
traffic:
'any(http.request.body.scan.dlp.profiles[*] == "MCP-Shield: MCP JSON-RPC Detection")',
};const dlpRule = {
name: "MCP-Shield: Block MCP JSON-RPC via DLP",
description: "Blocks requests with MCP JSON-RPC patterns detected by DLP",
precedence: 85,
enabled: true,
action: "block",
filters: ["http"],
traffic:
'any(http.request.body.scan.dlp.profiles[*] == "MCP-Shield: MCP JSON-RPC Detection")',
};Это правило срабатывает, когда профиль DLP обнаруживает совпадение с любым из регулярных выражений в теле запроса.
5. Классифицируйте трафик Portal и теневой трафик MCP
Cloudflare Порталы MCP-сервера предоставляют управляемую инфраструктуру для санкционированного доступа к MCP в вашей организации, включая:
- Управляемый доступ : Централизованная инфраструктура MCP, управляемая вашей ИТ-командой
- Следы аудита : Все запросы MCP регистрируются через Gateway с указанием пользователя
- Применение политики : Политики Zero Trust применяются автоматически, включая аутентификацию и DLP
- Утверждённые инструменты : Подобранный набор инструментов и ресурсов MCP, проверенных службой безопасности
При анализе журналов Gateway полезно различать два типа трафика MCP:
| Тип трафика | Характеристики | Уровень риска | Действие |
|---|---|---|---|
| Трафик портала MCP | httpHost соответствует домену вашего портала (например, mcp.yourcompany.com или mcp-portal.pages.dev) |
Авторизовано | Мониторинг |
| Shadow MCP traffic | httpHost не соответствует ни одному домену портала (например, mcp.datadog.com, api.stripe.com/mcp) |
Расследовать | Блокировать, перенаправлять или проверять |
Увеличьте время обработки запроса с Обработайте результаты запроса чтобы классифицировать трафик, сравнивая имена хостов со списком одобренных доменов портала:
const portalDomains = [
"mcp.yourcompany.com",
"mcp-portal.pages.dev",
"approved-mcp.workers.dev",
];
const results = groups.map((group) => {
const isPortalTraffic = portalDomains.some((domain) =>
group.dimensions.httpHost.includes(domain),
);
return {
domain: group.dimensions.httpHost,
requestCount: group.count,
users: group.dimensions.users || [],
trafficType: isPortalTraffic ? "portal" : "shadow",
riskLevel: isPortalTraffic ? "low" : "high",
};
});
const portalTraffic = results.filter((r) => r.trafficType === "portal");
const shadowTraffic = results.filter((r) => r.trafficType === "shadow");
console.log("Portal traffic:", portalTraffic);
console.log("Shadow MCP traffic:", shadowTraffic);const portalDomains = [
"mcp.yourcompany.com",
"mcp-portal.pages.dev",
"approved-mcp.workers.dev",
];
const results = groups.map((group) => {
const isPortalTraffic = portalDomains.some((domain) =>
group.dimensions.httpHost.includes(domain),
);
return {
domain: group.dimensions.httpHost,
requestCount: group.count,
users: group.dimensions.users || [],
trafficType: isPortalTraffic ? "portal" : "shadow",
riskLevel: isPortalTraffic ? "low" : "high",
};
});
const portalTraffic = results.filter((r) => r.trafficType === "portal");
const shadowTraffic = results.filter((r) => r.trafficType === "shadow");
console.log("Portal traffic:", portalTraffic);
console.log("Shadow MCP traffic:", shadowTraffic);Замените portalDomains array с реальными доменами ваших одобренных порталов серверов MCP.