INTEGRITY Dokumentace

Detekce provozu MCP v protokolech Gateway

Organizacím může chybět přehled o provozu Model Context Protocol (MCP), což zaměstnancům umožňuje připojovat se ke vzdáleným serverům MCP mimo dohled IT. Taková připojení s sebou nesou riziko úniku citlivých interních dat a přihlašovacích údajů, útoků typu tool injection nebo rizik spojených s dodavatelským řetězcem softwaru.

Jako správce IT chcete odhalit stínový provoz MCP, abyste zabránili neoprávněné exfiltraci dat, a zároveň nadále podporovali řízené případy použití. V tomto návodu použijete Cloudflare GraphQL Analytics API k prohledání protokolů Gateway HTTP a vyhledání vzorů provozu MCP, vytvoříte profily DLP, které detekují metody MCP JSON-RPC, a provoz rozdělíte tak, abyste odlišili autorizovaný provoz směřující na portály serverů MCP od provozu směřujícího na „stínové" vzdálené servery MCP.

Předpoklady

1. Zkontrolujte Gateway HTTP dataset

gatewayHttpRequestsAdaptiveGroups dataset v GraphQL Analytics API poskytuje agregovaná data protokolů HTTP Gateway. Pomocí tohoto datasetu můžete dotazovat vzory provozu související s MCP:

2. Vytvořte detekční dotaz MCP

Provoz MCP lze identifikovat pomocí tří signálů:

  1. Vzory domén: Hostname obsahující mcp (například mcp.datadog.com)
  2. cesty URL: Standardní endpointy MCP, například /mcp, /mcp/sse, a /sse
  3. Shody DLP: Metody JSON-RPC v tělech požadavků (popsáno v dalším kroku)

Následující dotaz GraphQL prohledává protokoly Gateway a hledá první dva signály:

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 || [];

Nahraďte <YOUR_ACCOUNT_ID> s ID vašeho účtu Cloudflare. Nahraďte <START_DATE> a <END_DATE> s časovými značkami ve formátu ISO-8601 pokrývajícími požadovaný časový rozsah (až 30 dnů).

3. Zpracujte výsledky dotazu

Každá skupina v odpovědi představuje agregovaný provoz pro konkrétní httpHost a action kombinace. Výsledky rozeberte a zjistěte, která připojení MCP nejsou blokována:

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`);

Klíčové poznatky z dat:

4. Vytvořte profily DLP pro detekci MCP JSON-RPC

Zásady Gateway HTTP mohou porovnávat domény a cesty URL, ale nemohou kontrolovat těla požadavků. Profily DLP skenují POST obsah těla podle vzorů, což je užitečné pro detekci shadow MCP, protože MCP používá JSON-RPC přes HTTP a má několik detekovatelných charakteristických znaků.

Každý požadavek MCP obsahuje "method" pole:

{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "tools/call",
	"params": { "name": "read_file", "arguments": { "path": "/etc/passwd" } }
}

Útočník by mohl spustit server MCP na nestandardní doméně (například internal-tools.company.com/api/assistant) aniž by se aktivovala pravidla založená na doméně nebo cestě. Můžete použít kontroly DLP POST tělo pro "method": "tools/call" a dalších vzorů specifických pro MCP pro zajištění robustnější ochrany provozu MCP.

Zkontrolujte omezení DLP

Před vytvářením detekčních vzorů si všimněte následujících omezení DLP:

Sestavit detekční vzory MCP

Indikátory MCP lze nalézt v polích metod protokolu JSON-RPC. Následující regulární výrazy pokrývají základní metody protokolu 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"',
	},
];

Vysvětlení vzoru:

Vytvořit profil DLP přes API

Odešlete POST požadavek k vytvoření vlastního profilu DLP obsahujícího všechny vzory detekce:

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}`);
}

Nahraďte ${accountId} s ID vašeho účtu Cloudflare a ${apiToken} s vaším API tokenem.

Odkažte na profil DLP v pravidle Gateway

Jakmile profil DLP existuje, vytvořte zásadu Gateway HTTP, která bude blokovat požadavky odpovídající tomuto profilu:

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")',
};

Toto pravidlo se spustí, když profil DLP odpovídá některému z regulárních výrazů (regex) v těle požadavku.

5. Klasifikujte provoz Portal a provoz shadow MCP

Cloudflare MCP Server Portals poskytují řízenou infrastrukturu pro schválený přístup MCP v rámci vaší organizace, včetně:

Při analýze protokolů Gateway je užitečné rozlišovat dva typy provozu MCP:

Typ provozu Vlastnosti Úroveň rizika Akce
Provoz MCP Portal httpHost odpovídá doméně vašeho portálu (například mcp.yourcompany.com nebo mcp-portal.pages.dev) Autorizováno Sledovat
Shadow MCP traffic httpHost neodpovídá žádné doméně portálu (například mcp.datadog.com, api.stripe.com/mcp) Prošetření Blokovat, přesměrovat nebo zkontrolovat

Prodlužte zpracování dotazu z Zpracujte výsledky dotazu klasifikovat provoz porovnáním hostname se seznamem schválených domén portálu:

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);

Nahraďte portalDomains array se skutečnými doménami vašich schválených MCP Server Portals.