@aborruso/ckan-mcp-server 0.4.118 → 0.4.120
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LOG.md +91 -0
- package/dist/index.js +50 -7
- package/package.json +1 -1
package/LOG.md
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
# LOG
|
|
2
2
|
|
|
3
|
+
## 2026-09-05
|
|
4
|
+
|
|
5
|
+
### v0.4.120 - SSRF hardening release + dependency audit
|
|
6
|
+
|
|
7
|
+
Releases PR #531 (env-proxy refusal, multicast/reserved/site-local ranges, see 2026-09-04 entry). Also:
|
|
8
|
+
|
|
9
|
+
- `npm audit fix`: axios 1.13.2 -> 1.20.0 (29 open advisories, including NO_PROXY/SSRF bypasses), follow-redirects -> 1.16.0, wrangler -> 4.129.0, vitest -> 4.1.11. Remaining findings (body-parser, express, qs) need express 5, a major: left for a dedicated change.
|
|
10
|
+
- `src/server.ts` and `src/worker.ts` were still at 0.4.118: the 0.4.119 bump skipped them because CLAUDE.md's release step lists only the three JSON files. Fixed here.
|
|
11
|
+
- Verified: build, worker build, 497 tests, real HTTP e2e (search on dati.gov.it, datastore on Messina, `169.254.169.254` still refused).
|
|
12
|
+
|
|
13
|
+
## 2026-09-04
|
|
14
|
+
|
|
15
|
+
### SSRF guard aligned with datagouv-mcp v1.0.0
|
|
16
|
+
|
|
17
|
+
Compared our `isBlockedIp` and request chain against the SSRF hardening shipped in [datagouv-mcp v1.0.0](https://github.com/datagouv/datagouv-mcp/releases/tag/v1.0.0) (PR #126, external report). Connect-time pinning, redirect re-checks and IPv4-mapped/6to4/NAT64 unwrapping were already in place; their specific vector (fetching producer-supplied URLs from catalog metadata) does not apply here. Two gaps closed:
|
|
18
|
+
|
|
19
|
+
- `proxy: false` on the axios path: with `HTTP_PROXY`/`HTTPS_PROXY` set, axios connected to the proxy and the SSRF-safe lookup validated only the proxy's IP, not the target's.
|
|
20
|
+
- `isBlockedIp` now also rejects 224.0.0.0/4 (multicast), 240.0.0.0/4 (reserved), ff00::/8 (IPv6 multicast) and fec0::/10 (site-local). Running their 37 test cases against our function surfaced exactly these.
|
|
21
|
+
|
|
22
|
+
## 2026-08-31
|
|
23
|
+
|
|
24
|
+
### Support #4682889 answered: the Advisory Database queue reaches back to June 2026
|
|
25
|
+
|
|
26
|
+
Second reply from Tempy (25 August, seen on the portal - the notification was in the inbox but the question was already answered before I looked): the advisory curation team confirmed that publication of repository advisories to the Global Advisory Database **is queued**, reviewed manually, and that "the advisory publication queue stretches back to June 2026", with work under way to reduce it.
|
|
27
|
+
|
|
28
|
+
So nothing is wrong with the records. The pattern we measured fits the explanation exactly: the only two advisories in the global database are the oldest ones (March and May), and CVE-2026-61612 (June) sits right at the edge of the backlog. Nothing on our side accelerates this, and there is no action left - the ticket auto-closed after seven days and is best left closed.
|
|
29
|
+
|
|
30
|
+
Worth keeping: **Dependabot alerts are driven from the advisory in the database, not from the CVE assignment** (Support, 24 August). The thirteen CVEs this repository now holds are a public reference, not downstream protection - that arrives only when the advisory clears the queue.
|
|
31
|
+
|
|
32
|
+
## 2026-08-24
|
|
33
|
+
|
|
34
|
+
### Support #4682889: GitHub answers about CVEs, the question was about advisories
|
|
35
|
+
|
|
36
|
+
First reply from GitHub Support (Tempy). It attributes the delay to an industry-wide surge in CVE publication and records being processed in order of arrival - which answers a question the ticket did not ask. The useful part is a statement that undercuts that very explanation:
|
|
37
|
+
|
|
38
|
+
> The associated advisory in the GitHub Advisory Database is published independently of the CVE record, and Dependabot alerts are driven from the advisory - not the CVE assignment.
|
|
39
|
+
|
|
40
|
+
If alerts are driven from the advisory, the CVE backlog cannot explain twelve published advisories being absent from the database. Replied restating the scope, with a new data point: `GHSA-x32r`, published 2026-08-20, is at its third working day and still not in the database either - so this is not confined to the older records.
|
|
41
|
+
|
|
42
|
+
State unchanged: 14 published advisories, 2 in the global database, 13 carrying a CVE. Linked from the reply: "Inside the Advisory Database and what happens when vulnerability volume breaks records", which suggests the bottleneck is database curation rather than CVE issuance. If that is the answer, "queued, and the queue is long" would be enough - and is worth asking for explicitly.
|
|
43
|
+
|
|
44
|
+
## 2026-08-20
|
|
45
|
+
|
|
46
|
+
### CVEs issued for the four narrowed advisories
|
|
47
|
+
|
|
48
|
+
The 2026-08-15 narrowing round came back green: `GHSA-vqff` -> CVE-2026-76894, `GHSA-c499` -> CVE-2026-76895, `GHSA-3369` -> CVE-2026-76896, `GHSA-v3j5` -> CVE-2026-76897. Arguing dependency for `GHSA-c499` (one missing neutralization pass produces both the injection and the spoofing) worked as well as splitting did for the others - no advisory had to be re-published as a new one. Every published advisory now carries a CVE except `GHSA-x32r`, requested today.
|
|
49
|
+
|
|
50
|
+
`SECURITY.md` updated. The propagation gap is unchanged and remains the subject of ticket #4682889: a CVE is necessary but not sufficient, and the global database still lists two of fourteen.
|
|
51
|
+
|
|
52
|
+
### v0.4.119 - classify IPv6 by value, not by prefix string (GHSA-x32r-mh7g-q2rf)
|
|
53
|
+
|
|
54
|
+
`0xRomSec` reported that `isBlockedIp` misses the IPv6 ranges that embed an IPv4 address: NAT64 (`64:ff9b::/96`, `64:ff9b:1::/48`), 6to4 (`2002::/16`) and IPv4-compatible (`::/96`). Verified against the real function - all eight cases in the report return `false`. `64:ff9b::a9fe:a9fe` is 169.254.169.254 written in IPv6, and the function is the single decision point for both the literal guard and the DNS-resolution guard, so the gap applied to both halves.
|
|
55
|
+
|
|
56
|
+
Fixed structurally rather than by appending a fifth prefix: `parseIpv6` expands any spelling (compressed, expanded, upper-case, leading zeros, dotted-quad tail, zone id) into eight 16-bit words, and the classifier decides on values. Unparseable input fails closed. Added `2001::/32` (Teredo) and IPv4 `198.18.0.0/15` as hardening - both embed *public* addresses and open no path to RFC1918, so they sit outside the vector.
|
|
57
|
+
|
|
58
|
+
The DNS half needed no change: `createSsrfSafeLookup` already passes `all: true` with `family: family || 0` and iterates every result, so an AAAA record was always inspected - it was the classifier that waved it through. Checked before writing the fix, since a `family: 4` there would have made the patch half a fix.
|
|
59
|
+
|
|
60
|
+
Tests: the eight advisory cases, four non-canonical spellings of the same address, four unparseable literals, `198.18.0.0/15` boundaries, plus three guard-level regressions that exercise the actual bypass chain (literal guard, `assertHostnameResolvesSafe`, connection-time lookup) rather than the classifier alone. 495 passing. Both build targets checked - `http.ts` is shared with the Workers bundle.
|
|
61
|
+
|
|
62
|
+
Released the full round: npm 0.4.119 (CI, provenance), GitHub release with dxt/skill assets, Cloudflare deployed and verified in prod (`http://[64:ff9b::a9fe:a9fe]` rejected, normal search intact), MCP Registry 0.4.119 `isLatest=true`.
|
|
63
|
+
|
|
64
|
+
API note, same class as the missing comments endpoint: **there is no `POST .../security-advisories/{ghsa}/publish`** - 404, and the OpenAPI description lists only `get`, `patch`, `cve` and `forks` for that path. Publishing an advisory is UI-only. `PATCH` (description, `patched_versions`) and `POST .../cve` do work from the API.
|
|
65
|
+
|
|
66
|
+
## 2026-08-20
|
|
67
|
+
|
|
68
|
+
### CVE-2026-76811/76812/76813 issued; advisory table added to SECURITY.md
|
|
69
|
+
|
|
70
|
+
GitHub issued CVEs for the three advisories still pending from the July batch: `GHSA-38f8` (CVE-2026-76811, High), `GHSA-vmrr` (CVE-2026-76812, Critical) and `GHSA-q5gv` (CVE-2026-76813, High). All three were already published and patched (0.4.110 / 0.4.111), so no action was required on the advisories themselves. The 2026-08-15 narrowing round (`GHSA-3369`, `GHSA-v3j5`, `GHSA-c499`, `GHSA-vqff`) is still without an outcome.
|
|
71
|
+
|
|
72
|
+
`SECURITY.md` now carries a GHSA → CVE → fixed-in table for all thirteen published advisories, plus a supported-versions note. Until now the CVE mapping existed only scattered across LOG entries.
|
|
73
|
+
|
|
74
|
+
Propagation remains the real gap. `GET /advisories?affects=@aborruso/ckan-mcp-server` still returns only two records (CVE-2026-33060, CVE-2026-53509) out of thirteen published advisories, so Dependabot has never alerted downstream users about the other eleven — including the Critical SSRF-to-cloud-metadata one. CVE-2026-61612 has had its CVE since 2026-06-22 without propagating, which argues against a plain review backlog. Escalated to GitHub Support as ticket **#4682889** (org `ondata`, category Repositories / repository features), 2026-08-20. The portal's AI triage confirmed the reading before letting the ticket through: the documented review window after publication is 72 hours, nothing in the docs describes a further maintainer action to force propagation, and inspecting why some advisories were promoted and others not requires Support. Draft kept in `tmp/github-support-advisory-propagation.md`.
|
|
75
|
+
|
|
76
|
+
## 2026-08-15
|
|
77
|
+
|
|
78
|
+
### CVE requests: three rejected advisories narrowed and resubmitted
|
|
79
|
+
|
|
80
|
+
GitHub issued CVE-2026-73844 (`GHSA-6f9w`), CVE-2026-73845 (`GHSA-83x6`) and CVE-2026-73846 (`GHSA-78x9`), and rejected three others under CNA rule 4.2.11 — each advisory described more than one independently fixable vulnerability. The requests originate from `GHSA-p5c9`, where the reporters (Gal3m, mrostamipoor) asked for one CVE per finding.
|
|
81
|
+
|
|
82
|
+
Rather than split published advisories into new public ones, each was **narrowed** to a single defect, with the rest demoted to impact or deployment context:
|
|
83
|
+
|
|
84
|
+
- `GHSA-3369` — kept the second-order SSRF (destination derived from resource `url` metadata); dropped the `Promise.all` amplification claim, which a concurrency cap fixes independently. CWE-400 removed; vector `S:U/C:L/A:L` (4.7) → `S:C/C:L/A:N` (4.0, still Medium).
|
|
85
|
+
- `GHSA-v3j5` — kept the missing `Origin`/`Host` validation. The MCP spec (Basic/Transports) draws the line for us: origin validation is **MUST**, loopback binding and authentication are **SHOULD**. The latter two stay as deployment context. CWE-306 and CWE-1327 removed.
|
|
86
|
+
- `GHSA-c499` — argued dependency rather than narrowing: one missing neutralization pass at the render boundary produces both the prompt injection and the content spoofing, and no patch fixes one without the other. The remediation section was rewritten as a single control (it previously listed two, which read as two defects). CWE-79 removed.
|
|
87
|
+
|
|
88
|
+
Also requested the CVE for `GHSA-vqff`, which was in the reporters' list but had neither a CVE nor a rejection — the request had apparently never been submitted.
|
|
89
|
+
|
|
90
|
+
API notes: `PATCH /repos/{o}/{r}/security-advisories/{ghsa}` accepts `summary`, `description`, `cwe_ids`, `cvss_vector_string` (mutually exclusive with `severity`); `POST .../{ghsa}/cve` re-requests the CVE. There is **no** comments endpoint (404, absent from the OpenAPI description) — advisory comments were posted by replying to the GitHub notification email.
|
|
91
|
+
|
|
92
|
+
Worth noting for expectations: a CVE is necessary but not sufficient for Dependabot alerts. Only 2 of the repo's advisories are in the global GitHub Advisory Database; CVE-2026-61612 has had a CVE since 2026-06-22 and is still not propagated.
|
|
93
|
+
|
|
3
94
|
## 2026-08-06
|
|
4
95
|
|
|
5
96
|
### v0.4.118 — pin the fetch path to the validated IP
|
package/dist/index.js
CHANGED
|
@@ -660,14 +660,53 @@ async function decodePossiblyCompressed(data, headers) {
|
|
|
660
660
|
return text;
|
|
661
661
|
}
|
|
662
662
|
}
|
|
663
|
+
function parseIpv6(input) {
|
|
664
|
+
const v = input.split("%")[0];
|
|
665
|
+
if (v.includes(":::") || (v.match(/::/g) || []).length > 1) return null;
|
|
666
|
+
const [head, tail, ...rest] = v.split("::");
|
|
667
|
+
if (rest.length > 0) return null;
|
|
668
|
+
const compressed = v.includes("::");
|
|
669
|
+
const words = [];
|
|
670
|
+
const pushGroups = (part) => {
|
|
671
|
+
if (part === "") return true;
|
|
672
|
+
for (const g of part.split(":")) {
|
|
673
|
+
if (g.includes(".")) {
|
|
674
|
+
const m = g.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
675
|
+
if (!m) return false;
|
|
676
|
+
const o = m.slice(1).map(Number);
|
|
677
|
+
if (o.some((n) => n > 255)) return false;
|
|
678
|
+
words.push(o[0] << 8 | o[1], o[2] << 8 | o[3]);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (!/^[0-9a-f]{1,4}$/.test(g)) return false;
|
|
682
|
+
words.push(parseInt(g, 16));
|
|
683
|
+
}
|
|
684
|
+
return true;
|
|
685
|
+
};
|
|
686
|
+
if (!pushGroups(head)) return null;
|
|
687
|
+
const headLen = words.length;
|
|
688
|
+
if (!pushGroups(compressed ? tail : "")) return null;
|
|
689
|
+
const tailLen = words.length - headLen;
|
|
690
|
+
if (!compressed) return words.length === 8 ? words : null;
|
|
691
|
+
const gap = 8 - headLen - tailLen;
|
|
692
|
+
if (gap < 1) return null;
|
|
693
|
+
return [...words.slice(0, headLen), ...Array(gap).fill(0), ...words.slice(headLen)];
|
|
694
|
+
}
|
|
663
695
|
function isBlockedIp(ip) {
|
|
664
696
|
const v = ip.toLowerCase().trim();
|
|
665
697
|
if (v.includes(":")) {
|
|
666
|
-
|
|
667
|
-
if (
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
698
|
+
const w = parseIpv6(v);
|
|
699
|
+
if (!w) return true;
|
|
700
|
+
const zeros = (n) => w.slice(0, n).every((x) => x === 0);
|
|
701
|
+
return zeros(6) || // ::/96 unspecified, loopback, IPv4-compatible
|
|
702
|
+
zeros(5) && w[5] === 65535 || // ::ffff:0:0/96 IPv4-mapped
|
|
703
|
+
w[0] === 100 && w[1] === 65435 && (w[2] === 0 && w[3] === 0 && w[4] === 0 && w[5] === 0 || w[2] === 1) || // 64:ff9b::/96 + 64:ff9b:1::/48 NAT64
|
|
704
|
+
w[0] === 8194 || // 2002::/16 6to4
|
|
705
|
+
w[0] === 8193 && w[1] === 0 || // 2001::/32 Teredo
|
|
706
|
+
(w[0] & 65024) === 64512 || // fc00::/7 unique local
|
|
707
|
+
(w[0] & 65472) === 65152 || // fe80::/10 link-local
|
|
708
|
+
(w[0] & 65472) === 65216 || // fec0::/10 site-local (deprecated)
|
|
709
|
+
(w[0] & 65280) === 65280;
|
|
671
710
|
}
|
|
672
711
|
const m = v.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
|
|
673
712
|
if (!m) return false;
|
|
@@ -680,7 +719,8 @@ function isBlockedIp(ip) {
|
|
|
680
719
|
o1 === 169 && o2 === 254 || // 169.254.0.0/16 link-local / cloud metadata
|
|
681
720
|
o1 === 172 && o2 >= 16 && o2 <= 31 || // 172.16.0.0/12 private
|
|
682
721
|
o1 === 192 && o2 === 168 || // 192.168.0.0/16 private
|
|
683
|
-
o1 ===
|
|
722
|
+
o1 === 198 && (o2 === 18 || o2 === 19) || // 198.18.0.0/15 benchmarking
|
|
723
|
+
o1 >= 224;
|
|
684
724
|
}
|
|
685
725
|
function validateServerUrl(serverUrl) {
|
|
686
726
|
let parsed;
|
|
@@ -911,6 +951,9 @@ async function makeCkanRequest(serverUrl, action, params = {}, opts = {}) {
|
|
|
911
951
|
timeout: 3e4,
|
|
912
952
|
responseType: "arraybuffer",
|
|
913
953
|
maxRedirects: 5,
|
|
954
|
+
// Never route through HTTP_PROXY/HTTPS_PROXY: a proxy would connect to the
|
|
955
|
+
// target itself, bypassing the SSRF-safe lookup pinned in the agents below.
|
|
956
|
+
proxy: false,
|
|
914
957
|
maxContentLength: MAX_RESPONSE_BYTES,
|
|
915
958
|
maxBodyLength: MAX_RESPONSE_BYTES,
|
|
916
959
|
...safeAgents ? { httpAgent: safeAgents.httpAgent, httpsAgent: safeAgents.httpsAgent } : {},
|
|
@@ -5757,7 +5800,7 @@ var registerAllPrompts = (server) => {
|
|
|
5757
5800
|
function createServer() {
|
|
5758
5801
|
return new McpServer({
|
|
5759
5802
|
name: "ckan-mcp-server",
|
|
5760
|
-
version: "0.4.
|
|
5803
|
+
version: "0.4.120"
|
|
5761
5804
|
});
|
|
5762
5805
|
}
|
|
5763
5806
|
function registerAll(server) {
|