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
12 changes: 12 additions & 0 deletions scripts/checks/extract-installer-pins.mts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [
runtimeTemplateSha256: [
"c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068",
"abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c",
// Allow the gateway-preparation runtime template; image and manifest digest stay unchanged.
"b6e467dd20e1bcb27d26d40ccc56eaef2a0c1321537108884e0941661a840373",
],
},
pinLayout: LEGACY_OPENSHELL_PIN_LAYOUT,
Expand Down Expand Up @@ -315,6 +317,8 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [
runtimeTemplateSha256: [
"c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068",
"abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c",
// Allow the gateway-preparation runtime template; image and manifest digest stay unchanged.
"b6e467dd20e1bcb27d26d40ccc56eaef2a0c1321537108884e0941661a840373",
],
},
pinLayout: LEGACY_OPENSHELL_PIN_LAYOUT,
Expand Down Expand Up @@ -366,6 +370,8 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [
runtimeTemplateSha256: [
"c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068",
"abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c",
// Allow the gateway-preparation runtime template; image and manifest digest stay unchanged.
"b6e467dd20e1bcb27d26d40ccc56eaef2a0c1321537108884e0941661a840373",
],
},
pinLayout: LEGACY_OPENSHELL_PIN_LAYOUT,
Expand Down Expand Up @@ -414,6 +420,8 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [
runtimeTemplateSha256: [
"c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068",
"abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c",
// Allow the gateway-preparation runtime template; image and manifest digest stay unchanged.
"b6e467dd20e1bcb27d26d40ccc56eaef2a0c1321537108884e0941661a840373",
],
},
pinLayout: LEGACY_OPENSHELL_PIN_LAYOUT,
Expand Down Expand Up @@ -482,6 +490,8 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [
runtimeTemplateSha256: [
"c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068",
"abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c",
// Allow the gateway-preparation runtime template; image and manifest digest stay unchanged.
"b6e467dd20e1bcb27d26d40ccc56eaef2a0c1321537108884e0941661a840373",
],
},
pinLayout: LEGACY_OPENSHELL_PIN_LAYOUT,
Expand Down Expand Up @@ -543,6 +553,8 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [
runtimeTemplateSha256: [
"c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068",
"abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c",
// Allow the gateway-preparation runtime template; image and manifest digest stay unchanged.
"b6e467dd20e1bcb27d26d40ccc56eaef2a0c1321537108884e0941661a840373",
// Exact #11251 template after the reviewed 0.0.116-only recovery gate.
"593ced09573f8cea5d2323b6d388ebb5d30f6da241d4f511e5364a3057887911",
// Exact #11251 template after stable supervisor override binding.
Expand Down
58 changes: 58 additions & 0 deletions test/helpers/openshell-installer-template.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import assert from "node:assert/strict";

const MACOS_METHOD_START = `MACOS_INSTALL_METHOD="\${_NEMOCLAW_OPENSHELL_INSTALL_METHOD:-auto}"`;
const MACOS_METHOD_END = "esac\n";

Expand All @@ -16,3 +18,59 @@ export function installerReleaseTemplate(source: string, version: string): strin
"test/",
);
}

