{"id":"GHSA-jwp7-wg77-3w9v","revision":"1e30a65187624e11952a4b193c9168de","records":[{"revision":"1e30a65187624e11952a4b193c9168de","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-jwp7-wg77-3w9v","aliases":["CVE-2026-46341"],"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) => 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","state":0,"publishedAt":"2026-05-19T16:34:34+00:00","modifiedAt":"2026-09-10T03:51:06.2382421+00:00","affected":[{"ecosystem":"npm","name":"@apify/actors-mcp-server","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"0.9.21"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/apify/apify-mcp-server/security/advisories/GHSA-jwp7-wg77-3w9v","https://github.com/apify/apify-mcp-server"],"sourceUrl":"https://github.com/advisories/GHSA-jwp7-wg77-3w9v","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-jwp7-wg77-3w9v","identifiers":["GHSA-jwp7-wg77-3w9v","CVE-2026-46341"]},"retained":false,"historicalAffected":[]}],"coverage":[]}