GHSA-83x6-42hr-jc76: Input Validation
Summary
The ckangetmqaquality and ckangetmqaqualitydetails tools restrict their serverurl 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.
Affected code
js // src/tools/quality.ts const ALLOWEDSERVERPATTERNS = [ /^https?:\/\/(www\.)?dati\.gov\.it/i // <-- no end anchor / host boundary ]; export function isValidMqaServer(serverUrl: string): boolean { return ALLOWEDSERVERPATTERNS.some(pattern => pattern.test(serverUrl)); }
All of the following return true:
| URL | Real host | |-----|-----------| | https://dati.gov.it.attacker.com/x | dati.gov.it.attacker.com (attacker) | | http://dati.gov.it.evil.example/api | dati.gov.it.evil.example (attacker) | | https://dati.gov.it@attacker.com/x | attacker.com (userinfo trick) |
After passing this check, serverurl flows into getMqaQuality/getMqaQualityDetails, which call makeCkanRequest(serverUrl, "packageshow", { id }). The server therefore issues a request to the attacker-controlled host and returns its (parsed) response to the caller.
Impact
- The intended "dati.gov.it only" trust boundary for the MQA tools is defeated; they can be driven against arbitrary external hosts. - The attacker host receives the request (including the datasetid) 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. - 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.
The @-userinfo variant is the most severe form because validation passes on a string whose actual host is fully attacker-chosen.
Proof of concept
poc/mqa-allowlist-poc.mjs runs the verbatim regex over benign and malicious URLs:
accepted expectedlegit url true true https://dati.gov.it/opendata <- legit true false https://dati.gov.it.attacker.com/x <- BYPASS true false http://dati.gov.it.evil.example/api <- BYPASS true false https://dati.gov.it@attacker.com/x <- BYPASS
Remediation
Validate the parsed host, not the raw string. For example:
js function isValidMqaServer(serverUrl) { let u; try { u = new URL(serverUrl); } catch { return false; } if (u.protocol !== "https:") return false; const h = u.hostname.toLowerCase(); return h === "dati.gov.it" || h === "www.dati.gov.it"; // or: h === "dati.gov.it" || h.endsWith(".dati.gov.it") }
Anchoring 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.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/@aborruso/ckan-mcp-serverto a version that resolves this vulnerability.Fixed in 0.4.112 - Configuration
Replace the current regex-only check (e.g., /^https?:\/\/(www\.)?dati\.gov\.it/i) with URL parsing and exact host comparison. Validate the parsed hostname (u.hostname.toLowerCase()) against an allowlist of only 'dati.gov.it' and 'www.dati.gov.it', so bypasses like 'https://dati.gov.it.evil.example/api', 'https://dati.gov.it.attacker.com/x', and 'https://dati.gov.it@attacker.com/x' are rejected.
src/tools/quality.ts (isValidMqaServer / server_url validation for CKAN MQA tools: ckan_get_mqa_quality, ckan_get_mqa_quality_details) Allowed server URL/hostname validation = Parse URL and validate exact hostname (e.g., u.hostname == 'dati.gov.it' OR 'www.dati.gov.it'), not regex match on raw serverUrl
Event History
Frequently Asked Questions
What access does an attacker need to exploit this issue?
An attacker needs to be able to supply the server_url argument to either ckan_get_mqa_quality or ckan_get_mqa_quality_details. The supplied value can use a host beginning with dati.gov.it or use dati.gov.it as URL userinfo before an @ to pass validation while directing the request elsewhere.
Which requests can be redirected to an attacker-controlled server?
Requests made after validation by getMqaQuality or getMqaQualityDetails can be redirected, because they pass server_url to makeCkanRequest for the package_show operation. Examples include https://dati.gov.it.attacker.com/x and https://dati.gov.it@attacker.com/x.