diff --git a/internal/security-reviews/openclaw-2026.6.10-dependency-review.md b/internal/security-reviews/openclaw-2026.6.10-dependency-review.md index 3d06d37fdd7..2ac520c99c7 100644 --- a/internal/security-reviews/openclaw-2026.6.10-dependency-review.md +++ b/internal/security-reviews/openclaw-2026.6.10-dependency-review.md @@ -9,6 +9,8 @@ Review date: 2026-07-03 Advisory audit revalidated: 2026-07-21 +Retained remediation revalidated: 2026-08-21 + WeChat locked-graph audit revalidated: 2026-07-12 Scope: NemoClaw runtime pin `openclaw@2026.6.10`, the locked `mcporter@0.7.3` runtime graph, runtime helper pin `@zed-industries/codex-acp@0.11.1`, optional OpenClaw plugins, and built-in messaging OpenClaw plugins. @@ -86,10 +88,11 @@ The OpenClaw 2026.6.10 bump does not newly introduce an unfrozen OpenClaw transi ### Transitive Remediation Boundary This section is a point-in-time record of the remediation shipped for the -2026.6.10 runtime. The current 2026.7.1 path installs the reviewed core archive -directly because its core graph already contains the fixed versions, and keeps -only the version-scoped Slack and Microsoft Teams Axios remediation plus the -diagnostics Jaeger remediation added for the 2026.7.1 archive. See +2026.6.10 runtime. The retained compatibility branch now replaces its affected +`tar` dependency with `7.5.21` after `GHSA-r292-9mhp-454m` affected releases +through `7.5.20`. The current 2026.7.1 path also remediates its source `tar` and +`fs-safe` graph, while retaining the version-scoped Slack and Microsoft Teams +Axios remediation and the diagnostics Jaeger remediation. See [`openclaw-2026.7.1-dependency-review.md`](./openclaw-2026.7.1-dependency-review.md) for the active source and validation boundary. @@ -100,14 +103,14 @@ It also rejects unsafe archive members before extraction and after repacking. For `openclaw@2026.6.10`, the helper makes these changes: -- Replaces `tar@7.5.16` with `tar@7.5.19`. +- Replaces `tar@7.5.16` with `tar@7.5.21`. - Replaces `brace-expansion@5.0.6` with `brace-expansion@5.0.7`. -- Bundles the reviewed `@openclaw/fs-safe@0.3.0` package and removes its duplicate optional `tar` and `jszip` declarations. The bundled package resolves OpenClaw's reviewed direct `tar@7.5.19` and `jszip@3.10.1` dependencies instead, including during a global npm install. +- Bundles the reviewed `@openclaw/fs-safe@0.3.0` package and removes its duplicate optional `tar` and `jszip` declarations. The bundled package resolves OpenClaw's reviewed direct `tar@7.5.21` and `jszip@3.10.1` dependencies instead, including during a global npm install. - Verifies the installed global dependency tree before either the reviewed base image or production image can complete. For the E2E-only `openclaw@2026.3.11` identity, the helper requires the exact `tar@7.5.11` declaration, no bundled dependencies, no bundled tar package, and no npm shrinkwrap. The reviewed source archive SRI binds the remainder of the source manifest and package bytes. -The helper then verifies the exact `tar@7.5.19` registry SRI and tarball URL, copies that reviewed package into the remediated archive, and declares it as a bundled dependency so the later global install cannot resolve the replacement tar package from mutable registry state. +The helper then verifies the exact `tar@7.5.21` registry SRI and tarball URL, copies that reviewed package into the remediated archive, and declares it as a bundled dependency so the later global install cannot resolve the replacement tar package from mutable registry state. The committed patched-metadata hash binds the OpenClaw identity, replacement declaration, bundled-dependency marker, and bundled tar identity. For `@openclaw/slack@2026.6.10` and `@openclaw/msteams@2026.6.10`, the helper makes these changes: @@ -130,7 +133,7 @@ The replacement packages are bound to these registry identities: | Package | Reviewed npm integrity | Reviewed npm tarball URL | |---|---|---| -| `tar@7.5.19` | `sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==` | `https://registry.npmjs.org/tar/-/tar-7.5.19.tgz` | +| `tar@7.5.21` | `sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==` | `https://registry.npmjs.org/tar/-/tar-7.5.21.tgz` | | `brace-expansion@5.0.7` | `sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==` | `https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz` | | `@openclaw/fs-safe@0.3.0` | `sha512-uIBE441CIt1kIURoP9qRGKZ8LkGyfD9ZzeESjwAd29ZPWtghws/5GR3Pjb67jKdcJHP1I6roNXcvnhzAU7lHlA==` | `https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.3.0.tgz` | | `axios@1.18.0` | `sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==` | `https://registry.npmjs.org/axios/-/axios-1.18.0.tgz` | @@ -143,8 +146,8 @@ The helper extracts reviewed archives without invoking package lifecycle scripts It binds each patched package manifest and shrinkwrap to a committed SHA-512 metadata value. The core value also covers the bundled `@openclaw/fs-safe` package manifest. The diagnostics value also covers the bundled SDK, Jaeger propagator, and nested core package manifests. -The expected values are `sha512-B5O6Gu3YGY52w+Px8diL5zBtk8mj0u7E1ZvVK7KOLWX9H+S3B7kYUxnGfyB239mVYSluecfiWGvFFMk5eFhwKg==` for OpenClaw core, `sha512-ByLYBs3KXz3u0mPuj9DcP/xPTJNgQaLTPxazybhyIC1VjyftEmKQuoZufPZ8z8CjwBsOPm6NbjMQB2BfX36TTg==` for diagnostics OTEL, `sha512-AXllGzI+m33jUq3w1nCVXngLA1m9kH8c9XryHSoPzuVhGP6xwWpzgKl3yyfOMoIykN0GKcka59ZZbjEwkxFudQ==` for Slack, and `sha512-eTTIpA8HzcBwXBLt6UZDoFgOUmkRgIhcZFBOwg+5Jfgt8HDwtfPnqKo6vm2DdDdPMPhu08FbEzU5Gt3RoL5fIw==` for Microsoft Teams. -The E2E-only `openclaw@2026.3.11` value is `sha512-1i30XSb/2NEcuTcuhXfR/x3YKaXVhWq6ttecFBSD9nrCKrzjNxSNMfK1y3qRcnblNOzRWmHtJZwZKeej02s/EQ==`. +The expected values are `sha512-XMycUUV7gCzUYbjgwrglER0AQEtfuKUz6wyo4ilm/7nSSkLocYUYVkrJuBFYPW3no8Y5FW/1+2hWCssIyjxn3g==` for OpenClaw core, `sha512-ByLYBs3KXz3u0mPuj9DcP/xPTJNgQaLTPxazybhyIC1VjyftEmKQuoZufPZ8z8CjwBsOPm6NbjMQB2BfX36TTg==` for diagnostics OTEL, `sha512-AXllGzI+m33jUq3w1nCVXngLA1m9kH8c9XryHSoPzuVhGP6xwWpzgKl3yyfOMoIykN0GKcka59ZZbjEwkxFudQ==` for Slack, and `sha512-eTTIpA8HzcBwXBLt6UZDoFgOUmkRgIhcZFBOwg+5Jfgt8HDwtfPnqKo6vm2DdDdPMPhu08FbEzU5Gt3RoL5fIw==` for Microsoft Teams. +The E2E-only `openclaw@2026.3.11` value is `sha512-Yz/7GyAgLSPtJkijdUsVzxnjhATMPLRSFFMhl2H565aW7tReHZmuPeExBq0K4EEFkvg7zM2sFm2CP3f2oNw32Q==`. Both the library and command-line entry points enforce the same committed values. `Dockerfile.base` records `ignore-scripts+reviewed-lifecycle+transitive-remediation-v1` in its protected provenance marker. The production Dockerfile rejects stale base provenance and repeats the remediation when the marker does not match. @@ -163,7 +166,7 @@ The following concerns record the failure mode, completed disposition, and remai | `DEP-3` | The diagnostics OTEL archive bundles a Jaeger propagator that throws for malformed percent-encoded trace or baggage headers. A remote header can terminate extraction through an unhandled exception. | The `migrate`, `guard`, and `test` dispositions replace Jaeger with `2.9.0`, isolate its exact `2.9.0` core dependency, bind the patched metadata, and reject upstream graph drift. | Full E2E and the reviewed npm audit must pass for the PR SHA. | | `DEP-4` | `body-parser` retains one low root finding, while Hono and `protobufjs` retain moderate root findings. Expanding this patch to their package graphs without review can cause silent dependency drift. | The `document` disposition records each package, consumer, severity, and fix availability in the raw reports. The configured `high` threshold passes. | Re-review the affected package shrinkwraps before a later change remediates these findings. | | `DEP-5` | A previously built base image can claim the unremediated install recipe. | The `guard` and `test` dispositions change the protected provenance recipe. A stale or mismatched marker takes the complete reviewed install path. | Base-image and production-image CI must pass for the PR SHA. | -| `DEP-6` | The replacement graph has no repository-generated lock-derived SBOM. The `https-proxy-agent@5.0.1` and `agent-base@6.0.2` tarballs declare MIT in package metadata but contain no license file. | The `document` disposition records reviewed registry metadata, SRI, tarball URL, declared license, and packaged license-file inventory. The other replacement tarballs include license files. `tar@7.5.19` declares BlueOak-1.0.0, the OpenTelemetry packages declare Apache-2.0, and the other packages declare MIT. | Maintainers must retain this notice and SBOM limitation or add generated attribution evidence before release policy requires it. | +| `DEP-6` | The replacement graph has no repository-generated lock-derived SBOM. The `https-proxy-agent@5.0.1` and `agent-base@6.0.2` tarballs declare MIT in package metadata but contain no license file. | The `document` disposition records reviewed registry metadata, SRI, tarball URL, declared license, and packaged license-file inventory. The other replacement tarballs include license files. `tar@7.5.21` declares BlueOak-1.0.0, the OpenTelemetry packages declare Apache-2.0, and the other packages declare MIT. | Maintainers must retain this notice and SBOM limitation or add generated attribution evidence before release policy requires it. | ## Slack Source Review diff --git a/scripts/lib/openclaw-npm-remediation.mts b/scripts/lib/openclaw-npm-remediation.mts index d32dd735294..79bbbf90080 100755 --- a/scripts/lib/openclaw-npm-remediation.mts +++ b/scripts/lib/openclaw-npm-remediation.mts @@ -72,10 +72,10 @@ const AGENT_BASE_VERSION = "6.0.2"; const AGENT_BASE_INTEGRITY = "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="; const AGENT_BASE_TARBALL = "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz"; -const TAR_VERSION = "7.5.19"; +const TAR_VERSION = "7.5.21"; const TAR_INTEGRITY = - "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw=="; -const TAR_TARBALL = "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz"; + "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA=="; +const TAR_TARBALL = "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz"; const FS_SAFE_VERSION = "0.3.0"; const FS_SAFE_INTEGRITY = "sha512-uIBE441CIt1kIURoP9qRGKZ8LkGyfD9ZzeESjwAd29ZPWtghws/5GR3Pjb67jKdcJHP1I6roNXcvnhzAU7lHlA=="; @@ -162,7 +162,7 @@ const REMEDIATIONS: Readonly> = Object.freeze({ }, "openclaw@2026.6.10": { expectedPatchedMetadataIntegrity: - "sha512-B5O6Gu3YGY52w+Px8diL5zBtk8mj0u7E1ZvVK7KOLWX9H+S3B7kYUxnGfyB239mVYSluecfiWGvFFMk5eFhwKg==", + "sha512-XMycUUV7gCzUYbjgwrglER0AQEtfuKUz6wyo4ilm/7nSSkLocYUYVkrJuBFYPW3no8Y5FW/1+2hWCssIyjxn3g==", kind: "core", version: "2026.6.10", }, @@ -177,7 +177,7 @@ const REMEDIATIONS: Readonly> = Object.freeze({ "openclaw@2026.3.11": { kind: "legacy-core", expectedPatchedMetadataIntegrity: - "sha512-1i30XSb/2NEcuTcuhXfR/x3YKaXVhWq6ttecFBSD9nrCKrzjNxSNMfK1y3qRcnblNOzRWmHtJZwZKeej02s/EQ==", + "sha512-Yz/7GyAgLSPtJkijdUsVzxnjhATMPLRSFFMhl2H565aW7tReHZmuPeExBq0K4EEFkvg7zM2sFm2CP3f2oNw32Q==", version: "2026.3.11", }, }); @@ -683,8 +683,7 @@ export function patchCurrentOpenClawCorePackageGraph(packageDirectory: string): } if ( fsSafe?.version !== "0.4.1" || - fsSafe.resolved !== - "https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.4.1.tgz" || + fsSafe.resolved !== "https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.4.1.tgz" || fsSafe.integrity !== "sha512-hQi+BxO10KdRFlYUot1syC+hTaUnGeQNdqX5kwkKJig8CFq1tKsYJLPm+zkiiGsSKOprPAquQl/txejEhpKPgg==" || fsSafe.license !== "MIT" || diff --git a/test/mcp-add-crash-consistency.test.ts b/test/mcp-add-crash-consistency.test.ts index d00d4161c23..656f5b65021 100644 --- a/test/mcp-add-crash-consistency.test.ts +++ b/test/mcp-add-crash-consistency.test.ts @@ -33,10 +33,12 @@ function buildAddProcessScript( home: string, crashAfter: CrashBoundary, includeSecret = true, + initializeSandbox = true, ): string { return String.raw` process.env.HOME = ${JSON.stringify(home)}; const includeSecret = ${JSON.stringify(includeSecret)}; +const initializeSandbox = ${JSON.stringify(initializeSandbox)}; includeSecret ? (process.env.FAKE_MCP_SECRET = "host-only-secret") : delete process.env.FAKE_MCP_SECRET; const fs = require("node:fs"); const path = require("node:path"); @@ -280,7 +282,7 @@ processRecovery.executeSandboxCommand = (_sandbox, command) => { }; }; -if (!registry.getSandbox("crash-test")) { +if (initializeSandbox && !registry.getSandbox("crash-test")) { registry.registerSandbox({ name: "crash-test", agent: "openclaw", @@ -305,6 +307,26 @@ bridge.addMcpBridge("crash-test", { `; } +function initializeSandboxRegistry(home: string): void { + const result = spawnSync( + process.execPath, + [ + "-e", + `process.env.HOME = ${JSON.stringify(home)}; const registry = require("./src/lib/state/registry.js"); registry.registerSandbox({ name: "crash-test", agent: "openclaw", gatewayName: "nemoclaw" });`, + ], + { + cwd: process.cwd(), + encoding: "utf8", + env: { ...process.env, HOME: home }, + timeout: 30_000, + }, + ); + expect( + result.status, + `Could not initialize the MCP race fixture:\n${result.stdout}\n${result.stderr}`, + ).toBe(0); +} + function runAddProcess(home: string, crashAfter: CrashBoundary, includeSecret = true) { const script = buildAddProcessScript(home, crashAfter, includeSecret); return spawnSync(process.execPath, ["-e", script], { @@ -574,7 +596,10 @@ describe("MCP add crash consistency", () => { it("commits one bridge and rejects one duplicate after delayed credential projection (#9764)", async () => { const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-mcp-add-concurrent-projection-")); try { - const script = buildAddProcessScript(home, "credential-projection-coalesced"); + // Create the fixture before either process loads the registry. The + // behavior under test starts at the lifecycle lock, after fixture creation. + initializeSandboxRegistry(home); + const script = buildAddProcessScript(home, "credential-projection-coalesced", true, false); const first = spawnScript(home, script); const second = spawnScript(home, script); const results = await Promise.all([collectProcess(first), collectProcess(second)]); diff --git a/test/openclaw-2026-6-npm-remediation.test.ts b/test/openclaw-2026-6-npm-remediation.test.ts index b6ee0eccc60..e60bb3d5fa0 100644 --- a/test/openclaw-2026-6-npm-remediation.test.ts +++ b/test/openclaw-2026-6-npm-remediation.test.ts @@ -384,14 +384,14 @@ describe("OpenClaw npm remediation", () => { bundledDependencies?: string[]; dependencies?: Record; }>(path.join(directory, "package.json")); - expect(packageJson.dependencies).toMatchObject({ jszip: "3.10.1", tar: "7.5.19" }); + expect(packageJson.dependencies).toMatchObject({ jszip: "3.10.1", tar: "7.5.21" }); expect(packageJson.bundledDependencies).toEqual(["@openclaw/fs-safe"]); - expect(shrinkwrap.packages[""]).toMatchObject({ dependencies: { tar: "7.5.19" } }); + expect(shrinkwrap.packages[""]).toMatchObject({ dependencies: { tar: "7.5.21" } }); expect(shrinkwrap.packages["node_modules/tar"]).toMatchObject({ - version: "7.5.19", - resolved: "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", + version: "7.5.21", + resolved: "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz", integrity: - "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==", + "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==", }); expect(shrinkwrap.packages["node_modules/@openclaw/fs-safe"]?.optionalDependencies).toBe( undefined, @@ -454,7 +454,7 @@ describe("OpenClaw npm remediation", () => { ); expect(packageJson).toMatchObject({ bundledDependencies: ["@openclaw/fs-safe"], - dependencies: { jszip: "3.10.1", tar: "7.5.19" }, + dependencies: { jszip: "3.10.1", tar: "7.5.21" }, }); expect(fsSafePackageJson.optionalDependencies).toBeUndefined(); }); diff --git a/test/openclaw-dependency-review.test.ts b/test/openclaw-dependency-review.test.ts index adbd76e3897..64a4b3274f8 100644 --- a/test/openclaw-dependency-review.test.ts +++ b/test/openclaw-dependency-review.test.ts @@ -422,15 +422,15 @@ describe("OpenClaw 2026.6.10 dependency review contract", () => { expect(review).toContain("Transitive Dependency Graph Rationale"); expect(review).toContain("Transitive Remediation Boundary"); expect(review).toContain("point-in-time record of the remediation shipped for the"); - expect(review).toContain("current 2026.7.1 path installs the reviewed core archive"); + expect(review).toContain("current 2026.7.1 path also remediates its source `tar`"); expect(review).toContain("openclaw-2026.7.1-dependency-review.md"); expect(review).toContain("Transitive Remediation Concern Ledger"); expect(review).toContain("`openclaw@2026.6.10`, the helper makes these changes"); - expect(review).toContain("`tar@7.5.16` with `tar@7.5.19`"); + expect(review).toContain("`tar@7.5.16` with `tar@7.5.21`"); expect(review).toContain("`brace-expansion@5.0.6` with `brace-expansion@5.0.7`"); expect(review).toContain("`@openclaw/fs-safe@0.3.0`"); expect(review).toContain("removes its duplicate optional `tar` and `jszip` declarations"); - expect(review).toContain("direct `tar@7.5.19` and `jszip@3.10.1` dependencies"); + expect(review).toContain("direct `tar@7.5.21` and `jszip@3.10.1` dependencies"); expect(review).toContain("`axios@1.16.0` with `axios@1.18.0`"); expect(review).toContain("`https-proxy-agent@5.0.1` and `agent-base@6.0.2`"); expect(review).toContain("`@opentelemetry/propagator-jaeger@2.8.0` with `2.9.0`"); @@ -449,7 +449,7 @@ describe("OpenClaw 2026.6.10 dependency review contract", () => { "core value also covers the bundled `@openclaw/fs-safe` package manifest", ); for (const integrity of [ - "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==", + "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==", "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "sha512-uIBE441CIt1kIURoP9qRGKZ8LkGyfD9ZzeESjwAd29ZPWtghws/5GR3Pjb67jKdcJHP1I6roNXcvnhzAU7lHlA==", "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", @@ -457,7 +457,7 @@ describe("OpenClaw 2026.6.10 dependency review contract", () => { "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "sha512-4mYGty27rYvSM0jtp1ZUOqd3LfVRCYg9H5G9OFzSx5HViYToU21MFhWfco7x1HwXr7ER8yGOiCIHZUwjPksc0Q==", "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", - "sha512-B5O6Gu3YGY52w+Px8diL5zBtk8mj0u7E1ZvVK7KOLWX9H+S3B7kYUxnGfyB239mVYSluecfiWGvFFMk5eFhwKg==", + "sha512-XMycUUV7gCzUYbjgwrglER0AQEtfuKUz6wyo4ilm/7nSSkLocYUYVkrJuBFYPW3no8Y5FW/1+2hWCssIyjxn3g==", "sha512-ByLYBs3KXz3u0mPuj9DcP/xPTJNgQaLTPxazybhyIC1VjyftEmKQuoZufPZ8z8CjwBsOPm6NbjMQB2BfX36TTg==", "sha512-AXllGzI+m33jUq3w1nCVXngLA1m9kH8c9XryHSoPzuVhGP6xwWpzgKl3yyfOMoIykN0GKcka59ZZbjEwkxFudQ==", "sha512-eTTIpA8HzcBwXBLt6UZDoFgOUmkRgIhcZFBOwg+5Jfgt8HDwtfPnqKo6vm2DdDdPMPhu08FbEzU5Gt3RoL5fIw==", @@ -465,7 +465,7 @@ describe("OpenClaw 2026.6.10 dependency review contract", () => { expect(review).toContain(integrity); } for (const tarball of [ - "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", + "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz", "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", "https://registry.npmjs.org/@openclaw/fs-safe/-/fs-safe-0.3.0.tgz", "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", @@ -482,7 +482,7 @@ describe("OpenClaw 2026.6.10 dependency review contract", () => { "`https-proxy-agent@5.0.1` and `agent-base@6.0.2` tarballs declare MIT in package metadata but contain no license file", ); expect(review).toContain("The other replacement tarballs include license files"); - expect(review).toContain("`tar@7.5.19` declares BlueOak-1.0.0"); + expect(review).toContain("`tar@7.5.21` declares BlueOak-1.0.0"); expect(review).toContain("the OpenTelemetry packages declare Apache-2.0"); expect(review).toContain("the other packages declare MIT"); expect(review).toContain( diff --git a/test/openclaw-npm-remediation.test.ts b/test/openclaw-npm-remediation.test.ts index de41c4622a2..754a9ae8d7b 100644 --- a/test/openclaw-npm-remediation.test.ts +++ b/test/openclaw-npm-remediation.test.ts @@ -352,9 +352,9 @@ function writeLegacyCoreArchiveFixtures(): { mkdirSync(tarDirectory, { recursive: true }); writeFileSync( path.join(tarDirectory, "package.json"), - `${JSON.stringify({ name: "tar", version: "7.5.19" }, null, 2)}\n`, + `${JSON.stringify({ name: "tar", version: "7.5.21" }, null, 2)}\n`, ); - const tarArchive = path.join(root, "tar-7.5.19-source.tgz"); + const tarArchive = path.join(root, "tar-7.5.21-source.tgz"); packFixture(tarDirectory, tarArchive); const npmExecutable = path.join(root, "npm-fixture.sh"); @@ -365,9 +365,9 @@ function writeLegacyCoreArchiveFixtures(): { "set -euo pipefail", `tar_archive=${JSON.stringify(tarArchive)}`, 'case "$1:$2:${3:-}" in', - ' "view:tar@7.5.19:dist.integrity") value="sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==" ;;', - ' "view:tar@7.5.19:dist.tarball") value="https://registry.npmjs.org/tar/-/tar-7.5.19.tgz" ;;', - ' "pack:https://registry.npmjs.org/tar/-/tar-7.5.19.tgz:--pack-destination") ;;', + ' "view:tar@7.5.21:dist.integrity") value="sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==" ;;', + ' "view:tar@7.5.21:dist.tarball") value="https://registry.npmjs.org/tar/-/tar-7.5.21.tgz" ;;', + ' "pack:https://registry.npmjs.org/tar/-/tar-7.5.21.tgz:--pack-destination") ;;', ' *) echo "unexpected npm fixture invocation: $*" >&2; exit 1 ;;', "esac", 'if [ "$1" = "view" ]; then printf "%s\\n" "$value"; exit 0; fi', @@ -376,8 +376,8 @@ function writeLegacyCoreArchiveFixtures(): { ' if [ "$1" = "--pack-destination" ]; then destination="$2"; shift 2; continue; fi', " shift", "done", - 'cp "$tar_archive" "$destination/tar-7.5.19.tgz"', - 'printf \'[{"filename":"tar-7.5.19.tgz","integrity":"sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw=="}]\\n\'', + 'cp "$tar_archive" "$destination/tar-7.5.21.tgz"', + 'printf \'[{"filename":"tar-7.5.21.tgz","integrity":"sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA=="}]\\n\'', "", ].join("\n"), { mode: 0o700 }, @@ -632,18 +632,21 @@ describe("OpenClaw npm remediation", () => { ["fast-uri", "3.1.1", "fast-uri layout changed after review"], ["undici", "8.4.0", "dependency boundary changed after review"], ["ip-address", "10.1.1", "ip-address layout changed after review"], - ])("rejects a current OpenClaw %s graph that changed after review", (dependency, version, error) => { - const directory = - dependency === "brace-expansion" - ? writeCurrentCoreFixture(version) - : dependency === "fast-uri" - ? writeCurrentCoreFixture("5.0.7", version) - : dependency === "undici" - ? writeCurrentCoreFixture("5.0.7", "3.1.2", version) - : writeCurrentCoreFixture("5.0.7", "3.1.2", "8.5.0", version); - - expect(() => patchCurrentOpenClawCorePackageGraph(directory)).toThrow(error); - }); + ])( + "rejects a current OpenClaw %s graph that changed after review", + (dependency, version, error) => { + const directory = + dependency === "brace-expansion" + ? writeCurrentCoreFixture(version) + : dependency === "fast-uri" + ? writeCurrentCoreFixture("5.0.7", version) + : dependency === "undici" + ? writeCurrentCoreFixture("5.0.7", "3.1.2", version) + : writeCurrentCoreFixture("5.0.7", "3.1.2", "8.5.0", version); + + expect(() => patchCurrentOpenClawCorePackageGraph(directory)).toThrow(error); + }, + ); it.each([ ["resolved", "https://registry.npmjs.org/undici/-/undici-8.4.0.tgz"], @@ -767,11 +770,11 @@ describe("OpenClaw npm remediation", () => { bundledDependencies?: string[]; dependencies?: Record; }>(path.join(extracted, "package", "package.json")), - ).toMatchObject({ bundledDependencies: ["tar"], dependencies: { tar: "7.5.19" } }); + ).toMatchObject({ bundledDependencies: ["tar"], dependencies: { tar: "7.5.21" } }); expect( readJson<{ name?: string; version?: string }>( path.join(extracted, "package", "node_modules", "tar", "package.json"), ), - ).toMatchObject({ name: "tar", version: "7.5.19" }); + ).toMatchObject({ name: "tar", version: "7.5.21" }); }, 60_000); }); diff --git a/test/openclaw-security-revision-container-e2e.test.ts b/test/openclaw-security-revision-container-e2e.test.ts index 51596e75723..93406a17945 100644 --- a/test/openclaw-security-revision-container-e2e.test.ts +++ b/test/openclaw-security-revision-container-e2e.test.ts @@ -14,10 +14,10 @@ const IMAGE_ENV = "NEMOCLAW_OPENCLAW_SECURITY_REVISION_IMAGE"; const EVIDENCE_PREFIX = "NEMOCLAW_SECURITY_REVISION_EVIDENCE="; const OPENCLAW_ROOT = "/usr/local/lib/node_modules/openclaw"; const OPENCLAW_ENTRYPOINT = "/usr/local/bin/openclaw"; -const TAR_VERSION = "7.5.19"; +const TAR_VERSION = "7.5.21"; const TAR_INTEGRITY = - "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw=="; -const TAR_TARBALL = "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz"; + "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA=="; +const TAR_TARBALL = "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz"; const BRACE_EXPANSION_VERSION = "5.0.7"; const BRACE_EXPANSION_INTEGRITY = "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="; @@ -459,23 +459,24 @@ describe("OpenClaw current-image security revision contract (#7272)", () => { ); }); - test.each( - [ - ["--network", "none"], - ["--cap-drop", "ALL"], - ["--security-opt", "no-new-privileges"], - ] as const, - )("uses an offline read-only least-privilege Docker boundary [case %#]", (option, value) => { - const args = secureDockerRunArgs("security-e2e", "candidate:local"); - - const optionIndex = args.indexOf(option); - expect(args.slice(optionIndex, optionIndex + 2)).toEqual([option, value]); - - expect(args).not.toContain("host"); - expect(args).toContain("--read-only"); - expect(args.join(" ")).not.toContain("docker.sock"); - expect(args).not.toContain("--mount"); - }); + test.each([ + ["--network", "none"], + ["--cap-drop", "ALL"], + ["--security-opt", "no-new-privileges"], + ] as const)( + "uses an offline read-only least-privilege Docker boundary [case %#]", + (option, value) => { + const args = secureDockerRunArgs("security-e2e", "candidate:local"); + + const optionIndex = args.indexOf(option); + expect(args.slice(optionIndex, optionIndex + 2)).toEqual([option, value]); + + expect(args).not.toContain("host"); + expect(args).toContain("--read-only"); + expect(args.join(" ")).not.toContain("docker.sock"); + expect(args).not.toContain("--mount"); + }, + ); test("rejects vulnerable or incomplete installed dependency evidence", () => { const good = exactEvidence();