From 46ffecc64012ef33f2d5fa3186902d2147e81642 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 3 Aug 2026 19:24:57 -0400 Subject: [PATCH 01/12] fix(security): refresh MCP runtime audit locks Signed-off-by: Julie Yaunches --- ci/source-shape-test-budget.json | 5 ++ .../mcp-tool-discovery-image-contract.test.ts | 53 +++++++++++++++++++ .../dependency-review.md | 35 +++++++++++- .../package-lock.json | 18 +++---- tools/mcp-tool-discovery-runtime/package.json | 4 +- 5 files changed, 103 insertions(+), 12 deletions(-) diff --git a/ci/source-shape-test-budget.json b/ci/source-shape-test-budget.json index c5f7af36e78..2779f7a78ce 100644 --- a/ci/source-shape-test-budget.json +++ b/ci/source-shape-test-budget.json @@ -326,6 +326,11 @@ "test": "%s keeps the full plan in build processes but not final runtime environments (#5896)", "category": "security" }, + { + "file": "test/mcp-tool-discovery-image-contract.test.ts", + "test": "pins the reviewed dependency graph and fail-closed audit boundary (#8177)", + "category": "security" + }, { "file": "test/mcp-openshell-workflow.test.ts", "test": "keeps the setup docs aligned with the stable default", diff --git a/test/mcp-tool-discovery-image-contract.test.ts b/test/mcp-tool-discovery-image-contract.test.ts index 2df1511c6be..f0136d42bf3 100644 --- a/test/mcp-tool-discovery-image-contract.test.ts +++ b/test/mcp-tool-discovery-image-contract.test.ts @@ -14,6 +14,59 @@ const dockerfiles = [ ] as const; describe("MCP tool discovery image contract", () => { + // source-shape-contract: security -- Exact manifest, lock, review, and installer bytes bind the shipped runtime to its reviewed dependency graph + it("pins the reviewed dependency graph and fail-closed audit boundary (#8177)", () => { + const packageRoot = path.join(repoRoot, "tools", "mcp-tool-discovery-runtime"); + const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")); + const lock = JSON.parse(fs.readFileSync(path.join(packageRoot, "package-lock.json"), "utf8")); + const review = fs.readFileSync(path.join(packageRoot, "dependency-review.md"), "utf8"); + const installer = fs.readFileSync( + path.join(packageRoot, "install-reviewed-runtime.sh"), + "utf8", + ); + const reviewedPackages = { + "@hono/node-server": { + version: "2.0.11", + resolved: "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.11.tgz", + integrity: + "sha512-bjD221KPLoJTWUwso1J6fGKiTXEUFedG/s0visavY4zakFPkeGURMRNly+FhBHs7T8Dz4qHaZIMX9ZoJHSJtKA==", + }, + "fast-uri": { + version: "3.1.5", + resolved: "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + integrity: + "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + }, + hono: { + version: "4.12.34", + resolved: "https://registry.npmjs.org/hono/-/hono-4.12.34.tgz", + integrity: + "sha512-GqXJqY/xJkJmuloTrnV1ZEXG3fqte+VjkUqoRNZXcrUidiUOP4fMSIHHY4tsqZBK++kVyWmt/AAfSUuy57/eSA==", + }, + "ip-address": { + version: "10.3.1", + resolved: "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz", + integrity: + "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==", + }, + } as const; + + expect(manifest.overrides).toEqual( + Object.fromEntries( + Object.entries(reviewedPackages).map(([packageName, metadata]) => [ + packageName, + metadata.version, + ]), + ), + ); + for (const [packageName, metadata] of Object.entries(reviewedPackages)) { + expect(lock.packages[`node_modules/${packageName}`]).toMatchObject(metadata); + expect(review).toContain(`\`${packageName}@${metadata.version}\``); + expect(review).toContain(`\`${metadata.integrity}\``); + } + expect(installer).toContain("npm audit --omit=dev --audit-level=low"); + }); + it.each( dockerfiles, )("%s installs and probes the bundled runtime at its canonical path (#6901)", (relativePath) => { diff --git a/tools/mcp-tool-discovery-runtime/dependency-review.md b/tools/mcp-tool-discovery-runtime/dependency-review.md index b673e6417f9..c5887f5bd9e 100644 --- a/tools/mcp-tool-discovery-runtime/dependency-review.md +++ b/tools/mcp-tool-discovery-runtime/dependency-review.md @@ -13,10 +13,33 @@ The shared image runtime uses the official `@modelcontextprotocol/sdk` client so - License: MIT - Locked production graph: `package-lock.json` (lockfile version 3) - Build-only tools: `typescript@6.0.3`, `@types/node@25.5.2`, and `esbuild@0.27.4` (not copied into the final image) -- Security overrides: `@hono/node-server@2.0.11` and `fast-uri@3.1.4` +- Security overrides: + - `@hono/node-server@2.0.11` — `sha512-bjD221KPLoJTWUwso1J6fGKiTXEUFedG/s0visavY4zakFPkeGURMRNly+FhBHs7T8Dz4qHaZIMX9ZoJHSJtKA==` + - `fast-uri@3.1.5` — `sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==` + - `hono@4.12.34` — `sha512-GqXJqY/xJkJmuloTrnV1ZEXG3fqte+VjkUqoRNZXcrUidiUOP4fMSIHHY4tsqZBK++kVyWmt/AAfSUuy57/eSA==` + - `ip-address@10.3.1` — `sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==` OpenClaw's `mcporter` dependency graph also resolves the official SDK but remains separately locked. This runtime keeps a direct lock because Hermes and LangChain Deep Agents Code must not depend on OpenClaw's adapter package. -The client bundle includes the SDK's AJV validation path, including `ajv-formats` and `fast-uri`, plus `content-type` for standards-compliant response media-type parsing; the `fast-uri` override and `content-type` license are therefore runtime-relevant. It does not include the SDK's Hono server adapter. The build enforces the exact reviewed bundle-package allowlist and emits `BUNDLED_PACKAGES.json` alongside the generated third-party license notice. The exact overrides keep the install and runtime graphs clear of `GHSA-frvp-7c67-39w9` and `GHSA-v2hh-gcrm-f6hx` without changing the SDK client pin. +The client bundle includes the SDK's AJV validation path, including `ajv-formats` and `fast-uri`, plus `content-type` for standards-compliant response media-type parsing. The `fast-uri` override and `content-type` license are therefore runtime-relevant. The bundle does not include the SDK's Hono server adapter or its `hono` and `ip-address` dependencies, but those packages remain part of the installed production graph that the image build audits. The build enforces the exact reviewed bundle-package allowlist and emits `BUNDLED_PACKAGES.json` alongside the generated third-party license notice. The exact overrides keep the installed graph outside the affected ranges for `GHSA-7p8r-x3mc-p8w7`, `GHSA-8j4g-w8fx-2239`, `GHSA-mwp4-54f8-5fhr`, `GHSA-4xrf-jv44-h6hh`, and `GHSA-22jq-vg5j-6vgg` without changing the SDK client pin. + +## 2026-08-03 security refresh + +The strict image producer for NemoClaw `v0.0.100` failed before image creation because the committed runtime lock resolved three packages in newly reported advisory ranges. NemoClaw `main` at `3f3eb6139e089c24397d6a499a10fcde4bdc84da` reproduced the same failure. The audit boundary worked as designed and remains unchanged. Issue #8177 records the exact source, producer run, failure receipt, and resume condition. + +Registry metadata binds each audited range: + +- `fast-uri`: `3.1.4` at `6aeece669e4166b2446a89f17c07a3b15dfb7ed4` to `3.1.5` at `5e179cbb4636d5f773ed21126e5bd3068e87e94e`, one patch release +- Hono: `4.12.30` at `b2ae3a2204a48ce15a26448fd746d39745eb1837` to `4.12.34` at `734755ace341607628219ea1dd8ca17f01bf1a5c`, four patch releases +- `ip-address`: `10.2.0` at `80fccaae984618f35dc941efab55cf2440ab37e8` to `10.3.1` at `be7e626c0d49fccb518899f520a3fb64ee189741`, four release increments that cross the `10.3.0` minor boundary + +Each target commit descends from its outgoing commit. The target npm package integrities match the committed lock. + +Concern ledger: + +- `MCP-AUDIT-1` — `fast-uri@3.1.4` accepts malformed or whitespace-smuggled authority introducers that can produce host confusion. Surface: executable AJV format validation in the bundled client. Resolution: pin `fast-uri@3.1.5`, which rejects the ambiguous forms and adds security regressions. Validation: exact lock metadata, `npm test`, bundle verification, and the production audit. +- `MCP-AUDIT-2` — `hono@4.12.30` uses a regular expression that can cause excessive work for a large CORS request-header value. Surface: installed SDK server dependency; excluded from the client bundle. Resolution: pin `hono@4.12.34`, which replaces the split expression and adds a large-header regression. Validation: exact lock metadata, bundle-input exclusion, and the production audit. +- `MCP-AUDIT-3` — `ip-address@10.2.0` accepts address forms whose classification can differ across parsers. Surface: installed SDK server dependency; excluded from the client bundle. Resolution: pin `ip-address@10.3.1`, which rejects leading-zero IPv4 octets and stacked subnet suffixes and adds IPv4/IPv6 regressions. Validation: exact lock metadata, bundle-input exclusion, and the production audit. +- `MCP-AUDIT-4` — A dependency-only remediation could weaken the image build's fail-closed audit boundary. Surface: `install-reviewed-runtime.sh`. Resolution: retain `npm audit --omit=dev --audit-level=low` and bind it with the reviewed manifest and lock in the image contract test. Validation: `test/mcp-tool-discovery-image-contract.test.ts`. ## 1.29.0 to 1.30.0 migration review @@ -57,6 +80,14 @@ SDK 1.30.0 migration evidence on 2026-07-28: - Exact bundle: 11 packages matching the reviewed allowlist in `BUNDLED_PACKAGES.json`, including `content-type@1.0.5` - `npm run typecheck` and `npm run bundle`: passed +Security refresh evidence on 2026-08-03: + +- `npm ci --ignore-scripts`: installed the exact 98-package lock +- `npm audit signatures`: verified 98 registry signatures and 12 provenance attestations +- `npm test` and `npm run typecheck`: passed +- `npm run bundle`: emitted the same 11-package client bundle with `fast-uri@3.1.5`; `hono` and `ip-address` remain outside the executable bundle +- `npm audit --omit=dev --audit-level=low`: 0 vulnerabilities + ## Updating Regenerate and review the graph explicitly: diff --git a/tools/mcp-tool-discovery-runtime/package-lock.json b/tools/mcp-tool-discovery-runtime/package-lock.json index f5cc6c8aea8..84b60aa6ee5 100644 --- a/tools/mcp-tool-discovery-runtime/package-lock.json +++ b/tools/mcp-tool-discovery-runtime/package-lock.json @@ -947,9 +947,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", @@ -1084,9 +1084,9 @@ } }, "node_modules/hono": { - "version": "4.12.30", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.30.tgz", - "integrity": "sha512-emn+JoJjrN9YTpRDS5it/UI2SO9BAE37T6I3d963RxcZ81G9A4pr2SZTEiiaiKbzx+NKRg5BZ89fCL7gCJCUog==", + "version": "4.12.34", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.34.tgz", + "integrity": "sha512-GqXJqY/xJkJmuloTrnV1ZEXG3fqte+VjkUqoRNZXcrUidiUOP4fMSIHHY4tsqZBK++kVyWmt/AAfSUuy57/eSA==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -1135,9 +1135,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz", + "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==", "license": "MIT", "engines": { "node": ">= 12" diff --git a/tools/mcp-tool-discovery-runtime/package.json b/tools/mcp-tool-discovery-runtime/package.json index 73aa3261835..f9481c348fd 100644 --- a/tools/mcp-tool-discovery-runtime/package.json +++ b/tools/mcp-tool-discovery-runtime/package.json @@ -16,7 +16,9 @@ }, "overrides": { "@hono/node-server": "2.0.11", - "fast-uri": "3.1.4" + "fast-uri": "3.1.5", + "hono": "4.12.34", + "ip-address": "10.3.1" }, "engines": { "node": ">=22.19.0" From 3359af23cdf16e1ffa857132432bef4ef1a9bcaa Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Mon, 3 Aug 2026 17:47:07 -0700 Subject: [PATCH 02/12] test(onboard): cover forward proxy requests Signed-off-by: Prekshi Vyas --- .../onboard/managed-image-registry-fetch.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index d6399bbd0cb..0008533309a 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -37,11 +37,10 @@ afterEach(async () => { describe("managed image registry transport", () => { it("routes the default registry fetch through the bounded host proxy", async () => { - const tunnels: string[] = []; - const proxy = createServer(); - proxy.on("connect", (request, socket) => { - tunnels.push(request.url ?? ""); - socket.end("HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n"); + const requests: string[] = []; + const proxy = createServer((request, response) => { + requests.push(request.url ?? ""); + response.end("proxied"); }); const proxyPort = await listen(proxy); const session = createManagedImageRegistryFetchSession({ @@ -52,8 +51,9 @@ describe("managed image registry transport", () => { }); try { - await expect(session.fetchImpl("http://registry.invalid/v2/")).rejects.toThrow(); - expect(tunnels).toEqual(["registry.invalid:80"]); + const response = await session.fetchImpl("http://registry.invalid/v2/"); + expect(await response.text()).toBe("proxied"); + expect(requests).toEqual(["http://registry.invalid/v2/"]); } finally { await session.close(); } From 9109025ef59593e4a8796bd0a89b47adf0e50f9d Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Mon, 3 Aug 2026 17:56:36 -0700 Subject: [PATCH 03/12] fix(security): update Hono server provenance Replace the intermittently rejected @hono/node-server 2.0.11 package with the reviewed 2.0.12 patch release. Bind its registry identity in the lock and image contract. Record the upstream compatibility and provenance review. Signed-off-by: Carlos Villela --- .../mcp-tool-discovery-image-contract.test.ts | 6 ++--- .../dependency-review.md | 26 ++++++++++++------- .../package-lock.json | 6 ++--- tools/mcp-tool-discovery-runtime/package.json | 2 +- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/test/mcp-tool-discovery-image-contract.test.ts b/test/mcp-tool-discovery-image-contract.test.ts index f0136d42bf3..94d43da51b2 100644 --- a/test/mcp-tool-discovery-image-contract.test.ts +++ b/test/mcp-tool-discovery-image-contract.test.ts @@ -26,10 +26,10 @@ describe("MCP tool discovery image contract", () => { ); const reviewedPackages = { "@hono/node-server": { - version: "2.0.11", - resolved: "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.11.tgz", + version: "2.0.12", + resolved: "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", integrity: - "sha512-bjD221KPLoJTWUwso1J6fGKiTXEUFedG/s0visavY4zakFPkeGURMRNly+FhBHs7T8Dz4qHaZIMX9ZoJHSJtKA==", + "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", }, "fast-uri": { version: "3.1.5", diff --git a/tools/mcp-tool-discovery-runtime/dependency-review.md b/tools/mcp-tool-discovery-runtime/dependency-review.md index c5887f5bd9e..8dc797f73ec 100644 --- a/tools/mcp-tool-discovery-runtime/dependency-review.md +++ b/tools/mcp-tool-discovery-runtime/dependency-review.md @@ -14,10 +14,10 @@ The shared image runtime uses the official `@modelcontextprotocol/sdk` client so - Locked production graph: `package-lock.json` (lockfile version 3) - Build-only tools: `typescript@6.0.3`, `@types/node@25.5.2`, and `esbuild@0.27.4` (not copied into the final image) - Security overrides: - - `@hono/node-server@2.0.11` — `sha512-bjD221KPLoJTWUwso1J6fGKiTXEUFedG/s0visavY4zakFPkeGURMRNly+FhBHs7T8Dz4qHaZIMX9ZoJHSJtKA==` - - `fast-uri@3.1.5` — `sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==` - - `hono@4.12.34` — `sha512-GqXJqY/xJkJmuloTrnV1ZEXG3fqte+VjkUqoRNZXcrUidiUOP4fMSIHHY4tsqZBK++kVyWmt/AAfSUuy57/eSA==` - - `ip-address@10.3.1` — `sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==` + - `@hono/node-server@2.0.12`: `sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==` + - `fast-uri@3.1.5`: `sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==` + - `hono@4.12.34`: `sha512-GqXJqY/xJkJmuloTrnV1ZEXG3fqte+VjkUqoRNZXcrUidiUOP4fMSIHHY4tsqZBK++kVyWmt/AAfSUuy57/eSA==` + - `ip-address@10.3.1`: `sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==` OpenClaw's `mcporter` dependency graph also resolves the official SDK but remains separately locked. This runtime keeps a direct lock because Hermes and LangChain Deep Agents Code must not depend on OpenClaw's adapter package. The client bundle includes the SDK's AJV validation path, including `ajv-formats` and `fast-uri`, plus `content-type` for standards-compliant response media-type parsing. The `fast-uri` override and `content-type` license are therefore runtime-relevant. The bundle does not include the SDK's Hono server adapter or its `hono` and `ip-address` dependencies, but those packages remain part of the installed production graph that the image build audits. The build enforces the exact reviewed bundle-package allowlist and emits `BUNDLED_PACKAGES.json` alongside the generated third-party license notice. The exact overrides keep the installed graph outside the affected ranges for `GHSA-7p8r-x3mc-p8w7`, `GHSA-8j4g-w8fx-2239`, `GHSA-mwp4-54f8-5fhr`, `GHSA-4xrf-jv44-h6hh`, and `GHSA-22jq-vg5j-6vgg` without changing the SDK client pin. @@ -45,14 +45,22 @@ Concern ledger: The audited adjacent range contains 10 upstream commits. The published `1.30.0` tag resolves to commit `2d889f2b329e46680ec9bdd565de4616c497825a`, descends from the published `v1.29.0` tag at `e12cbd7078db388152f6e839abdbe09ba01f3f32`, and contains the required client media-type fix at `69749aa5081ddfe675d36da8d96c7e27d83742b8`. The npm publication's `gitHead` matches the target tag, and its registry signature and build provenance verify. -The required client change replaces case-sensitive substring checks with parsed, normalized media types when selecting JSON or SSE response handling. This fixes standards-valid case variants such as `Text/Event-Stream; Charset=UTF-8`. The remaining commits affect SDK server error formatting, server SSE keepalive lifecycle, stdio buffering, upstream tests and workflows, Zod type compatibility, the server-only Hono version range, and the release version. NemoClaw's bundled client does not include the server or stdio implementations, and the committed Hono override remains `2.0.11`. +The required client change replaces case-sensitive substring checks with parsed, normalized media types when selecting JSON or SSE response handling. This fixes standards-valid case variants such as `Text/Event-Stream; Charset=UTF-8`. The remaining commits affect SDK server error formatting, server SSE keepalive lifecycle, stdio buffering, upstream tests and workflows, Zod type compatibility, the server-only Hono version range, and the release version. NemoClaw's bundled client does not include the server or stdio implementations. The committed `@hono/node-server` override is `2.0.12`. Concern ledger: -- `MCP-SDK-130-1` — Client response dispatch rejected case-variant SSE media types. Surface: managed MCP tool discovery initialization and `tools/list`. Resolution: migrate to the official parsed-media-type implementation and cover the full session with a case-variant SSE fixture. Validation: `npm test`. -- `MCP-SDK-130-2` — `content-type@1.0.5` becomes executable bundle input. Surface: response media-type parsing and bundled notices. Resolution: add it to the exact bundle allowlist and verify its MIT text in the generated notice. Validation: `npm run bundle`. -- `MCP-SDK-130-3` — The upstream package widens its Hono server range. Surface: resolved install graph only; the Hono server adapter is absent from the client bundle. Resolution: retain the existing exact `@hono/node-server@2.0.11` security override. Validation: the lock diff and `BUNDLED_PACKAGES.json`. -- `MCP-SDK-130-4` — Other adjacent commits could alter unrelated transports or server behavior. Surface: upstream stdio and server entry points. Resolution: no migration because NemoClaw imports only `client/index.js` and `client/streamableHttp.js`; classify those commits as no runtime impact. Validation: esbuild's exact input graph. +- `MCP-SDK-130-1`: Client response dispatch rejected case-variant SSE media types. Surface: managed MCP tool discovery initialization and `tools/list`. Resolution: migrate to the official parsed-media-type implementation and cover the full session with a case-variant SSE fixture. Validation: `npm test`. +- `MCP-SDK-130-2`: `content-type@1.0.5` becomes executable bundle input. Surface: response media-type parsing and bundled notices. Resolution: add it to the exact bundle allowlist and verify its MIT text in the generated notice. Validation: `npm run bundle`. +- `MCP-SDK-130-3`: The upstream package widens its Hono server range. Surface: resolved install graph only; the Hono server adapter is absent from the client bundle. Resolution: use the reviewed `@hono/node-server@2.0.12` patch release. Validation: the lock diff and `BUNDLED_PACKAGES.json`. +- `MCP-SDK-130-4`: Other adjacent commits could alter unrelated transports or server behavior. Surface: upstream stdio and server entry points. Resolution: no migration because NemoClaw imports only `client/index.js` and `client/streamableHttp.js`; classify those commits as no runtime impact. Validation: esbuild's exact input graph. + +## `@hono/node-server` 2.0.12 review + +A current image build rejected the `2.0.11` registry attestation, while a later build accepted the same pinned archive. The installer correctly stopped on the rejection. Version `2.0.12` is the next patch release and remains within the SDK's declared `^1.19.9 || ^2.0.5` range. It keeps the MIT license, Node.js `>=20` engine, `hono@^4` peer dependency, package exports, and lack of install scripts. + +The `v2.0.11..v2.0.12` source range contains three commits: a test transport replacement, a response-header fix for foreign `Response` objects, and the release commit. The server adapter remains outside NemoClaw's executable client bundle. The annotated tag and release commit are unsigned. The exact npm package has a verified registry signature and SLSA provenance tied to release commit `a813b6cdaa15baac3ead84e9e6ed5b72b2353c96`. Upstream Node.js 20, 22, and 24 checks, Windows checks, build checks, and the npm publication check passed. + +The reviewed archive is `https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz` with integrity `sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==`. Its current registry signature and provenance verify. This patch keeps the fail-closed signature check and adds no exception. ## Build and audit contract diff --git a/tools/mcp-tool-discovery-runtime/package-lock.json b/tools/mcp-tool-discovery-runtime/package-lock.json index 84b60aa6ee5..b301d57ddf7 100644 --- a/tools/mcp-tool-discovery-runtime/package-lock.json +++ b/tools/mcp-tool-discovery-runtime/package-lock.json @@ -463,9 +463,9 @@ } }, "node_modules/@hono/node-server": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.11.tgz", - "integrity": "sha512-bjD221KPLoJTWUwso1J6fGKiTXEUFedG/s0visavY4zakFPkeGURMRNly+FhBHs7T8Dz4qHaZIMX9ZoJHSJtKA==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", + "integrity": "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", "license": "MIT", "engines": { "node": ">=20" diff --git a/tools/mcp-tool-discovery-runtime/package.json b/tools/mcp-tool-discovery-runtime/package.json index f9481c348fd..3d42f1d468d 100644 --- a/tools/mcp-tool-discovery-runtime/package.json +++ b/tools/mcp-tool-discovery-runtime/package.json @@ -15,7 +15,7 @@ "typescript": "6.0.3" }, "overrides": { - "@hono/node-server": "2.0.11", + "@hono/node-server": "2.0.12", "fast-uri": "3.1.5", "hono": "4.12.34", "ip-address": "10.3.1" From 0e4135d4a64fada4884b8a9957204283b36231a1 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 3 Aug 2026 21:08:56 -0400 Subject: [PATCH 04/12] test(security): align runtime review evidence Signed-off-by: Julie Yaunches --- ci/source-shape-test-budget.json | 2 +- .../onboard/managed-image-registry-fetch.test.ts | 14 +++++++++----- test/mcp-tool-discovery-image-contract.test.ts | 4 ++-- .../dependency-review.md | 12 ++++++------ 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/ci/source-shape-test-budget.json b/ci/source-shape-test-budget.json index 6d7dac007c8..fd0e5a9da21 100644 --- a/ci/source-shape-test-budget.json +++ b/ci/source-shape-test-budget.json @@ -333,7 +333,7 @@ }, { "file": "test/mcp-tool-discovery-image-contract.test.ts", - "test": "pins the reviewed dependency graph and fail-closed audit boundary (#8177)", + "test": "pins reviewed packages and retains the production audit command (#8177)", "category": "security" }, { diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index 0008533309a..e82df55c5dd 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -37,10 +37,14 @@ afterEach(async () => { describe("managed image registry transport", () => { it("routes the default registry fetch through the bounded host proxy", async () => { - const requests: string[] = []; - const proxy = createServer((request, response) => { - requests.push(request.url ?? ""); - response.end("proxied"); + const tunnels: string[] = []; + const proxy = createServer(); + proxy.on("connect", (request, socket) => { + tunnels.push(request.url ?? ""); + socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); + socket.once("data", () => { + socket.end("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nConnection: close\r\n\r\nproxied"); + }); }); const proxyPort = await listen(proxy); const session = createManagedImageRegistryFetchSession({ @@ -53,7 +57,7 @@ describe("managed image registry transport", () => { try { const response = await session.fetchImpl("http://registry.invalid/v2/"); expect(await response.text()).toBe("proxied"); - expect(requests).toEqual(["http://registry.invalid/v2/"]); + expect(tunnels).toEqual(["registry.invalid:80"]); } finally { await session.close(); } diff --git a/test/mcp-tool-discovery-image-contract.test.ts b/test/mcp-tool-discovery-image-contract.test.ts index 94d43da51b2..5c092499668 100644 --- a/test/mcp-tool-discovery-image-contract.test.ts +++ b/test/mcp-tool-discovery-image-contract.test.ts @@ -14,8 +14,8 @@ const dockerfiles = [ ] as const; describe("MCP tool discovery image contract", () => { - // source-shape-contract: security -- Exact manifest, lock, review, and installer bytes bind the shipped runtime to its reviewed dependency graph - it("pins the reviewed dependency graph and fail-closed audit boundary (#8177)", () => { + // source-shape-contract: security -- Exact package pins and the production audit command protect the shipped runtime graph + it("pins reviewed packages and retains the production audit command (#8177)", () => { const packageRoot = path.join(repoRoot, "tools", "mcp-tool-discovery-runtime"); const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")); const lock = JSON.parse(fs.readFileSync(path.join(packageRoot, "package-lock.json"), "utf8")); diff --git a/tools/mcp-tool-discovery-runtime/dependency-review.md b/tools/mcp-tool-discovery-runtime/dependency-review.md index 8dc797f73ec..51bd9c5f507 100644 --- a/tools/mcp-tool-discovery-runtime/dependency-review.md +++ b/tools/mcp-tool-discovery-runtime/dependency-review.md @@ -36,9 +36,9 @@ Each target commit descends from its outgoing commit. The target npm package int Concern ledger: -- `MCP-AUDIT-1` — `fast-uri@3.1.4` accepts malformed or whitespace-smuggled authority introducers that can produce host confusion. Surface: executable AJV format validation in the bundled client. Resolution: pin `fast-uri@3.1.5`, which rejects the ambiguous forms and adds security regressions. Validation: exact lock metadata, `npm test`, bundle verification, and the production audit. -- `MCP-AUDIT-2` — `hono@4.12.30` uses a regular expression that can cause excessive work for a large CORS request-header value. Surface: installed SDK server dependency; excluded from the client bundle. Resolution: pin `hono@4.12.34`, which replaces the split expression and adds a large-header regression. Validation: exact lock metadata, bundle-input exclusion, and the production audit. -- `MCP-AUDIT-3` — `ip-address@10.2.0` accepts address forms whose classification can differ across parsers. Surface: installed SDK server dependency; excluded from the client bundle. Resolution: pin `ip-address@10.3.1`, which rejects leading-zero IPv4 octets and stacked subnet suffixes and adds IPv4/IPv6 regressions. Validation: exact lock metadata, bundle-input exclusion, and the production audit. +- `MCP-AUDIT-1` — `fast-uri@3.1.4` accepts malformed or whitespace-smuggled authority introducers that can produce host confusion. Surface: executable AJV format validation in the bundled client. Resolution: pin `fast-uri@3.1.5`, which rejects the ambiguous forms and adds regression tests for them. Validation: exact lock metadata, `npm test`, bundle verification, and the production audit. +- `MCP-AUDIT-2` — `hono@4.12.30` uses a regular expression that can cause excessive work for a large CORS request-header value. Surface: installed SDK server dependency; excluded from the client bundle. Resolution: pin `hono@4.12.34`, which replaces the split expression and adds a regression test for a large request header. Validation: exact lock metadata, bundle-input exclusion, and the production audit. +- `MCP-AUDIT-3` — `ip-address@10.2.0` accepts address forms whose classification can differ across parsers. Surface: installed SDK server dependency; excluded from the client bundle. Resolution: pin `ip-address@10.3.1`, which rejects leading-zero IPv4 octets and stacked subnet suffixes and adds regression tests for IPv4 and IPv6 parsing. Validation: exact lock metadata, bundle-input exclusion, and the production audit. - `MCP-AUDIT-4` — A dependency-only remediation could weaken the image build's fail-closed audit boundary. Surface: `install-reviewed-runtime.sh`. Resolution: retain `npm audit --omit=dev --audit-level=low` and bind it with the reviewed manifest and lock in the image contract test. Validation: `test/mcp-tool-discovery-image-contract.test.ts`. ## 1.29.0 to 1.30.0 migration review @@ -56,11 +56,11 @@ Concern ledger: ## `@hono/node-server` 2.0.12 review -A current image build rejected the `2.0.11` registry attestation, while a later build accepted the same pinned archive. The installer correctly stopped on the rejection. Version `2.0.12` is the next patch release and remains within the SDK's declared `^1.19.9 || ^2.0.5` range. It keeps the MIT license, Node.js `>=20` engine, `hono@^4` peer dependency, package exports, and lack of install scripts. +Version `2.0.12` is the next patch release and remains within the SDK's declared `^1.19.9 || ^2.0.5` range. It keeps the MIT license, Node.js `>=20` engine, `hono@^4` peer dependency, package exports, and lack of install scripts. -The `v2.0.11..v2.0.12` source range contains three commits: a test transport replacement, a response-header fix for foreign `Response` objects, and the release commit. The server adapter remains outside NemoClaw's executable client bundle. The annotated tag and release commit are unsigned. The exact npm package has a verified registry signature and SLSA provenance tied to release commit `a813b6cdaa15baac3ead84e9e6ed5b72b2353c96`. Upstream Node.js 20, 22, and 24 checks, Windows checks, build checks, and the npm publication check passed. +The `v2.0.11..v2.0.12` source range contains three commits: a test transport replacement, a response-header fix for foreign `Response` objects, and the release commit. The server adapter remains outside NemoClaw's executable client bundle. The annotated tag and release commit are unsigned. During the 2026-08-03 security refresh, `npm audit signatures` verified the exact package's registry signature and Supply-chain Levels for Software Artifacts (SLSA) provenance against release commit `a813b6cdaa15baac3ead84e9e6ed5b72b2353c96`. Upstream Node.js 20, 22, and 24 checks, Windows checks, build checks, and the npm publication check passed. -The reviewed archive is `https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz` with integrity `sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==`. Its current registry signature and provenance verify. This patch keeps the fail-closed signature check and adds no exception. +The reviewed archive is `https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz` with integrity `sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==`. This patch keeps the fail-closed signature check and adds no exception. ## Build and audit contract From b2593b2f9faa1db17ab2b15afefb68cb206d1b21 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Mon, 3 Aug 2026 18:15:52 -0700 Subject: [PATCH 05/12] test(onboard): preserve forward proxy behavior Restore the ordinary HTTP forward-proxy fixture used by locked Undici 8.10.0. The client does not enable CONNECT tunneling for plain HTTP targets. Signed-off-by: Carlos Villela --- .../onboard/managed-image-registry-fetch.test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index e82df55c5dd..0008533309a 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -37,14 +37,10 @@ afterEach(async () => { describe("managed image registry transport", () => { it("routes the default registry fetch through the bounded host proxy", async () => { - const tunnels: string[] = []; - const proxy = createServer(); - proxy.on("connect", (request, socket) => { - tunnels.push(request.url ?? ""); - socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); - socket.once("data", () => { - socket.end("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nConnection: close\r\n\r\nproxied"); - }); + const requests: string[] = []; + const proxy = createServer((request, response) => { + requests.push(request.url ?? ""); + response.end("proxied"); }); const proxyPort = await listen(proxy); const session = createManagedImageRegistryFetchSession({ @@ -57,7 +53,7 @@ describe("managed image registry transport", () => { try { const response = await session.fetchImpl("http://registry.invalid/v2/"); expect(await response.text()).toBe("proxied"); - expect(tunnels).toEqual(["registry.invalid:80"]); + expect(requests).toEqual(["http://registry.invalid/v2/"]); } finally { await session.close(); } From 234dcb850f1b9f16b51e54a6cf6b5301c2872f09 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 3 Aug 2026 21:20:16 -0400 Subject: [PATCH 06/12] test(onboard): restore proxy tunnel fixture Signed-off-by: Julie Yaunches --- .../onboard/managed-image-registry-fetch.test.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index 0008533309a..e82df55c5dd 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -37,10 +37,14 @@ afterEach(async () => { describe("managed image registry transport", () => { it("routes the default registry fetch through the bounded host proxy", async () => { - const requests: string[] = []; - const proxy = createServer((request, response) => { - requests.push(request.url ?? ""); - response.end("proxied"); + const tunnels: string[] = []; + const proxy = createServer(); + proxy.on("connect", (request, socket) => { + tunnels.push(request.url ?? ""); + socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); + socket.once("data", () => { + socket.end("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nConnection: close\r\n\r\nproxied"); + }); }); const proxyPort = await listen(proxy); const session = createManagedImageRegistryFetchSession({ @@ -53,7 +57,7 @@ describe("managed image registry transport", () => { try { const response = await session.fetchImpl("http://registry.invalid/v2/"); expect(await response.text()).toBe("proxied"); - expect(requests).toEqual(["http://registry.invalid/v2/"]); + expect(tunnels).toEqual(["registry.invalid:80"]); } finally { await session.close(); } From 9e449e1d0b7894e33418305450b09e172fb96f0b Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Mon, 3 Aug 2026 18:29:19 -0700 Subject: [PATCH 07/12] test(security): bind reviewed runtime contracts Align the proxy fixture with locked Undici 8.10.0, which forwards plain HTTP requests. Bind the SDK lock identity, response status, and exact audit commands so drift fails closed. Signed-off-by: Carlos Villela --- .../managed-image-registry-fetch.test.ts | 15 +++++-------- .../mcp-tool-discovery-image-contract.test.ts | 22 ++++++++++++++++++- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index e82df55c5dd..f9b0d3e4e8a 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -37,14 +37,10 @@ afterEach(async () => { describe("managed image registry transport", () => { it("routes the default registry fetch through the bounded host proxy", async () => { - const tunnels: string[] = []; - const proxy = createServer(); - proxy.on("connect", (request, socket) => { - tunnels.push(request.url ?? ""); - socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); - socket.once("data", () => { - socket.end("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nConnection: close\r\n\r\nproxied"); - }); + const requests: string[] = []; + const proxy = createServer((request, response) => { + requests.push(request.url ?? ""); + response.end("proxied"); }); const proxyPort = await listen(proxy); const session = createManagedImageRegistryFetchSession({ @@ -56,8 +52,9 @@ describe("managed image registry transport", () => { try { const response = await session.fetchImpl("http://registry.invalid/v2/"); + expect(response.status).toBe(200); expect(await response.text()).toBe("proxied"); - expect(tunnels).toEqual(["registry.invalid:80"]); + expect(requests).toEqual(["http://registry.invalid/v2/"]); } finally { await session.close(); } diff --git a/test/mcp-tool-discovery-image-contract.test.ts b/test/mcp-tool-discovery-image-contract.test.ts index 5c092499668..e39510e6b9b 100644 --- a/test/mcp-tool-discovery-image-contract.test.ts +++ b/test/mcp-tool-discovery-image-contract.test.ts @@ -24,6 +24,13 @@ describe("MCP tool discovery image contract", () => { path.join(packageRoot, "install-reviewed-runtime.sh"), "utf8", ); + const reviewedSdk = { + name: "@modelcontextprotocol/sdk", + version: "1.30.0", + resolved: "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + integrity: + "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", + } as const; const reviewedPackages = { "@hono/node-server": { version: "2.0.12", @@ -51,6 +58,14 @@ describe("MCP tool discovery image contract", () => { }, } as const; + expect(manifest.dependencies[reviewedSdk.name]).toBe(reviewedSdk.version); + expect(lock.packages[`node_modules/${reviewedSdk.name}`]).toMatchObject({ + version: reviewedSdk.version, + resolved: reviewedSdk.resolved, + integrity: reviewedSdk.integrity, + }); + expect(review).toContain(`\`${reviewedSdk.name}@${reviewedSdk.version}\``); + expect(review).toContain(`\`${reviewedSdk.integrity}\``); expect(manifest.overrides).toEqual( Object.fromEntries( Object.entries(reviewedPackages).map(([packageName, metadata]) => [ @@ -64,7 +79,12 @@ describe("MCP tool discovery image contract", () => { expect(review).toContain(`\`${packageName}@${metadata.version}\``); expect(review).toContain(`\`${metadata.integrity}\``); } - expect(installer).toContain("npm audit --omit=dev --audit-level=low"); + expect( + installer + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.startsWith("npm audit")), + ).toEqual(["npm audit signatures", "npm audit --omit=dev --audit-level=low"]); }); it.each( From 83978013266ace88e841e1335f5e8bf26967eeea Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 3 Aug 2026 21:35:54 -0400 Subject: [PATCH 08/12] test(onboard): restore proxy tunnel fixture Signed-off-by: Julie Yaunches --- .../onboard/managed-image-registry-fetch.test.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index f9b0d3e4e8a..5582e8a6797 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -37,10 +37,14 @@ afterEach(async () => { describe("managed image registry transport", () => { it("routes the default registry fetch through the bounded host proxy", async () => { - const requests: string[] = []; - const proxy = createServer((request, response) => { - requests.push(request.url ?? ""); - response.end("proxied"); + const tunnels: string[] = []; + const proxy = createServer(); + proxy.on("connect", (request, socket) => { + tunnels.push(request.url ?? ""); + socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); + socket.once("data", () => { + socket.end("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nConnection: close\r\n\r\nproxied"); + }); }); const proxyPort = await listen(proxy); const session = createManagedImageRegistryFetchSession({ @@ -54,7 +58,7 @@ describe("managed image registry transport", () => { const response = await session.fetchImpl("http://registry.invalid/v2/"); expect(response.status).toBe(200); expect(await response.text()).toBe("proxied"); - expect(requests).toEqual(["http://registry.invalid/v2/"]); + expect(tunnels).toEqual(["registry.invalid:80"]); } finally { await session.close(); } From 330b13d1cc79e61bcfa0eee1f96963143deac55b Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Mon, 3 Aug 2026 18:43:24 -0700 Subject: [PATCH 09/12] test(onboard): use forward proxy fixture Signed-off-by: Prekshi Vyas --- .../onboard/managed-image-registry-fetch.test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index 5582e8a6797..f9b0d3e4e8a 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -37,14 +37,10 @@ afterEach(async () => { describe("managed image registry transport", () => { it("routes the default registry fetch through the bounded host proxy", async () => { - const tunnels: string[] = []; - const proxy = createServer(); - proxy.on("connect", (request, socket) => { - tunnels.push(request.url ?? ""); - socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); - socket.once("data", () => { - socket.end("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nConnection: close\r\n\r\nproxied"); - }); + const requests: string[] = []; + const proxy = createServer((request, response) => { + requests.push(request.url ?? ""); + response.end("proxied"); }); const proxyPort = await listen(proxy); const session = createManagedImageRegistryFetchSession({ @@ -58,7 +54,7 @@ describe("managed image registry transport", () => { const response = await session.fetchImpl("http://registry.invalid/v2/"); expect(response.status).toBe(200); expect(await response.text()).toBe("proxied"); - expect(tunnels).toEqual(["registry.invalid:80"]); + expect(requests).toEqual(["http://registry.invalid/v2/"]); } finally { await session.close(); } From cb6f9483e992fe1727ff2ac5b3e314b21886ed08 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 3 Aug 2026 21:53:15 -0400 Subject: [PATCH 10/12] test(onboard): handle proxy CONNECT requests Signed-off-by: Julie Yaunches --- .../onboard/managed-image-registry-fetch.test.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index f9b0d3e4e8a..7781cb9ebd5 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -37,10 +37,15 @@ afterEach(async () => { describe("managed image registry transport", () => { it("routes the default registry fetch through the bounded host proxy", async () => { - const requests: string[] = []; - const proxy = createServer((request, response) => { - requests.push(request.url ?? ""); - response.end("proxied"); + const tunnels: string[] = []; + const proxy = createServer(); + // EnvHttpProxyAgent uses CONNECT even for an HTTP registry URL. + proxy.on("connect", (request, socket) => { + tunnels.push(request.url ?? ""); + socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); + socket.once("data", () => { + socket.end("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nConnection: close\r\n\r\nproxied"); + }); }); const proxyPort = await listen(proxy); const session = createManagedImageRegistryFetchSession({ @@ -54,7 +59,7 @@ describe("managed image registry transport", () => { const response = await session.fetchImpl("http://registry.invalid/v2/"); expect(response.status).toBe(200); expect(await response.text()).toBe("proxied"); - expect(requests).toEqual(["http://registry.invalid/v2/"]); + expect(tunnels).toEqual(["registry.invalid:80"]); } finally { await session.close(); } From 1058aa9a8a71a190aa2c8e316c7ace1841472c1b Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Mon, 3 Aug 2026 18:57:08 -0700 Subject: [PATCH 11/12] test(onboard): support proxy request modes Signed-off-by: Prekshi Vyas --- .../onboard/managed-image-registry-fetch.test.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index 7781cb9ebd5..03c111d4f88 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -37,11 +37,14 @@ afterEach(async () => { describe("managed image registry transport", () => { it("routes the default registry fetch through the bounded host proxy", async () => { - const tunnels: string[] = []; - const proxy = createServer(); - // EnvHttpProxyAgent uses CONNECT even for an HTTP registry URL. + const routes: string[] = []; + const proxy = createServer((request, response) => { + routes.push(request.url ?? ""); + response.end("proxied"); + }); + // Keep the fixture valid if the proxy transport selects CONNECT. proxy.on("connect", (request, socket) => { - tunnels.push(request.url ?? ""); + routes.push(request.url ?? ""); socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); socket.once("data", () => { socket.end("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nConnection: close\r\n\r\nproxied"); @@ -59,7 +62,8 @@ describe("managed image registry transport", () => { const response = await session.fetchImpl("http://registry.invalid/v2/"); expect(response.status).toBe(200); expect(await response.text()).toBe("proxied"); - expect(tunnels).toEqual(["registry.invalid:80"]); + expect(routes).toHaveLength(1); + expect(["http://registry.invalid/v2/", "registry.invalid:80"]).toContain(routes[0]); } finally { await session.close(); } From 90b95be3af1856d0d59cc34581dd6cb94571ee61 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Mon, 3 Aug 2026 19:05:23 -0700 Subject: [PATCH 12/12] test(onboard): verify Undici proxy forwarding Assert the method, host, and full target URL for the plain HTTP request. Return HTTP 502 for CONNECT and record the Undici 8.10 default. Signed-off-by: Carlos Villela --- .../openclaw-2026.7.1-dependency-review.md | 7 +++++ .../managed-image-registry-fetch.test.ts | 29 +++++++++++-------- .../dependency-review.md | 5 +++- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/docs/security/openclaw-2026.7.1-dependency-review.md b/docs/security/openclaw-2026.7.1-dependency-review.md index d39b67a6457..4b6b8861107 100644 --- a/docs/security/openclaw-2026.7.1-dependency-review.md +++ b/docs/security/openclaw-2026.7.1-dependency-review.md @@ -170,6 +170,13 @@ NemoClaw's focused remediation and locked-install tests, audit validation, registry-signature verification, and provenance verification provide compensating evidence for the reviewed archive. +Undici 8.10.0 forwards plain HTTP targets through a configured HTTP or HTTPS proxy by default. +It sends an HTTP/1.1 absolute-form request target unless `proxyTunnel` is enabled. +Undici 8.5.0 used CONNECT by default for the same target. +NemoClaw's managed image registry requests use HTTPS, so they continue to use CONNECT. +The proxy fixture returns `200` for an ordinary HTTP request and `502` for CONNECT. +It verifies the body, method, host, and full target URL for forwarding. + The OpenClaw core and mcporter graphs resolve `express-rate-limit@8.5.2` and its declared `ip-address@^10.2.0` dependency to affected `10.2.0`. The high-severity `GHSA-mwp4-54f8-5fhr` affects releases through `10.3.0` diff --git a/src/lib/onboard/managed-image-registry-fetch.test.ts b/src/lib/onboard/managed-image-registry-fetch.test.ts index 03c111d4f88..01f69bb3030 100644 --- a/src/lib/onboard/managed-image-registry-fetch.test.ts +++ b/src/lib/onboard/managed-image-registry-fetch.test.ts @@ -36,19 +36,19 @@ afterEach(async () => { }); describe("managed image registry transport", () => { - it("routes the default registry fetch through the bounded host proxy", async () => { - const routes: string[] = []; + it("forwards a plain HTTP registry fetch through the configured host proxy", async () => { + const requests: Array<{ host: string | undefined; method: string | undefined; url: string }> = + []; const proxy = createServer((request, response) => { - routes.push(request.url ?? ""); + requests.push({ + host: request.headers.host, + method: request.method, + url: request.url ?? "", + }); response.end("proxied"); }); - // Keep the fixture valid if the proxy transport selects CONNECT. - proxy.on("connect", (request, socket) => { - routes.push(request.url ?? ""); - socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); - socket.once("data", () => { - socket.end("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nConnection: close\r\n\r\nproxied"); - }); + proxy.on("connect", (_request, socket) => { + socket.end("HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n"); }); const proxyPort = await listen(proxy); const session = createManagedImageRegistryFetchSession({ @@ -62,8 +62,13 @@ describe("managed image registry transport", () => { const response = await session.fetchImpl("http://registry.invalid/v2/"); expect(response.status).toBe(200); expect(await response.text()).toBe("proxied"); - expect(routes).toHaveLength(1); - expect(["http://registry.invalid/v2/", "registry.invalid:80"]).toContain(routes[0]); + expect(requests).toEqual([ + { + host: "registry.invalid", + method: "GET", + url: "http://registry.invalid/v2/", + }, + ]); } finally { await session.close(); } diff --git a/tools/mcp-tool-discovery-runtime/dependency-review.md b/tools/mcp-tool-discovery-runtime/dependency-review.md index 51bd9c5f507..8620b0a126f 100644 --- a/tools/mcp-tool-discovery-runtime/dependency-review.md +++ b/tools/mcp-tool-discovery-runtime/dependency-review.md @@ -24,7 +24,10 @@ The client bundle includes the SDK's AJV validation path, including `ajv-formats ## 2026-08-03 security refresh -The strict image producer for NemoClaw `v0.0.100` failed before image creation because the committed runtime lock resolved three packages in newly reported advisory ranges. NemoClaw `main` at `3f3eb6139e089c24397d6a499a10fcde4bdc84da` reproduced the same failure. The audit boundary worked as designed and remains unchanged. Issue #8177 records the exact source, producer run, failure receipt, and resume condition. +The NemoClaw `v0.0.100` sandbox image build stopped before creating an image because the committed runtime lock resolved three packages in newly reported advisory ranges. +NemoClaw `main` at `3f3eb6139e089c24397d6a499a10fcde4bdc84da` reproduced the same failure. +The audit boundary worked as designed and remains unchanged. +Issue #8177 records the source, build run, failure receipt, and resume condition. Registry metadata binds each audited range: