{"id":"CVE-2026-73845","aliases":["GHSA-83x6-42hr-jc76"],"url":"https://o3.security/vulnerability/CVE-2026-73845","summary":"CKAN MCP Server: MQA server allowlist bypass via unanchored regex (`isValidMqaServer`)","details":"## 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        // <-- no end anchor / host boundary\n];\nexport function isValidMqaServer(serverUrl: string): boolean {\n  return ALLOWED_SERVER_PATTERNS.some(pattern => 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          <- legit\ntrue      false           https://dati.gov.it.attacker.com/x    <- BYPASS\ntrue      false           http://dati.gov.it.evil.example/api   <- BYPASS\ntrue      false           https://dati.gov.it@attacker.com/x    <- 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.","published":"2026-08-14T16:45:06.714Z","modified":"2026-09-20T11:30:32.579421222Z","cvss":{"score":5.3,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"},"epss":{"score":0.00224,"percentile":0.13153,"asOf":"2026-09-16"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"@aborruso/ckan-mcp-server","fixedVersion":"0.4.112"}],"fix":{"url":"https://github.com/ondata/ckan-mcp-server/commit/8e1522f9bbfa1f3b21550f17887f60f133e24151","label":"ondata/ckan-mcp-server@8e1522f"},"references":[{"type":"WEB","url":"https://github.com/ondata/ckan-mcp-server/releases/tag/v0.4.112"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/73xxx/CVE-2026-73845.json"},{"type":"ADVISORY","url":"https://github.com/ondata/ckan-mcp-server/security/advisories/GHSA-83x6-42hr-jc76"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-73845"},{"type":"FIX","url":"https://github.com/ondata/ckan-mcp-server/commit/8e1522f9bbfa1f3b21550f17887f60f133e24151"},{"type":"PACKAGE","url":"https://github.com/ondata/ckan-mcp-server"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-09-20T11:30:32.579421222Z"}}