@aborruso/ckan-mcp-server 0.4.118 → 0.4.119

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.
Files changed (3) hide show
  1. package/LOG.md +40 -0
  2. package/dist/index.js +43 -5
  3. package/package.json +1 -1
package/LOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # LOG
2
2
 
3
+ ## 2026-08-20
4
+
5
+ ### v0.4.119 - classify IPv6 by value, not by prefix string (GHSA-x32r-mh7g-q2rf)
6
+
7
+ `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.
8
+
9
+ 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.
10
+
11
+ 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.
12
+
13
+ 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.
14
+
15
+ ## 2026-08-20
16
+
17
+ ### CVE-2026-76811/76812/76813 issued; advisory table added to SECURITY.md
18
+
19
+ 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.
20
+
21
+ `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.
22
+
23
+ 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`.
24
+
25
+ ## 2026-08-15
26
+
27
+ ### CVE requests: three rejected advisories narrowed and resubmitted
28
+
29
+ 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.
30
+
31
+ 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:
32
+
33
+ - `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).
34
+ - `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.
35
+ - `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.
36
+
37
+ 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.
38
+
39
+ 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.
40
+
41
+ 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.
42
+
3
43
  ## 2026-08-06
4
44
 
5
45
  ### v0.4.118 — pin the fetch path to the validated IP
package/dist/index.js CHANGED
@@ -660,14 +660,51 @@ 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
- if (v === "::1" || v === "::") return true;
667
- if (v.startsWith("fc") || v.startsWith("fd")) return true;
668
- if (v.startsWith("fe80")) return true;
669
- if (v.startsWith("::ffff:")) return true;
670
- return false;
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;
671
708
  }
672
709
  const m = v.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
673
710
  if (!m) return false;
@@ -680,6 +717,7 @@ function isBlockedIp(ip) {
680
717
  o1 === 169 && o2 === 254 || // 169.254.0.0/16 link-local / cloud metadata
681
718
  o1 === 172 && o2 >= 16 && o2 <= 31 || // 172.16.0.0/12 private
682
719
  o1 === 192 && o2 === 168 || // 192.168.0.0/16 private
720
+ o1 === 198 && (o2 === 18 || o2 === 19) || // 198.18.0.0/15 benchmarking
683
721
  o1 === 255;
684
722
  }
685
723
  function validateServerUrl(serverUrl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aborruso/ckan-mcp-server",
3
- "version": "0.4.118",
3
+ "version": "0.4.119",
4
4
  "mcpName": "io.github.aborruso/ckan-mcp-server",
5
5
  "description": "MCP server for interacting with CKAN open data portals",
6
6
  "repository": {