{"id":"CVE-2026-46341","aliases":["EUVD-2026-44984","GHSA-jwp7-wg77-3w9v"],"status":"published","title":"Apify MCP server: Domain Allowlist Bypass in fetch-apify-docs via String Prefix Matching","description":"The Apify MCP server enables AI agents to extract data from websites using ready-made scrapers, crawlers, and automation tools available on the Apify Store. Prior to 0.9.21, the fetch-apify-docs tool in src/tools/common/fetch_apify_docs.ts validates allowlisted documentation domains with String.startsWith() rather than URL hostname comparison, allowing attacker-controlled URLs such as `https://docs.apify.com.evil.com/` and `https://docs.apify.com@evil.com/` to pass the ALLOWED_DOC_DOMAINS check and return arbitrary fetched content to the LLM. This issue is fixed in version 0.9.21.","severity":{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.00234,"percentile":0.14427,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-183","CWE-20"],"published":"2026-05-19T16:34:34Z","modified":"2026-10-28T02:30:44Z","fixes":[{"ecosystem":"npm","package":"@apify/actors-mcp-server","fixed_in":["0.9.21"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"@apify/actors-mcp-server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.9.21"}]}]},{"source":"osv","ecosystem":"npm","package":"@apify/actors-mcp-server","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.9.21"}]}]}],"products":[{"vendor":"apify","product":"apify-mcp-server"}],"references":[{"url":"https://github.com/apify/apify-mcp-server/security/advisories/GHSA-jwp7-wg77-3w9v","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/apify/apify-mcp-server/pull/781","tags":["x_refsource_MISC","fix"]},{"url":"https://github.com/apify/apify-mcp-server/commit/e39bdee530da1db0b3b3d3713558b33c9608e629","tags":["x_refsource_MISC","fix"]},{"url":"https://github.com/apify/apify-mcp-server/releases/tag/v0.9.21","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-jwp7-wg77-3w9v"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/46xxx/CVE-2026-46341.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-46341","tags":["advisory"]},{"url":"https://github.com/apify/apify-mcp-server","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-46341","title":"Apify MCP server: Domain Allowlist Bypass in fetch-apify-docs via String Prefix Matching","description":"The Apify MCP server enables AI agents to extract data from websites using ready-made scrapers, crawlers, and automation tools available on the Apify Store. Prior to 0.9.21, the fetch-apify-docs tool in src/tools/common/fetch_apify_docs.ts validates allowlisted documentation domains with String.startsWith() rather than URL hostname comparison, allowing attacker-controlled URLs such as `https://docs.apify.com.evil.com/` and `https://docs.apify.com@evil.com/` to pass the ALLOWED_DOC_DOMAINS check and return arbitrary fetched content to the LLM. This issue is fixed in version 0.9.21.","status":"published","published":"2026-07-16T16:54:01.843Z","modified":"2026-07-17T14:01:46.108Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-20","CWE-183"],"updated_at":"2026-11-02T13:47:06.353366Z"}],"euvd":[{"id":"EUVD-2026-44984","description":"The Apify MCP server enables AI agents to extract data from websites using ready-made scrapers, crawlers, and automation tools available on the Apify Store. Prior to 0.9.21, the fetch-apify-docs tool in src/tools/common/fetch_apify_docs.ts validates allowlisted documentation domains with String.startsWith() rather than URL hostname comparison, allowing attacker-controlled URLs such as `https://docs.apify.com.evil.com/` and `https://docs.apify.com@evil.com/` to pass the ALLOWED_DOC_DOMAINS check and return arbitrary fetched content to the LLM. This issue is fixed in version 0.9.21.","status":"published","published":"2026-07-16T16:54:01Z","modified":"2026-07-17T14:01:46Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T18:19:50.846094Z"}],"ghsa":[{"id":"GHSA-jwp7-wg77-3w9v","title":"Apify Model Context Protocol (MCP) server: Domain Allowlist Bypass in fetch-apify-docs via String Prefix Matching","description":"### Summary\nThe `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.\n\n### Details\n#### Vulnerable component\n\n`src/tools/common/fetch_apify_docs.ts`, line 51:\n\n```typescript\nconst isAllowedDomain = ALLOWED_DOC_DOMAINS.some((domain) =\u003e url.startsWith(domain));\n```\n\n`src/const.ts`, lines 167-170:\n\n```typescript\nexport const ALLOWED_DOC_DOMAINS = [\n    'https://docs.apify.com',\n    'https://crawlee.dev',\n] as const;\n```\n\n#### How the bypass works\n\n`String.startsWith('https://docs.apify.com')` matches any string beginning with that prefix, including:\n\n- `https://docs.apify.com.evil.com/payload` - attacker-controlled subdomain\n- `https://docs.apify.com@evil.com/payload` - userinfo component in URL (browser behavior varies, but `fetch()` in Node.js may follow this)\n- `https://docs.apify.com.evil.com:8080/path` - custom port on attacker domain\n\nAll of these pass the `startsWith` check because they begin with the exact string `https://docs.apify.com`.\n\n#### The fetched content is returned to the LLM\n\nAfter the allowlist check passes, the tool fetches the URL and returns the full page content as markdown (`fetch_apify_docs.ts:69-103`):\n\n```typescript\nconst response = await fetch(url);\n// ...\nconst html = await response.text();\nmarkdown = htmlToMarkdown(html);\n// ...\nreturn buildMCPResponse({ texts: [`Fetched content from ${url}:\\n\\n${markdown}`], ... });\n```\n\nThe 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.\n\nWhile 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.\n\n### PoC\n```json\n{\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"fetch-apify-docs\",\n    \"arguments\": {\n      \"url\": \"https://docs.apify.com.evil.com/prompt-injection-payload\"\n    }\n  }\n}\n```\n\nThe URL passes the `startsWith('https://docs.apify.com')` check, fetches the attacker's page, and returns its content to the LLM.\n### Impact\n- **Prompt injection via fetched content**: Attacker hosts a page at `docs.apify.com.evil.com` containing LLM instructions. When the tool fetches and returns this content, the LLM may follow the injected instructions.\n- **Security boundary violation**: The allowlist was explicitly designed to restrict fetching to trusted documentation domains. The bypass defeats this intent.\n- **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.\n- **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 subsequent `call-actor` invocations, redirecting billable operations to a victim's account or accessing their private Actors","status":"published","published":"2026-05-19T16:34:34Z","modified":"2026-05-19T16:34:39Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-20","CWE-183"],"updated_at":"2026-11-02T14:40:12.430932Z"}],"nvd":[{"id":"CVE-2026-46341","description":"The Apify MCP server enables AI agents to extract data from websites using ready-made scrapers, crawlers, and automation tools available on the Apify Store. Prior to 0.9.21, the fetch-apify-docs tool in src/tools/common/fetch_apify_docs.ts validates allowlisted documentation domains with String.startsWith() rather than URL hostname comparison, allowing attacker-controlled URLs such as `https://docs.apify.com.evil.com/` and `https://docs.apify.com@evil.com/` to pass the ALLOWED_DOC_DOMAINS check and return arbitrary fetched content to the LLM. This issue is fixed in version 0.9.21.","status":"published","published":"2026-07-16T18:16:43.913Z","modified":"2026-10-24T20:10:00.137Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-20","CWE-183"],"updated_at":"2026-11-02T14:12:30.250437Z"}],"osv":[{"id":"CVE-2026-46341","title":"Apify MCP server: Domain Allowlist Bypass in fetch-apify-docs via String Prefix Matching","description":"The Apify MCP server enables AI agents to extract data from websites using ready-made scrapers, crawlers, and automation tools available on the Apify Store. Prior to 0.9.21, the fetch-apify-docs tool in src/tools/common/fetch_apify_docs.ts validates allowlisted documentation domains with String.startsWith() rather than URL hostname comparison, allowing attacker-controlled URLs such as `https://docs.apify.com.evil.com/` and `https://docs.apify.com@evil.com/` to pass the ALLOWED_DOC_DOMAINS check and return arbitrary fetched content to the LLM. This issue is fixed in version 0.9.21.","status":"published","published":"2026-07-16T16:54:01.843Z","modified":"2026-10-28T02:30:44.161458951Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-183","CWE-20"],"updated_at":"2026-11-02T15:45:05.870292Z"},{"id":"GHSA-jwp7-wg77-3w9v","title":"Apify Model Context Protocol (MCP) server: Domain Allowlist Bypass in fetch-apify-docs via String Prefix Matching","description":"### Summary\nThe `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.\n\n### Details\n#### Vulnerable component\n\n`src/tools/common/fetch_apify_docs.ts`, line 51:\n\n```typescript\nconst isAllowedDomain = ALLOWED_DOC_DOMAINS.some((domain) =\u003e url.startsWith(domain));\n```\n\n`src/const.ts`, lines 167-170:\n\n```typescript\nexport const ALLOWED_DOC_DOMAINS = [\n    'https://docs.apify.com',\n    'https://crawlee.dev',\n] as const;\n```\n\n#### How the bypass works\n\n`String.startsWith('https://docs.apify.com')` matches any string beginning with that prefix, including:\n\n- `https://docs.apify.com.evil.com/payload` - attacker-controlled subdomain\n- `https://docs.apify.com@evil.com/payload` - userinfo component in URL (browser behavior varies, but `fetch()` in Node.js may follow this)\n- `https://docs.apify.com.evil.com:8080/path` - custom port on attacker domain\n\nAll of these pass the `startsWith` check because they begin with the exact string `https://docs.apify.com`.\n\n#### The fetched content is returned to the LLM\n\nAfter the allowlist check passes, the tool fetches the URL and returns the full page content as markdown (`fetch_apify_docs.ts:69-103`):\n\n```typescript\nconst response = await fetch(url);\n// ...\nconst html = await response.text();\nmarkdown = htmlToMarkdown(html);\n// ...\nreturn buildMCPResponse({ texts: [`Fetched content from ${url}:\\n\\n${markdown}`], ... });\n```\n\nThe 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.\n\nWhile 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.\n\n### PoC\n```json\n{\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"fetch-apify-docs\",\n    \"arguments\": {\n      \"url\": \"https://docs.apify.com.evil.com/prompt-injection-payload\"\n    }\n  }\n}\n```\n\nThe URL passes the `startsWith('https://docs.apify.com')` check, fetches the attacker's page, and returns its content to the LLM.\n### Impact\n- **Prompt injection via fetched content**: Attacker hosts a page at `docs.apify.com.evil.com` containing LLM instructions. When the tool fetches and returns this content, the LLM may follow the injected instructions.\n- **Security boundary violation**: The allowlist was explicitly designed to restrict fetching to trusted documentation domains. The bypass defeats this intent.\n- **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.\n- **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 subsequent `call-actor` invocations, redirecting billable operations to a victim's account or accessing their private Actors","status":"published","published":"2026-05-19T16:34:34Z","modified":"2026-09-10T03:51:06.238242148Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-183","CWE-20"],"updated_at":"2026-11-02T15:46:51.10002Z"}]}}