export function addV00106OperationalTrust(source: string): string {
const withIdentityCheck = source.replace(
"pinned_sandbox_build_version() {",
`is_pinned_openshell_v00106_linux_x86_64_install() {
local openshell_bin="$1"
local gateway_bin="$2"
local sandbox_bin="$3"
local openshell_sha gateway_sha sandbox_sha

[ "$OS" = "Linux" ] && [ "$ARCH_LABEL" = "x86_64" ] || return 1
openshell_sha="$(file_sha256 "$openshell_bin")" || return 1
gateway_sha="$(file_sha256 "$gateway_bin")" || return 1
sandbox_sha="$(file_sha256 "$sandbox_bin")" || return 1
[ "$openshell_sha" = "98ecf95113fea999e94a928043e57b04cf58a45a1b66ae8bffc73d1bc8bb1d59" ] \\
&& [ "$gateway_sha" = "e6cde8a54568aa1926ff6584ffd6984314c68dad64d2722509618a74094c622c" ] \\
&& [ "$sandbox_sha" = "019301ec8618abbed8135e8d39dde7bea47e5e92813bbc17768550de34db59f8" ]
}

pinned_sandbox_build_version() {`,
);
const capabilityMarker = " # OpenShell #1865 has no authoritative CLI/RPC capability query yet.";
const result = withIdentityCheck.replace(
capabilityMarker,
` # The v0.0.106 release binaries are stripped and no longer retain every
# source-level capability marker used by the development-build fallback
# below. Accept only the reviewed executable byte identities as the stable
# release capability proof; arbitrary binaries that merely report 0.0.106
# must still pass the fail-closed marker checks.
if is_pinned_openshell_v00106_linux_x86_64_install \\
"$openshell_bin" "$gateway_bin" "$sandbox_bin"; then
return 0
fi

${capabilityMarker}`,
);
assert.notEqual(withIdentityCheck, source, "v0.0.106 executable identity helper");
assert.notEqual(result, withIdentityCheck, "v0.0.106 capability proof");
return result;
}

export function removeV00106OperationalTrust(source: string): string {
const identityStart = source.indexOf("is_pinned_openshell_v00106_linux_x86_64_install() {");
const sandboxStart = source.indexOf("pinned_sandbox_build_version() {", identityStart);
assert.ok(![identityStart, sandboxStart].includes(-1), "v0.0.106 helper boundaries");
const withoutIdentity = `${source.slice(0, identityStart)}${source.slice(sandboxStart)}`;
const capabilityStart = withoutIdentity.indexOf(
" # The v0.0.106 release binaries are stripped and no longer retain every",
);
const fallbackStart = withoutIdentity.indexOf(
" # OpenShell #1865 has no authoritative CLI/RPC capability query yet.",
capabilityStart,
);
assert.ok(![capabilityStart, fallbackStart].includes(-1), "v0.0.106 proof boundaries");
return `${withoutIdentity.slice(0, capabilityStart)}${withoutIdentity.slice(fallbackStart)}`;
}
55 changes: 55 additions & 0 deletions test/helpers/prepared-gateway-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import assert from "node:assert/strict";

export function selectPreparedGatewayRuntime(source: string): string {
return [
[
" getDockerDriverGatewayPid(): number | null;",
` getDockerDriverGatewayPreparation(
versionOutput?: string | null,
platform?: NodeJS.Platform,
): import("./docker-driver-gateway-env").DockerDriverGatewayPreparation;
getDockerDriverGatewayPid(): number | null;`,
],
[
` function getDockerDriverGatewayEnv(
versionOutput: string | null = null,
platform: NodeJS.Platform = process.platform,
): Record<string, string> {`,
` function getDockerDriverGatewayPreparation(
versionOutput: string | null = null,
platform: NodeJS.Platform = process.platform,
): import("./docker-driver-gateway-env").DockerDriverGatewayPreparation {`,
],
[
"const gatewayEnv = dockerDriverGatewayEnv.buildDockerDriverGatewayEnv({",
"const preparation = dockerDriverGatewayEnv.prepareDockerDriverGatewayEnv({",
],
[
` if (gatewayEnv.OPENSHELL_LOCAL_TLS_DIR) {
process.env.OPENSHELL_LOCAL_TLS_DIR = gatewayEnv.OPENSHELL_LOCAL_TLS_DIR;
}
return gatewayEnv;`,
` if (preparation.gatewayEnv.OPENSHELL_LOCAL_TLS_DIR) {
process.env.OPENSHELL_LOCAL_TLS_DIR = preparation.gatewayEnv.OPENSHELL_LOCAL_TLS_DIR;
}
return preparation;
}

function getDockerDriverGatewayEnv(
versionOutput: string | null = null,
platform: NodeJS.Platform = process.platform,
): Record<string, string> {
return getDockerDriverGatewayPreparation(versionOutput, platform).gatewayEnv;`,
],
[
" getDockerDriverGatewayEnv,",
" getDockerDriverGatewayEnv,\n getDockerDriverGatewayPreparation,",
],
].reduce((result, [expected, replacement]) => {
assert.ok(result.includes(expected), `preparation fixture must contain ${expected}`);
return result.replaceAll(expected, replacement);
}, source);
}
99 changes: 42 additions & 57 deletions test/install/installer-hash-check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ import {
V00116_SANDBOX_BUILD_DIGESTS,
v00116Pins,
} from "../helpers/openshell-release-fixtures";
import { installerReleaseTemplate } from "../helpers/openshell-installer-template";
import {
addV00106OperationalTrust,
installerReleaseTemplate,
removeV00106OperationalTrust,
} from "../helpers/openshell-installer-template";

