Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 17 additions & 13 deletions .github/workflows/managed-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -583,21 +583,24 @@ jobs:
slack: ["@openclaw/slack", "2026.7.1"],
whatsapp: ["@openclaw/whatsapp", "2026.7.1"],
msteams: ["@openclaw/msteams", "2026.7.1"],
googlechat: ["@openclaw/googlechat", "2026.7.1"],
};
const projectsRoot = "/sandbox/.openclaw/npm/projects";
const installedPackages = fs
.readdirSync(projectsRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.flatMap((entry) => {
const manifestPath = path.join(projectsRoot, entry.name, "package.json");
if (!fs.existsSync(manifestPath) || !fs.lstatSync(manifestPath).isFile()) return [];
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
return [{ name: manifest.name, version: manifest.version }];
});
for (const [id, [name, version]] of Object.entries(packages)) {
const install = config.plugins?.installs?.[id];
if (
!install ||
typeof install.installPath !== "string" ||
config.plugins?.entries?.[id]?.enabled !== false
) {
throw new Error(`managed OpenClaw plugin ${id} is missing or active`);
}
const manifest = JSON.parse(
fs.readFileSync(path.join(install.installPath, "package.json"), "utf8"),
const matches = installedPackages.filter(
(installed) => installed.name === name && installed.version === version,
);
if (manifest.name !== name || manifest.version !== version) {
throw new Error(`managed OpenClaw plugin ${id} has an unexpected package identity`);
if (matches.length !== 1 || config.plugins?.entries?.[id]?.enabled !== false) {
throw new Error(`managed OpenClaw plugin ${id} is missing, duplicated, or active`);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
for (const id of ["telegram", "tavily"]) {
Expand All @@ -612,6 +615,7 @@ jobs:
"slack",
"whatsapp",
"msteams",
"googlechat",
]) {
if (config.channels?.[id]?.enabled !== false) {
throw new Error(`managed OpenClaw channel ${id} is not explicitly disabled`);
Expand All @@ -634,7 +638,7 @@ jobs:
raise SystemExit("managed Hermes optional platform is not explicitly disabled")
if metadata.version("microsoft-teams-apps") != "2.0.13.4":
raise SystemExit("managed Hermes microsoft-teams-apps version is wrong")
if metadata.version("aiohttp") != "3.14.1":
if metadata.version("aiohttp") != "3.14.3":
raise SystemExit("managed Hermes aiohttp version is wrong")
VALIDATE_HERMES_UNION
;;
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/platform-vitest-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ jobs:
run: npm ci --ignore-scripts

- name: Build CLI
run: npm run build:cli
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
test "$(git rev-parse --verify HEAD)" = "$GITHUB_SHA"
npm run build:cli

- name: Run Ubuntu 26.04 compatibility contracts
run: |
Expand Down
2 changes: 1 addition & 1 deletion agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ RUN mkdir -p /sandbox/.nemoclaw/blueprints/0.1.0 \
RUN HERMES_HOME=/sandbox/.hermes /usr/local/bin/hermes doctor --fix \
&& node --experimental-strip-types /opt/nemoclaw-hermes-config/generate-config.ts \
&& if [ "$NEMOCLAW_MANAGED_IMAGE_CAPABILITY_UNION" = "1" ]; then \
/opt/hermes/.venv/bin/python -I -c 'import importlib.metadata as m, pathlib, yaml; config=yaml.safe_load(pathlib.Path("/sandbox/.hermes/config.yaml").read_text()); neutral={name: value for name, value in config["platforms"].items() if name != "api_server"}; assert len(neutral) == 30; assert all(value == {"enabled": False} for value in neutral.values()); assert m.version("microsoft-teams-apps") == "2.0.13.4"; assert m.version("aiohttp") == "3.14.1"'; \
/opt/hermes/.venv/bin/python -I -c 'import importlib.metadata as m, pathlib, yaml; config=yaml.safe_load(pathlib.Path("/sandbox/.hermes/config.yaml").read_text()); neutral={name: value for name, value in config["platforms"].items() if name != "api_server"}; assert len(neutral) == 30; assert all(value == {"enabled": False} for value in neutral.values()); assert m.version("microsoft-teams-apps") == "2.0.13.4"; assert m.version("aiohttp") == "3.14.3"'; \
GOOGLE_CHAT_PROJECT_ID=nemoclaw-hostile \
GOOGLE_CHAT_SUBSCRIPTION_NAME=projects/nemoclaw-hostile/subscriptions/nemoclaw-hostile \
GOOGLE_CHAT_SERVICE_ACCOUNT_JSON='{"type":"service_account","project_id":"nemoclaw-hostile"}' \
Expand Down
3 changes: 2 additions & 1 deletion scripts/checks/verify-managed-image-publication-evidence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ fi

workload_sha256="${workload_digest#sha256:}"
base_sha256="${base_reference##*@sha256:}"
base_dependency_uri="pkg:docker/${base_reference}?platform=${platform//\//%2F}"
base_repository="${base_reference%@sha256:*}"
base_dependency_uri="pkg:docker/${base_repository}?digest=sha256:${base_sha256}&platform=${platform//\//%2F}"
source_url="https://github.com/${repository}"
builder_id="${source_url}/actions/runs/${run_id}/attempts/${run_attempt}"
if ! jq -e \
Expand Down
2 changes: 1 addition & 1 deletion test/hermes-final-image-layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ describe("Hermes final image layout", () => {
);
expect(doctorLayer).toContain('if [ "$NEMOCLAW_MANAGED_IMAGE_CAPABILITY_UNION" = "1" ]; then');
expect(doctorLayer).toContain('assert m.version("microsoft-teams-apps") == "2.0.13.4"');
expect(doctorLayer).toContain('assert m.version("aiohttp") == "3.14.1"');
expect(doctorLayer).toContain('assert m.version("aiohttp") == "3.14.3"');
expect(doctorLayer).toContain("assert len(neutral) == 30");
expect(doctorLayer).toContain("neutral-platform-inertness");
expect(doctorLayer).toContain("GOOGLE_CHAT_SERVICE_ACCOUNT_JSON");
Expand Down
4 changes: 2 additions & 2 deletions test/managed-image-capability-union.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe("managed-image capability union", () => {
);
expect(collectManagedImageHermesUvPackages()).toEqual([
"microsoft-teams-apps==2.0.13.4",
"aiohttp==3.14.1",
"aiohttp==3.14.3",
]);
});

Expand Down Expand Up @@ -149,7 +149,7 @@ describe("managed-image capability union", () => {
NEMOCLAW_MANAGED_IMAGE_CAPABILITY_UNION: "1",
});
expect(fs.readFileSync(trace, "utf8").trim()).toBe(
"pip install --python /opt/hermes/.venv/bin/python --no-cache -- microsoft-teams-apps==2.0.13.4 aiohttp==3.14.1",
"pip install --python /opt/hermes/.venv/bin/python --no-cache -- microsoft-teams-apps==2.0.13.4 aiohttp==3.14.3",
);
} finally {
fs.rmSync(temporaryRoot, { force: true, recursive: true });
Expand Down
8 changes: 6 additions & 2 deletions test/managed-image-publication-evidence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const runAttempt = "1";
const cohort = `ghrun-${runId}-${runAttempt}`;
const baseReference = `ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:${"5".repeat(64)}`;
const image = "ghcr.io/nvidia/nemoclaw/openclaw-sandbox";
const buildkitBaseDependencyUri = (targetPlatform: string) => {
const [repositoryName, digest] = baseReference.split("@sha256:");
return `pkg:docker/${repositoryName}?digest=sha256:${digest}&platform=${targetPlatform.replaceAll("/", "%2F")}`;
};

type FixtureOptions = {
attestationWorkloadDigest?: string;
Expand Down Expand Up @@ -117,7 +121,7 @@ function runEvidence(options: FixtureOptions = {}, digestOverride?: string) {
internalParameters: { buildConfig: {} },
resolvedDependencies: [
{
uri: options.slsaDependencyUri ?? `pkg:docker/${baseReference}?platform=linux%2Famd64`,
uri: options.slsaDependencyUri ?? buildkitBaseDependencyUri(platform),
digest: { sha256: baseReference.split("@sha256:")[1] },
},
],
Expand Down Expand Up @@ -488,7 +492,7 @@ describe("managed image publication evidence verifier", () => {
[
"base dependency platform",
{
slsaDependencyUri: `pkg:docker/${baseReference}?platform=linux%2Farm64`,
slsaDependencyUri: buildkitBaseDependencyUri("linux/arm64"),
},
],
[
Expand Down
24 changes: 24 additions & 0 deletions test/managed-image-publication-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ function publicationBoundaryErrors(baseWorkflow: Workflow, managedWorkflow: Work
"@openclaw/slack",
"@openclaw/whatsapp",
"@openclaw/msteams",
"@openclaw/googlechat",
"/sandbox/.openclaw/npm/projects",
"lstatSync(manifestPath).isFile()",
"matches.length !== 1",
"microsoft-teams-apps",
"config.plugins?.entries?.[id]?.enabled !== false",
'config["platforms"].get(name) != {"enabled": False}',
Expand Down Expand Up @@ -251,6 +255,26 @@ describe("complete managed-image publication workflow", () => {
);

expect(publicationBoundaryErrors(baseWorkflow, managedWorkflow)).toEqual([]);
expect(JSON.stringify(managedWorkflow)).not.toContain("config.plugins?.installs?.[id]");
const validationRun =
step(managedPublisher(managedWorkflow), "Validate exact managed image before promotion")
.run ?? "";
const channelGuardEnd = validationRun.indexOf("managed OpenClaw channel");
const channelGuardStart = validationRun.lastIndexOf("for (const id of [", channelGuardEnd);
expect(channelGuardStart).toBeGreaterThan(-1);
expect(validationRun.slice(channelGuardStart, channelGuardEnd)).toContain('"googlechat",');
const weakenedWorkflow = structuredClone(managedWorkflow);
const weakenedValidation = step(
managedPublisher(weakenedWorkflow),
"Validate exact managed image before promotion",
);
weakenedValidation.run = weakenedValidation.run?.replace(
" || !fs.lstatSync(manifestPath).isFile()",
"",
);
expect(publicationBoundaryErrors(baseWorkflow, weakenedWorkflow)).toContain(
"exact managed image validation is missing lstatSync(manifestPath).isFile()",
);
expect(publisher).toMatchObject({
needs: ["build-and-push-hermes", "build-and-push-dcode", "build-and-push-openclaw"],
permissions: {
Expand Down
6 changes: 6 additions & 0 deletions test/platform-vitest-main-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ function step(jobName: string, name: string): WorkflowStep {
}

describe("platform Vitest main workflow", () => {
it("marks the container checkout safe before generating build identity", () => {
const run = step("ubuntu-2604-contract", "Build CLI").run ?? "";
expect(run).toContain('git config --global --add safe.directory "$GITHUB_WORKSPACE"');
expect(run).toContain('test "$(git rev-parse --verify HEAD)" = "$GITHUB_SHA"');
expect(run.indexOf("safe.directory")).toBeLessThan(run.indexOf("npm run build:cli"));
});
// source-shape-contract: security -- The trusted helper installs only checksum-verified official Node.js archives
it("pins and verifies the Node.js archive in the trusted WSL helper", () => {
const installSteps = [{ run: wslHelperSource }];
Expand Down
3 changes: 2 additions & 1 deletion test/snapshot-gateway-guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ function makeVmRestoreToEnv(
const cloneReadyMarker = path.join(home, "clone-1-ready");
const cloneRunningMarker = path.join(home, "clone-1-running");
const gatewayLifecycleLog = path.join(home, "gateway-lifecycle.log");
const dashboardBind = process.env.WSL_DISTRO_NAME ? "0.0.0.0" : "127.0.0.1";
writeExecutable(path.join(localBin, "openshell"), [
'case "$1 $2" in',
' "gateway info") printf "Gateway Info\\n\\nGateway: nemoclaw\\nGateway endpoint: https://127.0.0.1:8080/\\n"; exit 0 ;;',
Expand All @@ -208,7 +209,7 @@ function makeVmRestoreToEnv(
' printf "NEMOCLAW_DCODE_PROBE=no-runtime\\n"; exit 0 ;;',
' "sandbox ssh-config") printf "Host openshell-alpha\\n HostName 127.0.0.1\\n User sandbox\\n"; exit 0 ;;',
` "sandbox create") touch ${JSON.stringify(cloneReadyMarker)} ${JSON.stringify(cloneRunningMarker)}; printf "created clone-1\\n"; exit 0 ;;`,
` "forward list") printf "SANDBOX BIND PORT PID STATUS\\nclone-1 127.0.0.1 ${String(dashboardPort)} 4242 running\\n"; exit 0 ;;`,
` "forward list") printf "SANDBOX BIND PORT PID STATUS\\nclone-1 ${dashboardBind} ${String(dashboardPort)} 4242 running\\n"; exit 0 ;;`,
' "forward stop") exit 1 ;;',
"esac",
'if [ "$1" = "status" ]; then exit 0; fi',
Expand Down
Loading