Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
73f88fe
fix(security): update OpenClaw dependency graph
jyaunches Jul 21, 2026
a3ca9a0
fix(security): normalize remediated package integrity
jyaunches Jul 21, 2026
9317a2b
fix(security): complete OpenClaw 2026.7.1 validation
jyaunches Jul 21, 2026
c308bcf
fix(security): support OpenClaw 2026.7.1 startup
ericksoa Jul 21, 2026
098e617
test: satisfy conditional growth policy
ericksoa Jul 21, 2026
f4001bd
fix(security): close OpenClaw 2026.7.1 validation gaps
jyaunches Jul 21, 2026
1359aa7
fix(state): fail closed on sqlite backup errors
jyaunches Jul 21, 2026
0a182f7
test: satisfy OpenClaw repair conditional policy
jyaunches Jul 21, 2026
457fe5f
fix(openclaw): restore pairing and rebuild recovery
jyaunches Jul 21, 2026
097b4f1
fix(security): preserve private OpenClaw stores
jyaunches Jul 21, 2026
f4b7f28
fix(openclaw): preserve state across 2026.7.1 upgrade
ericksoa Jul 21, 2026
2ac4fca
test: satisfy conditional growth policy
ericksoa Jul 21, 2026
94026ba
style(test): apply repository formatting
ericksoa Jul 21, 2026
b97aa2f
test(e2e): honor redacted auth records
ericksoa Jul 21, 2026
0341921
test(e2e): use real legacy OpenClaw state
ericksoa Jul 21, 2026
26a9dbc
test: include installed-base upgrade signal shard
ericksoa Jul 21, 2026
399d796
test(e2e): materialize legacy OpenClaw database
ericksoa Jul 21, 2026
e1ad3a0
test(e2e): assert durable OpenClaw state only
ericksoa Jul 21, 2026
8677985
test: prove installed credential reuse on upgrade
ericksoa Jul 21, 2026
7bcac6d
Merge main into OpenClaw 2026.7.1 upgrade
ericksoa Jul 21, 2026
1632c16
Merge latest main into OpenClaw 2026.7.1 upgrade
ericksoa Jul 21, 2026
7d44971
Merge current main into OpenClaw 2026.7.1 upgrade
ericksoa Jul 21, 2026
aab8d64
chore(ci): refresh gate after runner cancellation
ericksoa Jul 21, 2026
ad4435a
fix(security): remediate mcporter Hono advisory
ericksoa Jul 21, 2026
8aea05d
test(e2e): keep upgrade assertions linear
ericksoa Jul 21, 2026
18045b4
fix(security): remediate diagnostics Jaeger advisory
ericksoa Jul 21, 2026
9e8be5e
Merge remote-tracking branch 'refs/remotes/live/main' into codex/fix-…
ericksoa Jul 21, 2026
4c655cf
test(security): verify Jaeger header remediation
apurvvkumaria Jul 21, 2026
b18b303
merge: refresh Jaeger regression on current main
senthilr-nv Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ jobs:
NEMOCLAW_REAL_OPENCLAW_DIST_HARNESS: "1"
run: npx vitest run --project integration test/openclaw-real-patched-dist-harness.test.ts --silent=false --reporter=default

- name: Verify reviewed Jaeger header handling
env:
NEMOCLAW_REAL_OPENCLAW_JAEGER_HARNESS: "1"
run: npx vitest run --project integration test/openclaw-diagnostics-jaeger-runtime.test.ts --silent=false --reporter=default