import { selectPreparedGatewayRuntime } from "../helpers/prepared-gateway-runtime";

const REPO_ROOT = path.join(import.meta.dirname, "../..");
const INSTALLER_TEMPLATE = fs.readFileSync(
Expand Down Expand Up @@ -615,62 +621,6 @@ function replacePinFunction(
return `${source.slice(0, start)}${replacement}${source.slice(next)}`;
}

function addV00106OperationalTrust(source: string): string {
const withIdentityCheck = source.replace(
"pinned_sandbox_build_version() {",
`is_pinned_openshell_v00106_linux_x86_64_install() {
local openshell_bin="$1"
local gateway_bin="$2"
local sandbox_bin="$3"
local openshell_sha gateway_sha sandbox_sha

[ "$OS" = "Linux" ] && [ "$ARCH_LABEL" = "x86_64" ] || return 1
openshell_sha="$(file_sha256 "$openshell_bin")" || return 1
gateway_sha="$(file_sha256 "$gateway_bin")" || return 1
sandbox_sha="$(file_sha256 "$sandbox_bin")" || return 1
[ "$openshell_sha" = "98ecf95113fea999e94a928043e57b04cf58a45a1b66ae8bffc73d1bc8bb1d59" ] \\
&& [ "$gateway_sha" = "e6cde8a54568aa1926ff6584ffd6984314c68dad64d2722509618a74094c622c" ] \\
&& [ "$sandbox_sha" = "019301ec8618abbed8135e8d39dde7bea47e5e92813bbc17768550de34db59f8" ]
}

pinned_sandbox_build_version() {`,
);
const capabilityMarker = " # OpenShell #1865 has no authoritative CLI/RPC capability query yet.";
const result = withIdentityCheck.replace(
capabilityMarker,
` # The v0.0.106 release binaries are stripped and no longer retain every
# source-level capability marker used by the development-build fallback
# below. Accept only the reviewed executable byte identities as the stable
# release capability proof; arbitrary binaries that merely report 0.0.106
# must still pass the fail-closed marker checks.
if is_pinned_openshell_v00106_linux_x86_64_install \\
"$openshell_bin" "$gateway_bin" "$sandbox_bin"; then
return 0
fi

${capabilityMarker}`,
);
expect(withIdentityCheck, "v0.0.106 executable identity helper").not.toBe(source);
expect(result, "v0.0.106 capability proof").not.toBe(withIdentityCheck);
return result;
}

function removeV00106OperationalTrust(source: string): string {
const identityStart = source.indexOf("is_pinned_openshell_v00106_linux_x86_64_install() {");
const sandboxStart = source.indexOf("pinned_sandbox_build_version() {", identityStart);
expect([identityStart, sandboxStart], "v0.0.106 helper boundaries").not.toContain(-1);
const withoutIdentity = `${source.slice(0, identityStart)}${source.slice(sandboxStart)}`;
const capabilityStart = withoutIdentity.indexOf(
" # The v0.0.106 release binaries are stripped and no longer retain every",
);
const fallbackStart = withoutIdentity.indexOf(
" # OpenShell #1865 has no authoritative CLI/RPC capability query yet.",
capabilityStart,
);
expect([capabilityStart, fallbackStart], "v0.0.106 proof boundaries").not.toContain(-1);
return `${withoutIdentity.slice(0, capabilityStart)}${withoutIdentity.slice(fallbackStart)}`;
}

function renderInstallerTemplate(openshellVersion: string, pinFunction: string): string {
const selected = installerReleaseTemplate(INSTALLER_TEMPLATE, openshellVersion)
.replace(/^MIN_VERSION="[0-9]+\.[0-9]+\.[0-9]+"$/m, `MIN_VERSION="${openshellVersion}"`)
Expand Down Expand Up @@ -1117,6 +1067,41 @@ describe("installer hash verification", () => {
},
);

it.each(["0.0.72", "0.0.99", "0.0.101", "0.0.103", "0.0.106", "0.0.116"])(
"accepts the gateway-preparation template with the selected OpenShell %s release (#11212)",
(version) => {
const root = createFixture(version);
const runtimePath = "src/lib/onboard/docker-driver-gateway-runtime.ts";
const candidatePins = fs.readFileSync(path.join(root, runtimePath), "utf8");
const source = fs.readFileSync(path.join(REPO_ROOT, runtimePath), "utf8");
const prepared = selectPreparedGatewayRuntime(source).replace(
/const OPENSHELL_SUPERVISOR_MANIFEST_DIGESTS: Readonly<Record<string, string>> = \{[\s\S]*?\n\};/,
candidatePins.trim(),
);
fs.writeFileSync(path.join(root, runtimePath), prepared);
const result = spawnSync(
"node",
[
"--no-warnings",
path.join(REPO_ROOT, "scripts/checks/extract-installer-pins.mts"),
"--blueprint",
path.join(root, "nemoclaw-blueprint/blueprint.yaml"),
"--installer",
path.join(root, "scripts/install-openshell.sh"),
"--brev-installer",
path.join(root, "scripts/brev-launchable-ci-cpu.sh"),
"--supervisor-runtime",
path.join(root, runtimePath),
"--format",
"tsv",
],
{ encoding: "utf8" },
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain(version);
},
);

it("rejects v0.0.116 pins when the stable selector requests a GNU sandbox (#10790)", () => {
const result = runFixture("stable-gnu-v00116", "0.0.116", true);

Expand Down
19 changes: 19 additions & 0 deletions test/install/installer-supervisor-manifest-trust.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
V00106_SUPERVISOR_MANIFEST_DIGEST,
} from "../helpers/openshell-release-fixtures";

import { selectPreparedGatewayRuntime } from "../helpers/prepared-gateway-runtime";

const REPO_ROOT = path.join(import.meta.dirname, "../..");
const PARSER = path.join(REPO_ROOT, "scripts/checks/extract-installer-pins.mts");
const INSTALLER_TEMPLATE = fs.readFileSync(
Expand Down Expand Up @@ -220,6 +222,23 @@ function runParser(options: RunOptions = {}) {
}

describe("OpenShell supervisor manifest trust", () => {
it("accepts the gateway runtime template that prepares the Docker driver environment (#11212)", () => {
const result = runParser({ transformSupervisor: selectPreparedGatewayRuntime });
expect(result.status, result.stderr).toBe(0);
});

it("rejects a repository mutation of the gateway-preparation runtime template (#11212)", () => {
const result = runParser({
transformSupervisor: (source) =>
selectPreparedGatewayRuntime(source).replace(
"ghcr.io/nvidia/openshell/supervisor@${manifestDigest}",
"registry.invalid/openshell/supervisor@${manifestDigest}",
),
});
expect(result.status).toBe(1);
expect(result.stderr).toContain("supervisor runtime operational template is not base-trusted");
});

it("accepts the selected base-trusted OpenShell 0.0.106 supervisor identity (#6256)", () => {
const result = runParser();

Expand Down
Loading