{"id":"CVE-2026-73845","aliases":["EUVD-2026-58725","GHSA-83x6-42hr-jc76"],"status":"published","title":"CKAN MCP Server: MQA server allowlist bypass via unanchored regex (`isValidMqaServer`)","description":"CKAN MCP Server is a tool for querying CKAN open data portals. Prior to 0.4.112, the ckan_get_mqa_quality and ckan_get_mqa_quality_details tools in src/tools/quality.ts use isValidMqaServer to validate the server_url parameter with a prefix-only regular expression for dati.gov.it, allowing suffix-host and URL-userinfo values to target an attacker-controlled host and return a spoofed response. This issue is fixed in version 0.4.112.","severity":{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00384,"percentile":0.32925,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-20","CWE-625","CWE-918"],"published":"2026-08-14T16:45:06Z","modified":"2026-10-28T02:30:39Z","fixes":[{"ecosystem":"npm","package":"@aborruso/ckan-mcp-server","fixed_in":["0.4.112"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"@aborruso/ckan-mcp-server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.4.112"}]}]},{"source":"osv","ecosystem":"npm","package":"@aborruso/ckan-mcp-server","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.4.112"}]}]}],"products":[{"vendor":"ondata","product":"ckan-mcp-server"}],"references":[{"url":"https://github.com/ondata/ckan-mcp-server/security/advisories/GHSA-83x6-42hr-jc76","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/ondata/ckan-mcp-server/commit/8e1522f9bbfa1f3b21550f17887f60f133e24151","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/ondata/ckan-mcp-server/releases/tag/v0.4.112","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-73845","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-83x6-42hr-jc76"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/73xxx/CVE-2026-73845.json","tags":["advisory"]},{"url":"https://github.com/ondata/ckan-mcp-server","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-73845","title":"CKAN MCP Server: MQA server allowlist bypass via unanchored regex (`isValidMqaServer`)","description":"CKAN MCP Server is a tool for querying CKAN open data portals. Prior to 0.4.112, the ckan_get_mqa_quality and ckan_get_mqa_quality_details tools in src/tools/quality.ts use isValidMqaServer to validate the server_url parameter with a prefix-only regular expression for dati.gov.it, allowing suffix-host and URL-userinfo values to target an attacker-controlled host and return a spoofed response. This issue is fixed in version 0.4.112.","status":"published","published":"2026-08-14T16:45:06.714Z","modified":"2026-08-14T18:06:04.17Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-20","CWE-625","CWE-918"],"updated_at":"2026-11-02T13:43:33.766176Z"}],"euvd":[{"id":"EUVD-2026-58725","description":"CKAN MCP Server is a tool for querying CKAN open data portals. Prior to 0.4.112, the ckan_get_mqa_quality and ckan_get_mqa_quality_details tools in src/tools/quality.ts use isValidMqaServer to validate the server_url parameter with a prefix-only regular expression for dati.gov.it, allowing suffix-host and URL-userinfo values to target an attacker-controlled host and return a spoofed response. This issue is fixed in version 0.4.112.","status":"published","published":"2026-08-14T16:45:06Z","modified":"2026-08-14T18:06:04Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium"}],"updated_at":"2026-11-02T18:01:53.90055Z"}],"ghsa":[{"id":"GHSA-83x6-42hr-jc76","title":"CKAN MCP Server: MQA server allowlist bypass via unanchored regex (`isValidMqaServer`)","description":"## Summary\n\nThe `ckan_get_mqa_quality` and `ckan_get_mqa_quality_details` tools restrict their `server_url` argument to `dati.gov.it` via a regular expression. The regex is anchored only at the start and places no boundary after the host, so any URL whose host merely **begins with** `dati.gov.it` — or that uses `dati.gov.it` as URL *userinfo* before an `@` — passes validation while actually targeting an attacker-controlled host.\n\n## Affected code\n\n```js\n// src/tools/quality.ts\nconst ALLOWED_SERVER_PATTERNS = [\n  /^https?:\\/\\/(www\\.)?dati\\.gov\\.it/i        // \u003c-- no end anchor / host boundary\n];\nexport function isValidMqaServer(serverUrl: string): boolean {\n  return ALLOWED_SERVER_PATTERNS.some(pattern =\u003e pattern.test(serverUrl));\n}\n```\n\nAll of the following return `true`:\n\n| URL | Real host |\n|-----|-----------|\n| `https://dati.gov.it.attacker.com/x` | `dati.gov.it.attacker.com` (attacker) |\n| `http://dati.gov.it.evil.example/api` | `dati.gov.it.evil.example` (attacker) |\n| `https://dati.gov.it@attacker.com/x` | `attacker.com` (userinfo trick) |\n\nAfter passing this check, `server_url` flows into `getMqaQuality`/`getMqaQualityDetails`, which call `makeCkanRequest(serverUrl, \"package_show\", { id })`. The server therefore issues a request to the attacker-controlled host and returns its (parsed) response to the caller.\n\n## Impact\n\n- The intended \"dati.gov.it only\" trust boundary for the MQA tools is defeated; they can be driven against arbitrary external hosts.\n- The attacker host receives the request (including the `dataset_id`) and controls the response body that is surfaced back to the model/user — enabling response spoofing and, in an agentic setting, indirect prompt-injection content delivered under the guise of a trusted-portal tool.\n- Contributes to SSRF surface: while `makeCkanRequest` blocks private/internal IPs, this bypass removes the domain restriction that the code intends to enforce for these tools.\n\nThe `@`-userinfo variant is the most severe form because validation passes on a string whose *actual* host is fully attacker-chosen.\n\n## Proof of concept\n\n`poc/mqa-allowlist-poc.mjs` runs the verbatim regex over benign and malicious URLs:\n\n```\naccepted  expected_legit  url\ntrue      true            https://dati.gov.it/opendata          \u003c- legit\ntrue      false           https://dati.gov.it.attacker.com/x    \u003c- BYPASS\ntrue      false           http://dati.gov.it.evil.example/api   \u003c- BYPASS\ntrue      false           https://dati.gov.it@attacker.com/x    \u003c- BYPASS\n```\n\n## Remediation\n\nValidate the parsed host, not the raw string. For example:\n\n```js\nfunction isValidMqaServer(serverUrl) {\n  let u; try { u = new URL(serverUrl); } catch { return false; }\n  if (u.protocol !== \"https:\") return false;\n  const h = u.hostname.toLowerCase();\n  return h === \"dati.gov.it\" || h === \"www.dati.gov.it\";\n  // or: h === \"dati.gov.it\" || h.endsWith(\".dati.gov.it\")\n}\n```\n\nAnchoring the regex end-to-end (`/^https:\\/\\/(www\\.)?dati\\.gov\\.it(\\/|$)/i`) also closes the suffix trick, but URL-parsing + exact host comparison is the robust fix and also neutralizes the `@`-userinfo variant.","status":"published","published":"2026-09-02T14:52:21Z","modified":"2026-09-02T14:52:22Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium"}],"cwes":["CWE-20","CWE-625","CWE-918"],"updated_at":"2026-11-02T14:41:10.121131Z"}],"nvd":[{"id":"CVE-2026-73845","description":"CKAN MCP Server is a tool for querying CKAN open data portals. Prior to 0.4.112, the ckan_get_mqa_quality and ckan_get_mqa_quality_details tools in src/tools/quality.ts use isValidMqaServer to validate the server_url parameter with a prefix-only regular expression for dati.gov.it, allowing suffix-host and URL-userinfo values to target an attacker-controlled host and return a spoofed response. This issue is fixed in version 0.4.112.","status":"published","published":"2026-08-14T17:20:36.82Z","modified":"2026-09-18T20:09:01.757Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-20","CWE-625","CWE-918"],"updated_at":"2026-11-02T14:12:37.557758Z"}],"osv":[{"id":"CVE-2026-73845","title":"CKAN MCP Server: MQA server allowlist bypass via unanchored regex (`isValidMqaServer`)","description":"CKAN MCP Server is a tool for querying CKAN open data portals. Prior to 0.4.112, the ckan_get_mqa_quality and ckan_get_mqa_quality_details tools in src/tools/quality.ts use isValidMqaServer to validate the server_url parameter with a prefix-only regular expression for dati.gov.it, allowing suffix-host and URL-userinfo values to target an attacker-controlled host and return a spoofed response. This issue is fixed in version 0.4.112.","status":"published","published":"2026-08-14T16:45:06.714Z","modified":"2026-10-28T02:30:39.987339495Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium"}],"cwes":["CWE-20","CWE-625","CWE-918"],"updated_at":"2026-11-02T15:45:16.365946Z"},{"id":"GHSA-83x6-42hr-jc76","title":"CKAN MCP Server: MQA server allowlist bypass via unanchored regex (`isValidMqaServer`)","description":"## Summary\n\nThe `ckan_get_mqa_quality` and `ckan_get_mqa_quality_details` tools restrict their `server_url` argument to `dati.gov.it` via a regular expression. The regex is anchored only at the start and places no boundary after the host, so any URL whose host merely **begins with** `dati.gov.it` — or that uses `dati.gov.it` as URL *userinfo* before an `@` — passes validation while actually targeting an attacker-controlled host.\n\n## Affected code\n\n```js\n// src/tools/quality.ts\nconst ALLOWED_SERVER_PATTERNS = [\n  /^https?:\\/\\/(www\\.)?dati\\.gov\\.it/i        // \u003c-- no end anchor / host boundary\n];\nexport function isValidMqaServer(serverUrl: string): boolean {\n  return ALLOWED_SERVER_PATTERNS.some(pattern =\u003e pattern.test(serverUrl));\n}\n```\n\nAll of the following return `true`:\n\n| URL | Real host |\n|-----|-----------|\n| `https://dati.gov.it.attacker.com/x` | `dati.gov.it.attacker.com` (attacker) |\n| `http://dati.gov.it.evil.example/api` | `dati.gov.it.evil.example` (attacker) |\n| `https://dati.gov.it@attacker.com/x` | `attacker.com` (userinfo trick) |\n\nAfter passing this check, `server_url` flows into `getMqaQuality`/`getMqaQualityDetails`, which call `makeCkanRequest(serverUrl, \"package_show\", { id })`. The server therefore issues a request to the attacker-controlled host and returns its (parsed) response to the caller.\n\n## Impact\n\n- The intended \"dati.gov.it only\" trust boundary for the MQA tools is defeated; they can be driven against arbitrary external hosts.\n- The attacker host receives the request (including the `dataset_id`) and controls the response body that is surfaced back to the model/user — enabling response spoofing and, in an agentic setting, indirect prompt-injection content delivered under the guise of a trusted-portal tool.\n- Contributes to SSRF surface: while `makeCkanRequest` blocks private/internal IPs, this bypass removes the domain restriction that the code intends to enforce for these tools.\n\nThe `@`-userinfo variant is the most severe form because validation passes on a string whose *actual* host is fully attacker-chosen.\n\n## Proof of concept\n\n`poc/mqa-allowlist-poc.mjs` runs the verbatim regex over benign and malicious URLs:\n\n```\naccepted  expected_legit  url\ntrue      true            https://dati.gov.it/opendata          \u003c- legit\ntrue      false           https://dati.gov.it.attacker.com/x    \u003c- BYPASS\ntrue      false           http://dati.gov.it.evil.example/api   \u003c- BYPASS\ntrue      false           https://dati.gov.it@attacker.com/x    \u003c- BYPASS\n```\n\n## Remediation\n\nValidate the parsed host, not the raw string. For example:\n\n```js\nfunction isValidMqaServer(serverUrl) {\n  let u; try { u = new URL(serverUrl); } catch { return false; }\n  if (u.protocol !== \"https:\") return false;\n  const h = u.hostname.toLowerCase();\n  return h === \"dati.gov.it\" || h === \"www.dati.gov.it\";\n  // or: h === \"dati.gov.it\" || h.endsWith(\".dati.gov.it\")\n}\n```\n\nAnchoring the regex end-to-end (`/^https:\\/\\/(www\\.)?dati\\.gov\\.it(\\/|$)/i`) also closes the suffix trick, but URL-parsing + exact host comparison is the robust fix and also neutralizes the `@`-userinfo variant.","status":"published","published":"2026-09-02T14:52:21Z","modified":"2026-09-02T15:00:10.82905857Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium"}],"cwes":["CWE-20","CWE-625","CWE-918"],"updated_at":"2026-11-02T15:46:41.732368Z"}]}}