- name: Audit managed OpenClaw security finding suppressions
env:
NEMOCLAW_REAL_OPENCLAW_AUDIT_HARNESS: "1"
Expand Down
15 changes: 12 additions & 3 deletions docs/security/openclaw-2026.7.1-dependency-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,17 @@ consumer on the plugin's reviewed `2.8.0` graph while satisfying Jaeger's exact
contains diagnostics `2026.7.1`, SDK Node `0.219.0`, Jaeger `2.8.0`, and no
preexisting nested Core. The resulting package tree is pinned to
`sha512-2qyDTRPqNs97jo/pAWWfxAkVZyCXYqui/IjrGf4eEfYop1eGN8qBMJ/Kp/bJ/V18RNnYpMxHi5ECFelekVxcAQ==`.
Malformed Jaeger trace and baggage headers no longer throw, while a valid
Jaeger header still produces the expected trace and span context in the real
remediated package graph.
The trusted main-only
`test/openclaw-diagnostics-jaeger-runtime.test.ts` harness runs with
`NEMOCLAW_REAL_OPENCLAW_JAEGER_HARNESS=1`.
It materializes the exact reviewed diagnostics archive, applies the production
remediation, and installs that local archive with lifecycle scripts disabled.
The child-process probe confirms these results:

- malformed percent-encoded `uber-trace-id` and `uberctx-*` headers do not throw;
- malformed baggage is ignored;
- valid `uberctx-test` baggage remains available;
- a valid Jaeger header produces the expected trace and span context.

`scripts/lib/openclaw-npm-remediation.mts` verifies the original plugin and
replacement package identities before it writes the archive. It rejects an
Expand All @@ -133,6 +141,7 @@ The Axios remediation is limited to `@openclaw/slack@2026.7.1` and
`@openclaw/diagnostics-otel@2026.7.1`. Remove each branch when a reviewed stable
OpenClaw plugin release bundles the corresponding patched graph and passes the
repository audit.
Issue #7337 tracks removal of the Jaeger branch and its exact replacement pins.

