Apify MCP Server CVE-2026-46341
MEDIUMSeverity by source
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Lifecycle Timeline
2DescriptionGitHub Advisory
Summary
The fetch-apify-docs tool validates URLs against a domain allowlist using String.startsWith() instead of proper URL hostname comparison. This allows bypass via attacker-controlled subdomains (e.g., https://docs.apify.com.evil.com/), enabling the tool to fetch and return arbitrary web content to the LLM.
Details
Vulnerable component
src/tools/common/fetch_apify_docs.ts, line 51:
const isAllowedDomain = ALLOWED_DOC_DOMAINS.some((domain) => url.startsWith(domain));src/const.ts, lines 167-170:
export const ALLOWED_DOC_DOMAINS = [
'https://docs.apify.com',
'https://crawlee.dev',
] as const;How the bypass works
String.startsWith('https://docs.apify.com') matches any string beginning with that prefix, including:
https://docs.apify.com.evil.com/payload- attacker-controlled subdomainhttps://docs.apify.com@evil.com/payload- userinfo component in URL (browser behavior varies, butfetch()in Node.js may follow this)https://docs.apify.com.evil.com:8080/path- custom port on attacker domain
All of these pass the startsWith check because they begin with the exact string https://docs.apify.com.
The fetched content is returned to the LLM
After the allowlist check passes, the tool fetches the URL and returns the full page content as markdown (fetch_apify_docs.ts:69-103):
const response = await fetch(url);
// ...
const html = await response.text();
markdown = htmlToMarkdown(html);
// ...
return buildMCPResponse({ texts: [`Fetched content from ${url}:\n\n${markdown}`], ... });The HTML is converted to markdown and returned verbatim to the LLM. This creates a prompt injection vector - the attacker's page can contain instructions that the LLM may follow.
While tools like get-html-skeleton have no domain allowlist at all - it accepts any URL. The fetch-apify-docs tool was clearly intended to be more restricted (documentation-only), but the startsWith check defeats that intent.
PoC
{
"method": "tools/call",
"params": {
"name": "fetch-apify-docs",
"arguments": {
"url": "https://docs.apify.com.evil.com/prompt-injection-payload"
}
}
}The URL passes the startsWith('https://docs.apify.com') check, fetches the attacker's page, and returns its content to the LLM.
Impact
- Prompt injection via fetched content: Attacker hosts a page at
docs.apify.com.evil.comcontaining LLM instructions. When the tool fetches and returns this content, the LLM may follow the injected instructions. - Security boundary violation: The allowlist was explicitly designed to restrict fetching to trusted documentation domains. The bypass defeats this intent.
- SSRF (limited): The tool can fetch from attacker-controlled servers, though the primary risk is the content returned to the LLM rather than network access.
- Account compromise via _meta.apifyToken: Injected prompt instructions can direct the LLM to include a specific
_meta.apifyToken(the server's per-request token feature) in subsequentcall-actorinvocations, redirecting billable operations to a victim's account or accessing their private Actors
AnalysisAI
Domain allowlist bypass in Apify MCP Server's fetch-apify-docs tool (npm/@apify/actors-mcp-server < 0.9.21) enables prompt injection against LLM agents by allowing attacker-controlled URLs to pass a flawed string prefix check. The tool validates requested URLs with String.startsWith() rather than parsing the URL hostname, so crafted URLs like https://docs.apify.com.evil.com/ satisfy the check while resolving to an attacker-controlled server. Publicly available exploit code (PoC) exists per the GitHub advisory GHSA-jwp7-wg77-3w9v; no CISA KEV listing at time of analysis, though the prompt injection vector can escalate to Apify account compromise via injected token redirection.
Technical ContextAI
The affected package is @apify/actors-mcp-server (npm), a Node.js-based Model Context Protocol server for the Apify automation platform. The flaw resides in src/tools/common/fetch_apify_docs.ts at line 51, where the allowlist check reads: ALLOWED_DOC_DOMAINS.some((domain) => url.startsWith(domain)), validating against the string literals 'https://docs.apify.com' and 'https://crawlee.dev' defined in src/const.ts. CWE-20 (Improper Input Validation) is the root cause: string prefix matching against raw URL strings does not account for DNS-level disambiguation between hostnames. A URL like https://docs.apify.com.evil.com starts with the required prefix lexically but resolves to a completely different host. Node.js's fetch() will follow the actual DNS resolution, fetching from the attacker's server, while the guard treats the URL as trusted. The fetched HTML is converted to markdown and returned verbatim to the LLM (fetch_apify_docs.ts:69-103), making this a direct prompt injection channel. Proper mitigation requires parsing the URL hostname component via the URL constructor and comparing it against the allowed set.
RemediationAI
Upgrade @apify/actors-mcp-server to version 0.9.21 or later, which is the vendor-released patch confirmed in the npm advisory. The fix replaces the String.startsWith() check with proper URL hostname parsing using the URL constructor, ensuring that only requests to the exact allowed hostnames (docs.apify.com, crawlee.dev) are permitted. Prior to patching, a specific compensating control is to configure network egress rules on the host running the MCP server to block outbound HTTP/HTTPS requests to any domain other than docs.apify.com and crawlee.dev - this prevents the server from actually reaching attacker infrastructure even if the bypass occurs, though it does not protect against cached or pre-fetched malicious content already in the pipeline. A second compensating control is to disable the fetch-apify-docs tool entirely in MCP server configuration if documentation fetching is not required for your use case; this eliminates the attack surface with no functional trade-off for deployments that do not use that tool. Full advisory: https://github.com/apify/apify-mcp-server/security/advisories/GHSA-jwp7-wg77-3w9v
FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote
Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t
Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete
Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc
An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner
Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi
Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin
The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic
Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read
Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio
Eval injection vulnerability in index.js in the syntax-error package before 1.1.1 for Node.js 0.10.x, as used in IBM Rat
The HTTP server in Node.js 0.10.x before 0.10.21 and 0.8.x before 0.8.26 allows remote attackers to cause a denial of se
Same weakness CWE-20 – Improper Input Validation
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-jwp7-wg77-3w9v