{"id":"GHSA-83x6-42hr-jc76","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        // \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.","aliases":["CVE-2026-73845"],"modified":"2026-09-02T15:00:10.829058570Z","published":"2026-09-02T14:52:21Z","database_specific":{"github_reviewed_at":"2026-09-02T14:52:21Z","nvd_published_at":"2026-08-14T17:20:36Z","cwe_ids":["CWE-20","CWE-625","CWE-918"],"severity":"MODERATE","github_reviewed":true},"references":[{"type":"WEB","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":"WEB","url":"https://github.com/ondata/ckan-mcp-server/commit/8e1522f9bbfa1f3b21550f17887f60f133e24151"},{"type":"PACKAGE","url":"https://github.com/ondata/ckan-mcp-server"},{"type":"WEB","url":"https://github.com/ondata/ckan-mcp-server/releases/tag/v0.4.112"}],"affected":[{"package":{"name":"@aborruso/ckan-mcp-server","ecosystem":"npm","purl":"pkg:npm/%40aborruso/ckan-mcp-server"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.4.112"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-83x6-42hr-jc76/GHSA-83x6-42hr-jc76.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"}]}