The reviewed installer verifies each registry identity and downloaded tarball
integrity. `scripts/lib/reviewed-npm-archive.mts` uses `npm pack --json` and
Expand Down
1 change: 1 addition & 0 deletions scripts/lib/openclaw-npm-remediation.mts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ const REMEDIATIONS: Readonly<Record<string, Remediation>> = Object.freeze({
kind: "axios",
version: "2026.6.10",
},
// #7337: remove this branch only after a reviewed diagnostics release ships a safe SDK graph.
"@openclaw/diagnostics-otel@2026.7.1": {
expectedPatchedTreeIntegrity:
"sha512-2qyDTRPqNs97jo/pAWWfxAkVZyCXYqui/IjrGf4eEfYop1eGN8qBMJ/Kp/bJ/V18RNnYpMxHi5ECFelekVxcAQ==",
Expand Down
8 changes: 8 additions & 0 deletions test/openclaw-dependency-review.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ describe("OpenClaw 2026.6.10 dependency review contract", () => {
);
expect(review).toContain("SDK Node `0.219.0`");
expect(review).toContain("preexisting nested Core");
expect(review).toContain("test/openclaw-diagnostics-jaeger-runtime.test.ts");
expect(review).toContain("NEMOCLAW_REAL_OPENCLAW_JAEGER_HARNESS=1");
});

it("records the active mcporter advisory remediations", () => {
Expand Down Expand Up @@ -717,6 +719,12 @@ grep -Fq -- '--phase post-agent-install' Dockerfile
expect(requiredStep(mainJob, "Audit the real patched OpenClaw distribution").run).toContain(
"test/openclaw-real-patched-dist-harness.test.ts",
);
expect(requiredStep(mainJob, "Verify reviewed Jaeger header handling").env).toEqual({
NEMOCLAW_REAL_OPENCLAW_JAEGER_HARNESS: "1",
});
expect(requiredStep(mainJob, "Verify reviewed Jaeger header handling").run).toContain(
"test/openclaw-diagnostics-jaeger-runtime.test.ts",
);
expect(
requiredStep(mainJob, "Audit managed OpenClaw security finding suppressions").env,
).toEqual({ NEMOCLAW_REAL_OPENCLAW_AUDIT_HARNESS: "1" });
Expand Down
222 changes: 222 additions & 0 deletions test/openclaw-diagnostics-jaeger-runtime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { remediateReviewedOpenClawPluginArchive } from "../scripts/lib/openclaw-npm-remediation.mts";
import { packReviewedNpmArchive } from "../scripts/lib/reviewed-npm-archive.mts";

const REPO_ROOT = path.join(import.meta.dirname, "..");
const RUNTIME_HARNESS_ENV = "NEMOCLAW_REAL_OPENCLAW_JAEGER_HARNESS";
const RUNTIME_TIMEOUT_MS = 180_000;

interface ReviewedPackage {
integrity: string;
label: string;
packageSpec: string;
tarballUrl: string;
}

const JAEGER_RUNTIME_PROBE = String.raw`
import assert from "node:assert/strict";
import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";

const diagnosticsRoot = process.argv[1];
assert.ok(path.isAbsolute(diagnosticsRoot), "diagnostics root must be absolute");

const diagnosticsRequire = createRequire(path.join(diagnosticsRoot, "package.json"));
const sdkEntry = diagnosticsRequire.resolve("@opentelemetry/sdk-node");
const sdkRequire = createRequire(sdkEntry);
const jaegerEntry = sdkRequire.resolve("@opentelemetry/propagator-jaeger");
const jaegerRequire = createRequire(jaegerEntry);
const { JaegerPropagator } = sdkRequire("@opentelemetry/propagator-jaeger");
const { ROOT_CONTEXT, propagation, trace } = jaegerRequire("@opentelemetry/api");

let packageRoot = path.dirname(jaegerEntry);
let packageJson;
while (packageRoot !== path.dirname(packageRoot)) {
const candidate = path.join(packageRoot, "package.json");
if (fs.existsSync(candidate)) {
const parsed = JSON.parse(fs.readFileSync(candidate, "utf-8"));
if (parsed.name === "@opentelemetry/propagator-jaeger") {
packageJson = parsed;
break;
}
}
packageRoot = path.dirname(packageRoot);
}
assert.ok(packageJson, "could not resolve the physical Jaeger package identity");
const versionMatch = /^(\d+)\.(\d+)\.(\d+)$/.exec(packageJson.version);
assert.ok(versionMatch, "Jaeger package must use a stable semantic version");
const version = versionMatch.slice(1).map(Number);
assert.ok(
version[0] > 2 || (version[0] === 2 && (version[1] > 9 || (version[1] === 9 && version[2] >= 0))),
"Jaeger package must be at least 2.9.0",
);

const propagator = new JaegerPropagator();
const getter = {
get(carrier, key) {
return carrier[key];
},
keys(carrier) {
return Object.keys(carrier);
},
};

for (const malformed of ["%ZZ", "abc%G:123:0:01", "%"]) {
const context = propagator.extract(
ROOT_CONTEXT,
{ "uber-trace-id": malformed },
getter,
);
assert.equal(
trace.getSpanContext(context),
undefined,
"malformed uber-trace-id must be ignored: " + malformed,
);
}

const baggageFromMalformedTrace = propagator.extract(
ROOT_CONTEXT,
{ "uber-trace-id": "%ZZ", "uberctx-test": "value" },
getter,
);
assert.equal(trace.getSpanContext(baggageFromMalformedTrace), undefined);
assert.equal(propagation.getBaggage(baggageFromMalformedTrace)?.getEntry("test")?.value, "value");

const validTraceWithMalformedBaggage = propagator.extract(
ROOT_CONTEXT,
{
"uber-trace-id": "d4cda95b652f4a1592b449d5929fda1b:6e0c63257de34c92:0:01",
"uberctx-bad": "%ZZ",
"uberctx-test": "value",
},
getter,
);
assert.deepEqual(trace.getSpanContext(validTraceWithMalformedBaggage), {
traceId: "d4cda95b652f4a1592b449d5929fda1b",
spanId: "6e0c63257de34c92",
traceFlags: 1,
isRemote: true,
});
const baggage = propagation.getBaggage(validTraceWithMalformedBaggage);
assert.equal(baggage?.getEntry("bad"), undefined);
assert.equal(baggage?.getEntry("test")?.value, "value");

console.log(JSON.stringify({
jaegerVersion: packageJson.version,
malformedTraceHeaders: 3,
malformedBaggageIgnored: true,
validTracePreserved: true,
}));
`;

function reviewedDiagnosticsPackage(): ReviewedPackage {
const config = JSON.parse(
fs.readFileSync(path.join(REPO_ROOT, "ci", "reviewed-npm-audit.json"), "utf-8"),
) as { archivePackages: ReviewedPackage[] };
const reviewed = config.archivePackages.find(({ packageSpec }) =>
packageSpec.startsWith("@openclaw/diagnostics-otel@"),
);
assert.ok(reviewed, "reviewed npm audit config must include OpenClaw diagnostics");
return reviewed;
}

function requireSpawnSuccess(
result: ReturnType<typeof spawnSync>,
label: string,
): asserts result is ReturnType<typeof spawnSync> & { status: 0 } {
const detail = result.error?.message || result.stderr || result.stdout || "empty output";
assert.equal(result.error, undefined, `${label} failed: ${detail}`);
assert.equal(result.status, 0, `${label} failed: ${detail}`);
}

describe.skipIf(process.env[RUNTIME_HARNESS_ENV] !== "1")(
"OpenClaw diagnostics Jaeger runtime",
() => {
it(
"ignores malformed trace and baggage headers in the reviewed production graph (#7337)",
() => {
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-jaeger-runtime-"));
try {
const home = path.join(workspace, "home");
const cache = path.join(workspace, "npm-cache");
fs.mkdirSync(home, { recursive: true, mode: 0o700 });
fs.mkdirSync(cache, { recursive: true, mode: 0o700 });
const env: NodeJS.ProcessEnv = {
...process.env,
HOME: home,
NPM_CONFIG_AUDIT: "false",
NPM_CONFIG_CACHE: cache,
NPM_CONFIG_FUND: "false",
NPM_CONFIG_IGNORE_SCRIPTS: "true",
NPM_CONFIG_UPDATE_NOTIFIER: "false",
NPM_CONFIG_USERCONFIG: "/dev/null",
};
const reviewed = reviewedDiagnosticsPackage();
const archive = packReviewedNpmArchive({
env,
expectedIntegrity: reviewed.integrity,
label: reviewed.label,
packageSpec: reviewed.packageSpec,
tarballUrl: reviewed.tarballUrl,
tempDirectory: workspace,
});
const productionArchive = remediateReviewedOpenClawPluginArchive({
archivePath: archive.archivePath,
env,
packageSpec: reviewed.packageSpec,
workingDirectory: archive.rootDirectory,
});
const runtime = path.join(workspace, "runtime");
const install = spawnSync(
"npm",
[
"install",
"--prefix",
runtime,
"--ignore-scripts",
"--no-audit",
"--no-fund",
productionArchive.archivePath,
],
{ encoding: "utf-8", env, timeout: RUNTIME_TIMEOUT_MS },
);
requireSpawnSuccess(install, "install reviewed diagnostics archive");

const diagnosticsRoot = path.join(
runtime,
"node_modules",
"@openclaw",
"diagnostics-otel",
);
const probe = spawnSync(
process.execPath,
["--input-type=module", "--eval", JAEGER_RUNTIME_PROBE, diagnosticsRoot],
{
encoding: "utf-8",
env: { HOME: home, NODE_OPTIONS: "", PATH: process.env.PATH },
timeout: RUNTIME_TIMEOUT_MS,
},
);
requireSpawnSuccess(probe, "execute reviewed Jaeger runtime probe");
expect(JSON.parse(probe.stdout)).toMatchObject({
malformedBaggageIgnored: true,
malformedTraceHeaders: 3,
validTracePreserved: true,
});
} finally {
fs.rmSync(workspace, { recursive: true, force: true });
}
},
RUNTIME_TIMEOUT_MS,
);
},
);
Loading