diff --git a/scripts/check-installer-hash.sh b/scripts/check-installer-hash.sh index 9d66ebca158..70e6207ccb1 100755 --- a/scripts/check-installer-hash.sh +++ b/scripts/check-installer-hash.sh @@ -25,45 +25,10 @@ else fi CHECKER_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Trust-anchor rollout is intentionally two-step. First land a prerequisite PR -# that adds the reviewed manifest, formula, standalone sandbox, and supervisor -# OCI identities while runtime selectors still name the current release. Only -# after that commit is on the target branch may a separate pin PR select the new -# release. Pull-request verification runs this checker and its parser from the -# base SHA, so a pin PR can never authorize its own identities. -readonly -a OPENSHELL_RELEASE_MANIFEST_ALLOWLIST=( - "0.0.72|openshell-checksums-sha256.txt|0049181983eaf925ef9510382f75348229a9511d02e27196107782e7c3259ae1" - "0.0.72|openshell-gateway-checksums-sha256.txt|3c454dc15154b8c700ec820628559ea8964c6e552d9c5f8af78b6ee19cf34547" - "0.0.72|openshell-sandbox-checksums-sha256.txt|d38507501338576437cf3e554df71fefe927dc0d72758f88e260069527ed9ccc" - "0.0.82|openshell-checksums-sha256.txt|74ba77d368744f412b2dd246099b63b38937962807333ded2b6284580a2d014e" - "0.0.82|openshell-gateway-checksums-sha256.txt|c0a369ba2c66bcde3c18ce2753b04ff942d1fe1b5f3e4656de520f6d4b175477" - "0.0.82|openshell-sandbox-checksums-sha256.txt|3300b9856cdbe8e3f9b0f8068bbad93673739c4cfd3212c80dc0675168ee2b8d" - "0.0.85|openshell-checksums-sha256.txt|6554b3f96c04006d661519786d40d17e34c7860b7aac8fd35259ef2aea01567f" - "0.0.85|openshell-gateway-checksums-sha256.txt|cc4f32afed376ebe9b43cccdb4d2a77b2524b57132a6b56bb88d705e02420f86" - "0.0.85|openshell-sandbox-checksums-sha256.txt|b6ac353c933fa4cf9a3ef11d66cce6635f39ecc2e928d9c8ff1783ca797308b3" - "0.0.99|openshell-checksums-sha256.txt|ea3e2c1a583e5ea00332c3b65a18068bd1f9b090f7ff0f5e24b29762cfc3b4c7" - "0.0.99|openshell-gateway-checksums-sha256.txt|7f84f728412548720c8ef51993c58414c4f04598451c282b26ead233185e40c5" - "0.0.99|openshell-sandbox-checksums-sha256.txt|9e67af6bab9f975432a1045fcfea5ab182ab585b17886c8c290c1eb77232b87a" - "0.0.101|openshell-checksums-sha256.txt|9c90869d00b109b5ac1062b1a9808a592c2311d3c0c4926bae44d136b979d8a9" - "0.0.101|openshell-gateway-checksums-sha256.txt|dcb3f1917713bf2a8e8e1803ac42c5e39d9dd41e644136b05def32b077082777" - "0.0.101|openshell-sandbox-checksums-sha256.txt|d16f7d369c54d74d36c7df036565267a960e7ce6fb143012fe9d77f257d6e8b3" - "0.0.103|openshell-checksums-sha256.txt|1a9016cfb9219ad6ea3dc623b3dfd517dbce062cba9484964a8ca9175c7d1c9d" - "0.0.103|openshell-gateway-checksums-sha256.txt|800f8501329b27b79d260f21de088d8aea36de45021eaa3d29d189c433fc04b5" - "0.0.103|openshell-sandbox-checksums-sha256.txt|ab7c77fe40e93b293e4d34e892824ed0cb131e8b973ba2660b155cdd0fa0f604" -) - -# OpenShell's Homebrew formula is a release asset but is not included in any -# published checksum manifest. Keep its reviewed identity tuple in the same -# base-trusted checker so a later pin PR cannot authorize a replaced formula by -# changing its candidate-controlled pin to match the live download. -readonly -a OPENSHELL_RELEASE_FORMULA_ALLOWLIST=( - "0.0.72|openshell.rb|https://github.com/NVIDIA/OpenShell/releases/download/v0.0.72/openshell.rb|4b75a7e3a7630eb8954d73ca828b394d5e0646adbaa4b087b2435329d53b61b3" - "0.0.82|openshell.rb|https://github.com/NVIDIA/OpenShell/releases/download/v0.0.82/openshell.rb|fa54640184e22fa74500ab24f5b4372582616c7e12a1152cb6983bc0738c5a74" - "0.0.85|openshell.rb|https://github.com/NVIDIA/OpenShell/releases/download/v0.0.85/openshell.rb|f53c62777fed23b42427822d231670451ee4358efeb2660c41a7a38919211b23" - "0.0.99|openshell.rb|https://github.com/NVIDIA/OpenShell/releases/download/v0.0.99/openshell.rb|8dd34fc17ee9a30327664a18c9509c8a765cb010de38cda8e22841bddbe92713" - "0.0.101|openshell.rb|https://github.com/NVIDIA/OpenShell/releases/download/v0.0.101/openshell.rb|87fadc7b0c854aa44f71d5b3a206865070117cd27825d59c61da252a99f402a2" - "0.0.103|openshell.rb|https://github.com/NVIDIA/OpenShell/releases/download/v0.0.103/openshell.rb|95a290f0e0e2f57d7d46ba9171fca6e99e5226875cd12e12391b7338f6c219f9" -) +# Trust-anchor rollout is intentionally two-step. The base-trusted TypeScript +# parser owns each release record and emits the manifest and formula identities +# that this shell checker downloads. A later pin PR can select only a complete +# record that already exists in the target branch. case "${1:-}" in "") ;; @@ -114,15 +79,12 @@ check_openshell_release_assets() { local brev_installer="${REPO_ROOT}/scripts/brev-launchable-ci-cpu.sh" local supervisor_runtime="${REPO_ROOT}/src/lib/onboard/docker-driver-gateway-runtime.ts" local release_base workspace manifests spec manifest expected actual source asset pinned upstream formula_asset - local matches required_manifest required_matches formula_expected="" formula_matches=0 formula_url="" - local pin_records parser_error parser_errors parsed_version release_version="" record_extra - local allowlist_entry allowlist_version allowlist_extra - local formula_allowlist_entry formula_allowlist_version formula_allowlist_asset formula_allowlist_url formula_allowlist_digest formula_allowlist_extra - local count=0 brev_count=0 published_count=0 expected_published_count=0 failures=0 + local matches formula_expected formula_matches formula_url + local pin_records parser_error parser_errors record_type parsed_version release_version record_extra + local selected_release_version="" release_versions="" version_pin_records + local count brev_count published_count expected_published_count failures=0 local -a manifest_specs=() workspace=$(mktemp -d) - manifests="${workspace}/published-sha256.txt" - : >"$manifests" trap 'rm -rf "$workspace"' RETURN # invalidState: target-controlled shell formatting hides, duplicates, or @@ -145,7 +107,7 @@ check_openshell_release_assets() { --installer "$installer" \ --brev-installer "$brev_installer" \ --supervisor-runtime "$supervisor_runtime" \ - --format tsv 2>"$parser_errors"); then + --format release-tsv 2>"$parser_errors"); then echo " STALE: unable to extract the OpenShell installer pin tables with trusted parser code." while IFS= read -r parser_error; do echo " ${parser_error}" @@ -153,169 +115,187 @@ check_openshell_release_assets() { return 1 fi - while IFS=$'\t' read -r parsed_version source asset pinned record_extra; do - if [[ ! "$parsed_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || -z "$source" || -z "$asset" || -z "$pinned" || -n "$record_extra" ]]; then - echo " STALE: trusted parser returned an invalid installer pin record." - return 1 - fi - if [[ -z "$release_version" ]]; then - release_version="$parsed_version" - elif [[ "$parsed_version" != "$release_version" ]]; then - echo " STALE: trusted parser returned multiple OpenShell release versions." + while IFS=$'\t' read -r record_type parsed_version source asset pinned record_extra; do + if [[ ! "$parsed_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || -z "$source" || -z "$asset" || ! "$pinned" =~ ^[a-f0-9]{64}$ || -n "$record_extra" ]]; then + echo " STALE: trusted parser returned an invalid OpenShell release record." return 1 fi - case "$source" in - installer) count=$((count + 1)) ;; - "Brev launchable") brev_count=$((brev_count + 1)) ;; + case "$record_type" in + manifest) + [[ "$source" == "OpenShell release" ]] || { + echo " STALE: trusted parser returned an invalid OpenShell manifest record." + return 1 + } + ;; + formula) + if [[ "$asset" != "openshell.rb" || "$source" != "https://github.com/NVIDIA/OpenShell/releases/download/v${parsed_version}/${asset}" ]]; then + echo " STALE: trusted parser returned an invalid OpenShell formula record." + return 1 + fi + ;; + pin) + case "$source" in + installer) ;; + "Brev launchable") + if [[ -z "$selected_release_version" ]]; then + selected_release_version="$parsed_version" + elif [[ "$selected_release_version" != "$parsed_version" ]]; then + echo " STALE: trusted parser returned multiple Brev OpenShell release versions." + return 1 + fi + ;; + *) + echo " STALE: trusted parser returned an unknown pin source." + return 1 + ;; + esac + ;; *) - echo " STALE: trusted parser returned an unknown pin source." + echo " STALE: trusted parser returned an unknown OpenShell release record type." return 1 ;; esac + case " ${release_versions} " in + *" ${parsed_version} "*) ;; + *) release_versions="${release_versions:+${release_versions} }${parsed_version}" ;; + esac done <<<"$pin_records" - if [[ "$count" -ne 9 ]]; then - echo " STALE: expected 9 pinned OpenShell v${release_version:-unknown} assets, found ${count}." - failures=$((failures + 1)) - fi - if [[ "$brev_count" -ne 2 ]]; then - echo " STALE: expected 2 pinned Brev OpenShell v${release_version:-unknown} CLI assets, found ${brev_count}." - failures=$((failures + 1)) - fi - if [[ "$failures" -ne 0 ]]; then - return "$failures" - fi + [[ -n "$selected_release_version" ]] || { + echo " STALE: trusted parser returned no selected Brev OpenShell release." + return 1 + } - for allowlist_entry in "${OPENSHELL_RELEASE_MANIFEST_ALLOWLIST[@]}"; do - IFS='|' read -r allowlist_version manifest expected allowlist_extra <<<"$allowlist_entry" - if [[ ! "$allowlist_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || ! "$expected" =~ ^[a-f0-9]{64}$ || -z "$manifest" || -n "$allowlist_extra" ]]; then - echo " STALE: trusted OpenShell release-manifest allowlist is invalid." - return 1 - fi - if [[ "$allowlist_version" == "$release_version" ]]; then - manifest_specs+=("${manifest}:${expected}") - fi - done + for release_version in $release_versions; do + manifests="${workspace}/published-sha256-${release_version}.txt" + : >"$manifests" + manifest_specs=() + formula_expected="" + formula_matches=0 + formula_url="" + count=0 + brev_count=0 + published_count=0 + version_pin_records="" - if [[ "${#manifest_specs[@]}" -eq 0 ]]; then - echo " STALE: OpenShell v${release_version} is not in the trusted release-manifest allowlist." - return 1 - fi - if [[ "${#manifest_specs[@]}" -ne 3 ]]; then - echo " STALE: OpenShell v${release_version} does not have exactly three trusted release-manifest digests." - return 1 - fi - for required_manifest in \ - openshell-checksums-sha256.txt \ - openshell-gateway-checksums-sha256.txt \ - openshell-sandbox-checksums-sha256.txt; do - required_matches=0 - for spec in "${manifest_specs[@]}"; do - if [[ "${spec%%:*}" == "$required_manifest" ]]; then - required_matches=$((required_matches + 1)) - fi - done - if [[ "$required_matches" -ne 1 ]]; then - echo " STALE: OpenShell v${release_version} does not have exactly one trusted ${required_manifest} digest." - failures=$((failures + 1)) - fi - done - if [[ "$failures" -ne 0 ]]; then - return "$failures" - fi + while IFS=$'\t' read -r record_type parsed_version source asset pinned record_extra; do + [[ "$parsed_version" == "$release_version" ]] || continue + case "$record_type" in + manifest) + manifest_specs+=("${asset}:${pinned}") + ;; + formula) + formula_matches=$((formula_matches + 1)) + formula_url="$source" + formula_expected="$pinned" + ;; + pin) + version_pin_records+="${record_type}"$'\t'"${parsed_version}"$'\t'"${source}"$'\t'"${asset}"$'\t'"${pinned}"$'\n' + case "$source" in + installer) count=$((count + 1)) ;; + "Brev launchable") brev_count=$((brev_count + 1)) ;; + esac + ;; + esac + done <<<"$pin_records" - for formula_allowlist_entry in "${OPENSHELL_RELEASE_FORMULA_ALLOWLIST[@]}"; do - IFS='|' read -r formula_allowlist_version formula_allowlist_asset formula_allowlist_url formula_allowlist_digest formula_allowlist_extra <<<"$formula_allowlist_entry" - if [[ ! "$formula_allowlist_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || "$formula_allowlist_asset" != "openshell.rb" || "$formula_allowlist_url" != "https://github.com/NVIDIA/OpenShell/releases/download/v${formula_allowlist_version}/${formula_allowlist_asset}" || ! "$formula_allowlist_digest" =~ ^[a-f0-9]{64}$ || -n "$formula_allowlist_extra" ]]; then - echo " STALE: trusted OpenShell formula allowlist is invalid." + if [[ "$count" -ne 9 ]]; then + echo " STALE: expected 9 pinned OpenShell v${release_version} assets, found ${count}." return 1 fi - if [[ "$formula_allowlist_version" == "$release_version" ]]; then - formula_matches=$((formula_matches + 1)) - formula_url="$formula_allowlist_url" - formula_expected="$formula_allowlist_digest" + if [[ "$release_version" == "$selected_release_version" && "$brev_count" -ne 2 ]]; then + echo " STALE: expected 2 pinned Brev OpenShell v${release_version} CLI assets, found ${brev_count}." + return 1 fi - done - - if [[ "$formula_matches" -ne 1 ]]; then - echo " STALE: OpenShell v${release_version} does not have exactly one trusted openshell.rb digest." - return 1 - fi - - release_base="https://github.com/NVIDIA/OpenShell/releases/download/v${release_version}" - echo "Checking OpenShell v${release_version} release assets..." - for spec in "${manifest_specs[@]}"; do - manifest="${spec%%:*}" - expected="${spec#*:}" - if ! fetch_file "${release_base}/${manifest}" "${workspace}/${manifest}"; then - echo " STALE: unable to download ${manifest}." - failures=$((failures + 1)) - continue + if [[ "$release_version" != "$selected_release_version" && "$brev_count" -ne 0 ]]; then + echo " STALE: unselected OpenShell v${release_version} must not have Brev pins." + return 1 fi - if ! actual=$(sha256_file "${workspace}/${manifest}"); then - echo " STALE: unable to hash ${manifest}." - failures=$((failures + 1)) - continue + if [[ "${#manifest_specs[@]}" -ne 3 ]]; then + echo " STALE: trusted parser did not return exactly three OpenShell v${release_version} release manifests." + return 1 fi - if [[ "$actual" != "$expected" ]]; then - echo " STALE: ${manifest} digest does not match the pinned v${release_version} release asset." - echo " pinned: ${expected}" - echo " upstream: ${actual}" - failures=$((failures + 1)) - continue + if [[ "$formula_matches" -ne 1 ]]; then + echo " STALE: trusted parser did not return exactly one OpenShell v${release_version} formula record." + return 1 fi - echo " OK: ${manifest} (${actual})" - cat "${workspace}/${manifest}" >>"$manifests" - done - while IFS=$'\t' read -r parsed_version source asset pinned record_extra; do - if [[ "$asset" == "openshell.rb" ]]; then - formula_asset="${workspace}/${asset}" - if ! fetch_file "$formula_url" "$formula_asset"; then - echo " STALE: unable to download ${source} ${asset}." + release_base="https://github.com/NVIDIA/OpenShell/releases/download/v${release_version}" + echo "Checking OpenShell v${release_version} release assets..." + for spec in "${manifest_specs[@]}"; do + manifest="${spec%%:*}" + expected="${spec#*:}" + if ! fetch_file "${release_base}/${manifest}" "${workspace}/${release_version}-${manifest}"; then + echo " STALE: unable to download ${manifest}." failures=$((failures + 1)) continue fi - if ! actual=$(sha256_file "$formula_asset"); then - echo " STALE: unable to hash ${source} ${asset}." + if ! actual=$(sha256_file "${workspace}/${release_version}-${manifest}"); then + echo " STALE: unable to hash ${manifest}." failures=$((failures + 1)) continue fi - if [[ "$actual" != "$formula_expected" ]]; then - echo " STALE: ${source} ${asset} does not match the base-trusted v${release_version} formula digest." - echo " trusted: ${formula_expected}" + if [[ "$actual" != "$expected" ]]; then + echo " STALE: ${manifest} digest does not match the pinned v${release_version} release asset." + echo " pinned: ${expected}" echo " upstream: ${actual}" failures=$((failures + 1)) - elif [[ "$pinned" == "$formula_expected" ]]; then + continue + fi + echo " OK: ${manifest} (${actual})" + cat "${workspace}/${release_version}-${manifest}" >>"$manifests" + done + + while IFS=$'\t' read -r record_type parsed_version source asset pinned record_extra; do + [[ -n "$parsed_version" ]] || continue + if [[ "$asset" == "openshell.rb" ]]; then + formula_asset="${workspace}/${asset}" + if ! fetch_file "$formula_url" "$formula_asset"; then + echo " STALE: unable to download ${source} ${asset}." + failures=$((failures + 1)) + continue + fi + if ! actual=$(sha256_file "$formula_asset"); then + echo " STALE: unable to hash ${source} ${asset}." + failures=$((failures + 1)) + continue + fi + if [[ "$actual" != "$formula_expected" ]]; then + echo " STALE: ${source} ${asset} does not match the base-trusted v${release_version} formula digest." + echo " trusted: ${formula_expected}" + echo " upstream: ${actual}" + failures=$((failures + 1)) + elif [[ "$pinned" == "$formula_expected" ]]; then + published_count=$((published_count + 1)) + echo " OK: ${source} ${asset} (${pinned})" + else + echo " STALE: ${source} ${asset} pin does not match the base-trusted v${release_version} formula digest." + echo " pinned: ${pinned}" + echo " trusted: ${formula_expected}" + failures=$((failures + 1)) + fi + continue + fi + matches=$(awk -v asset="$asset" '$2 == asset { count++ } END { print count + 0 }' "$manifests") + upstream=$(awk -v asset="$asset" '$2 == asset { print $1; exit }' "$manifests") + if [[ "$matches" -eq 1 && "$pinned" == "$upstream" ]]; then published_count=$((published_count + 1)) echo " OK: ${source} ${asset} (${pinned})" else - echo " STALE: ${source} ${asset} pin does not match the base-trusted v${release_version} formula digest." + echo " STALE: ${source} ${asset} does not match exactly one v${release_version} checksum entry." echo " pinned: ${pinned}" - echo " trusted: ${formula_expected}" + echo " upstream: ${upstream:-missing}" + echo " matches: ${matches}" failures=$((failures + 1)) fi - continue - fi - matches=$(awk -v asset="$asset" '$2 == asset { count++ } END { print count + 0 }' "$manifests") - upstream=$(awk -v asset="$asset" '$2 == asset { print $1; exit }' "$manifests") - if [[ "$matches" -eq 1 && "$pinned" == "$upstream" ]]; then - published_count=$((published_count + 1)) - echo " OK: ${source} ${asset} (${pinned})" - else - echo " STALE: ${source} ${asset} does not match exactly one v${release_version} checksum entry." - echo " pinned: ${pinned}" - echo " upstream: ${upstream:-missing}" - echo " matches: ${matches}" + done <<<"$version_pin_records" + + expected_published_count=$((count + brev_count)) + if [[ "$published_count" -ne "$expected_published_count" ]]; then + echo " STALE: expected all ${expected_published_count} pinned asset references for v${release_version}, matched ${published_count}." failures=$((failures + 1)) fi - done <<<"$pin_records" - - expected_published_count=$((count + brev_count)) - if [[ "$published_count" -ne "$expected_published_count" ]]; then - echo " STALE: expected all ${expected_published_count} pinned asset references for v${release_version}, matched ${published_count}." - failures=$((failures + 1)) - fi + done return "$failures" } diff --git a/scripts/checks/dependency-pins.mts b/scripts/checks/dependency-pins.mts index 450d6ec4f57..f2a8e4ce68c 100644 --- a/scripts/checks/dependency-pins.mts +++ b/scripts/checks/dependency-pins.mts @@ -271,26 +271,35 @@ function requireVersionReference( } } -function requireOpenShellReleaseManifestAllowlist( +function requireOpenShellReleaseTrustRecord( source: string, expectedVersion: string, failures: string[], ): void { - const entries = [ - ...source.matchAll(/^\s*"([0-9]+\.[0-9]+\.[0-9]+)\|([^|"\s]+)\|([a-f0-9]{64})"\s*$/gm), - ] - .filter((match) => match[1] === expectedVersion) - .map((match) => match[2]) + const marker = "const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = ["; + const start = source.indexOf(marker); + const end = source.indexOf("\n] as const;", start + marker.length); + const records = + start === -1 || end === -1 + ? [] + : [...source.slice(start + marker.length, end).matchAll(/^ \{\n([\s\S]*?)^ \},$/gm)].map( + (match) => match[1] ?? "", + ); + const matchingRecords = records.filter((record) => { + const versions = [...record.matchAll(/^ version: "([0-9]+\.[0-9]+\.[0-9]+)",$/gm)]; + return versions.length === 1 && versions[0]?.[1] === expectedVersion; + }); + const entries = [...(matchingRecords[0] ?? "").matchAll(/^ asset: "([^"]+)",$/gm)] + .map((match) => match[1]) .filter((manifest): manifest is string => manifest !== undefined); const complete = + matchingRecords.length === 1 && entries.length === OPENSHELL_RELEASE_MANIFESTS.length && OPENSHELL_RELEASE_MANIFESTS.every( (manifest) => entries.filter((entry) => entry === manifest).length === 1, ); if (!complete) { - failures.push( - `OpenShell release-manifest allowlist: expected one complete entry for ${expectedVersion}`, - ); + failures.push(`OpenShell release trust: expected one complete record for ${expectedVersion}`); } } @@ -303,7 +312,7 @@ function verifyOpenShellPins( hermesDockerfile: string; hermesMcpConfigTransaction: string; installer: string; - installerHashCheck: string; + installerPinExtractor: string; mcpBridgeValidation: string; openshellFeatureGate: string; openshellInstall: string; @@ -340,7 +349,7 @@ function verifyOpenShellPins( "OpenShell installer PIN_VERSION", failures, ); - requireOpenShellReleaseManifestAllowlist(sources.installerHashCheck, pins.maxVersion, failures); + requireOpenShellReleaseTrustRecord(sources.installerPinExtractor, pins.maxVersion, failures); compare( extractSingle( sources.openshellVersion, @@ -539,7 +548,11 @@ export function verifyDependencyPins(rootDir: string = REPO_ROOT): string[] { const brevLaunchable = readText(rootDir, "scripts/brev-launchable-ci-cpu.sh", failures); const installer = readText(rootDir, "scripts/install-openshell.sh", failures); - const installerHashCheck = readText(rootDir, "scripts/check-installer-hash.sh", failures); + const installerPinExtractor = readText( + rootDir, + "scripts/checks/extract-installer-pins.mts", + failures, + ); const e2eWorkflowSource = readText(rootDir, ".github/workflows/e2e.yaml", failures); const openclawManifestSource = readText(rootDir, "agents/openclaw/manifest.yaml", failures); const hermesManifestSource = readText(rootDir, "agents/hermes/manifest.yaml", failures); @@ -613,7 +626,7 @@ export function verifyDependencyPins(rootDir: string = REPO_ROOT): string[] { hermesDockerfile, hermesMcpConfigTransaction, installer, - installerHashCheck, + installerPinExtractor, mcpBridgeValidation, openshellFeatureGate, openshellInstall, diff --git a/scripts/checks/extract-installer-pins.mts b/scripts/checks/extract-installer-pins.mts index b22bb649046..cafc8e33533 100644 --- a/scripts/checks/extract-installer-pins.mts +++ b/scripts/checks/extract-installer-pins.mts @@ -30,8 +30,9 @@ type SandboxBuildPin = { version: string; }; -type TrustedSandboxBuildPin = SandboxBuildPin & { +type TrustedSandboxBuild = { required: boolean; + sha256: string; }; type SupervisorManifestPin = { @@ -40,14 +41,34 @@ type SupervisorManifestPin = { version: string; }; -type TrustedSupervisorManifestPin = SupervisorManifestPin & { +type TrustedSupervisorManifest = { + image: string; + manifestDigest: string; required: boolean; + runtimeTemplateSha256: readonly string[]; +}; + +type OpenShellReleaseTrust = { + brevTemplateSha256: readonly string[]; + formula: { + asset: "openshell.rb"; + sha256: string; + url: string; + }; + installerTemplateSha256: readonly string[]; + manifests: readonly { + asset: string; + sha256: string; + }[]; + sandboxBuilds: readonly TrustedSandboxBuild[]; + supervisor: TrustedSupervisorManifest | null; + version: string; }; type CliOptions = { blueprint: string; brevInstaller: string; - format: "json" | "tsv"; + format: "json" | "release-tsv" | "tsv"; installer: string; supervisorRuntime: string; }; @@ -57,134 +78,362 @@ const FUNCTION_LOCAL_SOURCE_PATTERN = const LITERAL_PIN_PATTERN = /^v([0-9]+\.[0-9]+\.[0-9]+):([A-Za-z0-9._+-]+)$/u; const SHA256_PATTERN = /^[a-f0-9]{64}$/u; const MAX_INSTALLER_INPUT_BYTES = 1024 * 1024; -// These hashes freeze the complete reviewed scripts after normalizing only the -// strictly parsed pin-table function and stable release selector. Update them -// only in a prerequisite trust-anchor PR that keeps the currently selected -// release; the later pin PR may then change release data without authorizing -// any operational installer change. A mismatch reports the candidate hash. -// #7739 extends the Homebrew trust transition into one operation boundary used -// by installation and later lifecycle calls. Keep only the reviewed successor: -// it verifies formula bytes before each operation, uses formula-scoped trust, -// and removes that temporary trust after success or failure. -const TRUSTED_INSTALLER_TEMPLATE_SHA256_ALLOWLIST = [ - "6226811887cc5c1a721a96fbf062f5ce5f75b09d3a8a1de49ed4dadc3236eb0c", -] as const; -const TRUSTED_BREV_TEMPLATE_SHA256_ALLOWLIST = [ - "c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a", -] as const; -const EXPECTED_INSTALLER_ASSETS = [ - "openshell-x86_64-unknown-linux-musl.tar.gz", - "openshell-aarch64-unknown-linux-musl.tar.gz", - "openshell-aarch64-apple-darwin.tar.gz", - "openshell-gateway-x86_64-unknown-linux-gnu.tar.gz", - "openshell-gateway-aarch64-unknown-linux-gnu.tar.gz", - "openshell-gateway-aarch64-apple-darwin.tar.gz", - "openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz", - "openshell-sandbox-aarch64-unknown-linux-gnu.tar.gz", - "openshell.rb", -] as const; -const EXPECTED_BREV_ASSETS = [ - "openshell-x86_64-unknown-linux-musl.tar.gz", - "openshell-aarch64-unknown-linux-musl.tar.gz", -] as const; -// These are SHA-256 identities of the standalone openshell-sandbox binaries -// extracted from checksum-verified OpenShell release archives. Existing -// fallback identities stay required so a candidate cannot silently remove -// downgrade/upgrade coverage. A future release is first added here as an -// optional trust prerequisite; only a later selector PR may add its exact -// digest/version pairs to the candidate-controlled shell map. -const TRUSTED_SANDBOX_BUILD_PINS: readonly TrustedSandboxBuildPin[] = [ - { - required: true, - sha256: "f9f991a24d10772ad5d24ae27a8ea6baad8cac671695bd90fcd0355e0e0ad198", - version: "0.0.72", - }, +// Each release record is one base-trusted qualification unit. The parser +// selects one record before it accepts candidate release data or emits the +// manifest and formula identities consumed by the shell checker. +const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [ { - required: true, - sha256: "32ca44fe7d9e6d332f2a753c6b8a1a6117b7388281dad9b5274d23ffc67e216f", + brevTemplateSha256: ["c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a"], + formula: { + asset: "openshell.rb", + sha256: "4b75a7e3a7630eb8954d73ca828b394d5e0646adbaa4b087b2435329d53b61b3", + url: "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.72/openshell.rb", + }, + installerTemplateSha256: ["6226811887cc5c1a721a96fbf062f5ce5f75b09d3a8a1de49ed4dadc3236eb0c"], + manifests: [ + { + asset: "openshell-checksums-sha256.txt", + sha256: "0049181983eaf925ef9510382f75348229a9511d02e27196107782e7c3259ae1", + }, + { + asset: "openshell-gateway-checksums-sha256.txt", + sha256: "3c454dc15154b8c700ec820628559ea8964c6e552d9c5f8af78b6ee19cf34547", + }, + { + asset: "openshell-sandbox-checksums-sha256.txt", + sha256: "d38507501338576437cf3e554df71fefe927dc0d72758f88e260069527ed9ccc", + }, + ], + sandboxBuilds: [ + { + required: true, + sha256: "f9f991a24d10772ad5d24ae27a8ea6baad8cac671695bd90fcd0355e0e0ad198", + }, + { + required: true, + sha256: "32ca44fe7d9e6d332f2a753c6b8a1a6117b7388281dad9b5274d23ffc67e216f", + }, + ], + supervisor: { + image: "ghcr.io/nvidia/openshell/supervisor", + manifestDigest: "sha256:80ed9cda5bf672fefdb9dcd4604b40a8b09c0891b6eb9d03e10227c7e3dfb49d", + required: true, + runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + }, version: "0.0.72", }, { - required: true, - sha256: "145246049bd73c60452ac3c2b4b1801663196c8e2f80575af820289c78c1cf09", - version: "0.0.82", - }, - { - required: true, - sha256: "76bc19b70d9f1e1e9871307045796cd39cc7b8fc4c08ffc90593cc934f36d500", + brevTemplateSha256: ["c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a"], + formula: { + asset: "openshell.rb", + sha256: "fa54640184e22fa74500ab24f5b4372582616c7e12a1152cb6983bc0738c5a74", + url: "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.82/openshell.rb", + }, + installerTemplateSha256: ["6226811887cc5c1a721a96fbf062f5ce5f75b09d3a8a1de49ed4dadc3236eb0c"], + manifests: [ + { + asset: "openshell-checksums-sha256.txt", + sha256: "74ba77d368744f412b2dd246099b63b38937962807333ded2b6284580a2d014e", + }, + { + asset: "openshell-gateway-checksums-sha256.txt", + sha256: "c0a369ba2c66bcde3c18ce2753b04ff942d1fe1b5f3e4656de520f6d4b175477", + }, + { + asset: "openshell-sandbox-checksums-sha256.txt", + sha256: "3300b9856cdbe8e3f9b0f8068bbad93673739c4cfd3212c80dc0675168ee2b8d", + }, + ], + sandboxBuilds: [ + { + required: true, + sha256: "145246049bd73c60452ac3c2b4b1801663196c8e2f80575af820289c78c1cf09", + }, + { + required: true, + sha256: "76bc19b70d9f1e1e9871307045796cd39cc7b8fc4c08ffc90593cc934f36d500", + }, + ], + supervisor: null, version: "0.0.82", }, { - required: true, - sha256: "a4b0c38ed90a6dd4b4f312ad3727824a25ec478d88d4e65d22a82377b18e6214", - version: "0.0.99", + brevTemplateSha256: ["c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a"], + formula: { + asset: "openshell.rb", + sha256: "f53c62777fed23b42427822d231670451ee4358efeb2660c41a7a38919211b23", + url: "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.85/openshell.rb", + }, + installerTemplateSha256: ["6226811887cc5c1a721a96fbf062f5ce5f75b09d3a8a1de49ed4dadc3236eb0c"], + manifests: [ + { + asset: "openshell-checksums-sha256.txt", + sha256: "6554b3f96c04006d661519786d40d17e34c7860b7aac8fd35259ef2aea01567f", + }, + { + asset: "openshell-gateway-checksums-sha256.txt", + sha256: "cc4f32afed376ebe9b43cccdb4d2a77b2524b57132a6b56bb88d705e02420f86", + }, + { + asset: "openshell-sandbox-checksums-sha256.txt", + sha256: "b6ac353c933fa4cf9a3ef11d66cce6635f39ecc2e928d9c8ff1783ca797308b3", + }, + ], + sandboxBuilds: [], + supervisor: null, + version: "0.0.85", }, { - required: true, - sha256: "f60ce5b76e4dbd645f690c8519852d261c8cf6a70b5fc56db329a23d68bc7b2e", + brevTemplateSha256: ["c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a"], + formula: { + asset: "openshell.rb", + sha256: "8dd34fc17ee9a30327664a18c9509c8a765cb010de38cda8e22841bddbe92713", + url: "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.99/openshell.rb", + }, + installerTemplateSha256: ["6226811887cc5c1a721a96fbf062f5ce5f75b09d3a8a1de49ed4dadc3236eb0c"], + manifests: [ + { + asset: "openshell-checksums-sha256.txt", + sha256: "ea3e2c1a583e5ea00332c3b65a18068bd1f9b090f7ff0f5e24b29762cfc3b4c7", + }, + { + asset: "openshell-gateway-checksums-sha256.txt", + sha256: "7f84f728412548720c8ef51993c58414c4f04598451c282b26ead233185e40c5", + }, + { + asset: "openshell-sandbox-checksums-sha256.txt", + sha256: "9e67af6bab9f975432a1045fcfea5ab182ab585b17886c8c290c1eb77232b87a", + }, + ], + sandboxBuilds: [ + { + required: true, + sha256: "a4b0c38ed90a6dd4b4f312ad3727824a25ec478d88d4e65d22a82377b18e6214", + }, + { + required: true, + sha256: "f60ce5b76e4dbd645f690c8519852d261c8cf6a70b5fc56db329a23d68bc7b2e", + }, + ], + supervisor: { + image: "ghcr.io/nvidia/openshell/supervisor", + manifestDigest: "sha256:ea3632b6e9528e2309103af5b6949606fcdc83ca1f69e8db81482a25bea84bb6", + required: true, + runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + }, version: "0.0.99", }, { - required: false, - sha256: "a2704babbb468fd0a359bfdd9844de71095b730758541b4ca8cbab77d4018920", - version: "0.0.101", - }, - { - required: false, - sha256: "88300e35f153123e4dc3021c537834dd6c0a09665a4a6d3974cd285d512345c4", + brevTemplateSha256: ["c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a"], + formula: { + asset: "openshell.rb", + sha256: "87fadc7b0c854aa44f71d5b3a206865070117cd27825d59c61da252a99f402a2", + url: "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.101/openshell.rb", + }, + installerTemplateSha256: [ + "6226811887cc5c1a721a96fbf062f5ce5f75b09d3a8a1de49ed4dadc3236eb0c", + "d3ee11fd805d84c0e0f760831e091c1f16632e61cf9c1af7e7856e0aafc9de54", + ], + manifests: [ + { + asset: "openshell-checksums-sha256.txt", + sha256: "9c90869d00b109b5ac1062b1a9808a592c2311d3c0c4926bae44d136b979d8a9", + }, + { + asset: "openshell-gateway-checksums-sha256.txt", + sha256: "dcb3f1917713bf2a8e8e1803ac42c5e39d9dd41e644136b05def32b077082777", + }, + { + asset: "openshell-sandbox-checksums-sha256.txt", + sha256: "d16f7d369c54d74d36c7df036565267a960e7ce6fb143012fe9d77f257d6e8b3", + }, + ], + sandboxBuilds: [ + { + required: false, + sha256: "a2704babbb468fd0a359bfdd9844de71095b730758541b4ca8cbab77d4018920", + }, + { + required: false, + sha256: "88300e35f153123e4dc3021c537834dd6c0a09665a4a6d3974cd285d512345c4", + }, + ], + supervisor: { + image: "ghcr.io/nvidia/openshell/supervisor", + manifestDigest: "sha256:b58be5e40c788977ffa0e8305a8cad9c656efdf1a3fe182582a00ca870bb0edb", + required: true, + runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + }, version: "0.0.101", }, { - required: false, - sha256: "412dc28fa288938373aca0a95c6be3f890066c377992bb75b3ca078d92dbef00", + brevTemplateSha256: ["c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a"], + formula: { + asset: "openshell.rb", + sha256: "95a290f0e0e2f57d7d46ba9171fca6e99e5226875cd12e12391b7338f6c219f9", + url: "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.103/openshell.rb", + }, + installerTemplateSha256: ["6226811887cc5c1a721a96fbf062f5ce5f75b09d3a8a1de49ed4dadc3236eb0c"], + manifests: [ + { + asset: "openshell-checksums-sha256.txt", + sha256: "1a9016cfb9219ad6ea3dc623b3dfd517dbce062cba9484964a8ca9175c7d1c9d", + }, + { + asset: "openshell-gateway-checksums-sha256.txt", + sha256: "800f8501329b27b79d260f21de088d8aea36de45021eaa3d29d189c433fc04b5", + }, + { + asset: "openshell-sandbox-checksums-sha256.txt", + sha256: "ab7c77fe40e93b293e4d34e892824ed0cb131e8b973ba2660b155cdd0fa0f604", + }, + ], + sandboxBuilds: [ + { + required: false, + sha256: "412dc28fa288938373aca0a95c6be3f890066c377992bb75b3ca078d92dbef00", + }, + { + required: false, + sha256: "fc1454705fad9cc0890297a84d2b7869670a364d01d5398685e3c987d2b6c123", + }, + ], + supervisor: { + image: "ghcr.io/nvidia/openshell/supervisor", + manifestDigest: "sha256:96228f110362ffd415bb12d3b7f584063c3c52c0c93f3ccf59faada1dc2dd5d3", + required: false, + runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + }, version: "0.0.103", }, { - required: false, - sha256: "fc1454705fad9cc0890297a84d2b7869670a364d01d5398685e3c987d2b6c123", - version: "0.0.103", + brevTemplateSha256: [ + "c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a", + "56fc6482d1508b73604099e6fd6c16daea16275cf36cc25c1c5366c82a4394e3", + ], + formula: { + asset: "openshell.rb", + sha256: "f0f86519e227b3b326431410058ba690b1a7b83e5af7384014e4b96283d3a642", + url: "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.106/openshell.rb", + }, + // Reviewed normalized template from the downstream 0.0.106 pin PR. This + // trust prerequisite intentionally leaves the selected product release + // unchanged until that separately reviewed pin update lands. + installerTemplateSha256: ["5d4cdb2db60df7539193b486ac15bb9be96ec1d40fc0f739a94d4d2f0bf597a0"], + manifests: [ + { + asset: "openshell-checksums-sha256.txt", + sha256: "7421aaf9d5550dc15aa33b523fa3dfe78571811e4ddf76f9f6c29576438bdb27", + }, + { + asset: "openshell-gateway-checksums-sha256.txt", + sha256: "26e4345449e02475e27a7c59cd0cf39199dd6c91b0aa635fbb8cb834835f4b39", + }, + { + asset: "openshell-sandbox-checksums-sha256.txt", + sha256: "88bc98ffdc915fb7598f39df84ab37a1a31e40e33e4125b37ed13adecd447dbb", + }, + ], + sandboxBuilds: [ + { + required: false, + sha256: "0031c6b257a23ecc1a2333153918324f3af0005e68abde388858d682ec646c55", + }, + { + required: false, + sha256: "019301ec8618abbed8135e8d39dde7bea47e5e92813bbc17768550de34db59f8", + }, + ], + supervisor: { + image: "ghcr.io/nvidia/openshell/supervisor", + manifestDigest: "sha256:722f44669722961b7f432b0b81de25b91a58f34a61d6403bef967acaf2b3af01", + required: false, + runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + }, + version: "0.0.106", }, ] as const; -const OPENSHELL_SUPERVISOR_IMAGE = "ghcr.io/nvidia/openshell/supervisor"; -// These are the reviewed OCI index identities for the OpenShell supervisor -// image. Existing mappings remain required so a candidate cannot silently -// remove downgrade or upgrade coverage. Add a future release here first while -// it remains unselected; a later selector PR may then add only that exact -// version/digest pair to the runtime map. -const TRUSTED_SUPERVISOR_MANIFEST_PINS: readonly TrustedSupervisorManifestPin[] = [ - { - image: OPENSHELL_SUPERVISOR_IMAGE, - manifestDigest: "sha256:80ed9cda5bf672fefdb9dcd4604b40a8b09c0891b6eb9d03e10227c7e3dfb49d", - required: true, - version: "0.0.72", - }, - { - image: OPENSHELL_SUPERVISOR_IMAGE, - manifestDigest: "sha256:ea3632b6e9528e2309103af5b6949606fcdc83ca1f69e8db81482a25bea84bb6", - required: true, - version: "0.0.99", - }, - { - image: OPENSHELL_SUPERVISOR_IMAGE, - manifestDigest: "sha256:b58be5e40c788977ffa0e8305a8cad9c656efdf1a3fe182582a00ca870bb0edb", - required: true, - version: "0.0.101", - }, - { - image: OPENSHELL_SUPERVISOR_IMAGE, - manifestDigest: "sha256:96228f110362ffd415bb12d3b7f584063c3c52c0c93f3ccf59faada1dc2dd5d3", - required: false, - version: "0.0.103", - }, +const EXPECTED_INSTALLER_ASSETS = [ + "openshell-x86_64-unknown-linux-musl.tar.gz", + "openshell-aarch64-unknown-linux-musl.tar.gz", + "openshell-aarch64-apple-darwin.tar.gz", + "openshell-gateway-x86_64-unknown-linux-gnu.tar.gz", + "openshell-gateway-aarch64-unknown-linux-gnu.tar.gz", + "openshell-gateway-aarch64-apple-darwin.tar.gz", + "openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz", + "openshell-sandbox-aarch64-unknown-linux-gnu.tar.gz", + "openshell.rb", ] as const; -const TRUSTED_SUPERVISOR_RUNTIME_TEMPLATE_SHA256_ALLOWLIST: readonly string[] = [ - "c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068", +const EXPECTED_BREV_ASSETS = [ + "openshell-x86_64-unknown-linux-musl.tar.gz", + "openshell-aarch64-unknown-linux-musl.tar.gz", ] as const; function fail(message: string): never { throw new Error(`Installer pin extraction failed: ${message}`); } +function validateTrustedRelease(release: OpenShellReleaseTrust): void { + const requiredManifests = [ + "openshell-checksums-sha256.txt", + "openshell-gateway-checksums-sha256.txt", + "openshell-sandbox-checksums-sha256.txt", + ] as const; + const manifestAssets = release.manifests.map((manifest) => manifest.asset).sort(); + if ( + !/^[0-9]+\.[0-9]+\.[0-9]+$/u.test(release.version) || + manifestAssets.length !== requiredManifests.length || + manifestAssets.some((asset, index) => asset !== [...requiredManifests].sort()[index]) || + release.manifests.some((manifest) => !SHA256_PATTERN.test(manifest.sha256)) + ) { + fail(`OpenShell v${release.version} must have exactly three trusted release-manifest digests`); + } + if ( + !release.formula || + release.formula.asset !== "openshell.rb" || + release.formula.url !== + `https://github.com/NVIDIA/OpenShell/releases/download/v${release.version}/openshell.rb` || + !SHA256_PATTERN.test(release.formula.sha256) + ) { + fail(`trusted OpenShell v${release.version} formula record is invalid`); + } + if ( + release.installerTemplateSha256.length === 0 || + release.installerTemplateSha256.some((sha256) => !SHA256_PATTERN.test(sha256)) || + release.brevTemplateSha256.length === 0 || + release.brevTemplateSha256.some((sha256) => !SHA256_PATTERN.test(sha256)) || + release.sandboxBuilds.some((pin) => !SHA256_PATTERN.test(pin.sha256)) + ) { + fail(`trusted OpenShell v${release.version} template or sandbox record is invalid`); + } + if ( + release.supervisor && + (release.supervisor.image !== "ghcr.io/nvidia/openshell/supervisor" || + !/^sha256:[a-f0-9]{64}$/u.test(release.supervisor.manifestDigest) || + release.supervisor.runtimeTemplateSha256.length === 0 || + release.supervisor.runtimeTemplateSha256.some((sha256) => !SHA256_PATTERN.test(sha256))) + ) { + fail(`trusted OpenShell v${release.version} supervisor record is invalid`); + } +} + +function trustedRelease(version: string): OpenShellReleaseTrust { + const duplicateVersions = TRUSTED_OPENSHELL_RELEASES.map((release) => release.version).filter( + (candidate, index, versions) => versions.indexOf(candidate) !== index, + ); + if (duplicateVersions.length > 0) { + fail( + `trusted OpenShell release records contain duplicate versions: ${[ + ...new Set(duplicateVersions), + ].join(", ")}`, + ); + } + for (const release of TRUSTED_OPENSHELL_RELEASES) validateTrustedRelease(release); + const release = TRUSTED_OPENSHELL_RELEASES.find((candidate) => candidate.version === version); + if (!release) fail(`OpenShell v${version} is not in the base-trusted release records`); + return release; +} + // Pull-request CI executes this parser from a trusted checkout while these // paths point into the mutable PR tree. Reject links and special files before // reading, verify that the opened file is still the one inspected, and cap the @@ -737,20 +986,27 @@ function extractSandboxBuildPins(source: string): SandboxBuildPin[] { // prerequisite that adds only exact digest/version pairs from a reviewed release. // removalCondition: remove this check only when the standalone sandbox exposes // a reliable authenticated build identity on every supported host. -function assertTrustedSandboxBuildPins(pins: SandboxBuildPin[], releaseVersion: string): void { +function assertTrustedSandboxBuildPins( + pins: SandboxBuildPin[], + release: OpenShellReleaseTrust, +): void { const pinKey = (pin: SandboxBuildPin): string => `${pin.version}:${pin.sha256}`; - const trustedKeys = new Set(TRUSTED_SANDBOX_BUILD_PINS.map(pinKey)); + const trustedPins = TRUSTED_OPENSHELL_RELEASES.flatMap((trustedRelease) => + trustedRelease.sandboxBuilds.map((pin) => ({ ...pin, version: trustedRelease.version })), + ); + const trustedKeys = new Set(trustedPins.map(pinKey)); const actualKeys = new Set(pins.map(pinKey)); - const selectedPins = TRUSTED_SANDBOX_BUILD_PINS.filter((pin) => pin.version === releaseVersion); + const selectedPins = release.sandboxBuilds; if (selectedPins.length === 0) { fail( - `no base-trusted standalone sandbox binary identities exist for release ${releaseVersion}`, + `no base-trusted standalone sandbox binary identities exist for release ${release.version}`, ); } - const missing = TRUSTED_SANDBOX_BUILD_PINS.filter( - (pin) => (pin.required || pin.version === releaseVersion) && !actualKeys.has(pinKey(pin)), - ) + const missing = trustedPins + .filter( + (pin) => (pin.required || pin.version === release.version) && !actualKeys.has(pinKey(pin)), + ) .map(pinKey) .sort(); const unexpected = pins @@ -765,7 +1021,10 @@ function assertTrustedSandboxBuildPins(pins: SandboxBuildPin[], releaseVersion: } } -function extractSupervisorManifestPins(source: string): SupervisorManifestPin[] { +function extractSupervisorManifestPins( + source: string, + trustedSupervisor: TrustedSupervisorManifest, +): SupervisorManifestPin[] { const mapHeader = "const OPENSHELL_SUPERVISOR_MANIFEST_DIGESTS: Readonly> = {\n"; const mapStart = source.indexOf(mapHeader); @@ -783,7 +1042,7 @@ function extractSupervisorManifestPins(source: string): SupervisorManifestPin[] const match = /^ "([0-9]+\.[0-9]+\.[0-9]+)": "(sha256:[a-f0-9]{64})",$/u.exec(line); if (!match) fail("supervisor runtime manifest digest map must contain only literal pins"); return { - image: OPENSHELL_SUPERVISOR_IMAGE, + image: trustedSupervisor.image, manifestDigest: match[2] ?? "", version: match[1] ?? "", }; @@ -803,10 +1062,10 @@ function extractSupervisorManifestPins(source: string): SupervisorManifestPin[] mapEnd, )}`; const templateSha256 = createHash("sha256").update(normalized).digest("hex"); - if (!TRUSTED_SUPERVISOR_RUNTIME_TEMPLATE_SHA256_ALLOWLIST.includes(templateSha256)) { + if (!trustedSupervisor.runtimeTemplateSha256.includes(templateSha256)) { fail( `supervisor runtime operational template is not base-trusted; ` + - `expected_sha256=[${TRUSTED_SUPERVISOR_RUNTIME_TEMPLATE_SHA256_ALLOWLIST.join(", ")}], ` + + `expected_sha256=[${trustedSupervisor.runtimeTemplateSha256.join(", ")}], ` + `actual_sha256=${templateSha256}`, ); } @@ -828,24 +1087,28 @@ function extractSupervisorManifestPins(source: string): SupervisorManifestPin[] // manifest directly drives the runtime selector without PR-authored identity data. function assertTrustedSupervisorManifestPins( pins: SupervisorManifestPin[], - releaseVersion: string, + release: OpenShellReleaseTrust, ): void { const pinKey = (pin: SupervisorManifestPin): string => `${pin.image}|${pin.version}|${pin.manifestDigest}`; - const trustedKeys = new Set(TRUSTED_SUPERVISOR_MANIFEST_PINS.map(pinKey)); - const actualKeys = new Set(pins.map(pinKey)); - const selectedPins = TRUSTED_SUPERVISOR_MANIFEST_PINS.filter( - (pin) => pin.version === releaseVersion, + const trustedPins = TRUSTED_OPENSHELL_RELEASES.flatMap((trustedRelease) => + trustedRelease.supervisor + ? [{ ...trustedRelease.supervisor, version: trustedRelease.version }] + : [], ); + const trustedKeys = new Set(trustedPins.map(pinKey)); + const actualKeys = new Set(pins.map(pinKey)); + const selectedPins = release.supervisor ? [release.supervisor] : []; if (selectedPins.length !== 1) { fail( - `release ${releaseVersion} must have exactly one base-trusted supervisor manifest identity`, + `release ${release.version} must have exactly one base-trusted supervisor manifest identity`, ); } - const missing = TRUSTED_SUPERVISOR_MANIFEST_PINS.filter( - (pin) => (pin.required || pin.version === releaseVersion) && !actualKeys.has(pinKey(pin)), - ) + const missing = trustedPins + .filter( + (pin) => (pin.required || pin.version === release.version) && !actualKeys.has(pinKey(pin)), + ) .map(pinKey) .sort(); const unexpected = pins @@ -1117,16 +1380,11 @@ export function extractInstallerPins(source: string, options: ExtractOptions): I if (pins.length === 0) { fail(`${options.functionName} contains no versioned pins`); } - const releaseVersions = [...new Set(pins.map((pin) => pin.releaseVersion))].sort(); - if (releaseVersions.length !== 1) { - fail( - `${options.functionName} must contain exactly one release version, found ${releaseVersions.join(", ")}`, - ); - } - + const releaseVersions = [...new Set(pins.map((pin) => pin.releaseVersion))]; const duplicateAssets = pins - .map((pin) => pin.asset) - .filter((asset, index, assets) => assets.indexOf(asset) !== index); + .map((pin) => `${pin.releaseVersion}:${pin.asset}`) + .filter((asset, index, assets) => assets.indexOf(asset) !== index) + .map((asset) => (releaseVersions.length === 1 ? asset.slice(asset.indexOf(":") + 1) : asset)); if (duplicateAssets.length > 0) { fail( `${options.functionName} contains duplicate assets: ${[...new Set(duplicateAssets)].join(", ")}`, @@ -1142,7 +1400,7 @@ function parseCliOptions(argv: string[]): CliOptions { const value = argv[index + 1] ?? ""; if (!option.startsWith("--") || !value) { fail( - "usage: extract-installer-pins.mts --blueprint PATH --installer PATH --brev-installer PATH --supervisor-runtime PATH [--format json|tsv]", + "usage: extract-installer-pins.mts --blueprint PATH --installer PATH --brev-installer PATH --supervisor-runtime PATH [--format json|release-tsv|tsv]", ); } if (values.has(option)) { @@ -1169,7 +1427,7 @@ function parseCliOptions(argv: string[]): CliOptions { !installer || !brevInstaller || !supervisorRuntime || - (format !== "json" && format !== "tsv") + (format !== "json" && format !== "release-tsv" && format !== "tsv") ) { fail(`invalid CLI options${unknownOptions.length > 0 ? `: ${unknownOptions.join(", ")}` : ""}`); } @@ -1193,20 +1451,41 @@ function runCli(): void { functionName: "openshell_cli_pinned_sha256", sourceLabel: "Brev launchable", }); - assertExactAssetSet(installerPins, EXPECTED_INSTALLER_ASSETS, "installer pin table"); + const installerReleaseVersions = [ + ...new Set(installerPins.map((pin) => pin.releaseVersion)), + ].sort(); + for (const version of installerReleaseVersions) { + assertExactAssetSet( + installerPins.filter((pin) => pin.releaseVersion === version), + EXPECTED_INSTALLER_ASSETS, + installerReleaseVersions.length === 1 + ? "installer pin table" + : `installer pin table for ${version}`, + ); + } assertExactAssetSet(brevPins, EXPECTED_BREV_ASSETS, "Brev pin table"); - const pins = [...installerPins, ...brevPins]; - const releaseVersions = [...new Set(pins.map((pin) => pin.releaseVersion))].sort(); - if (releaseVersions.length !== 1) { + const brevReleaseVersions = [...new Set(brevPins.map((pin) => pin.releaseVersion))].sort(); + if (brevReleaseVersions.length !== 1) { fail( - `installer and Brev launchable pin tables must use the same release version, found ${releaseVersions.join(", ")}`, + `Brev launchable pin table must contain exactly one release version, found ${brevReleaseVersions.join(", ")}`, ); } - const releaseVersion = releaseVersions[0] ?? fail("installer pin tables contain no release"); + const releaseVersion = brevReleaseVersions[0] ?? fail("Brev pin table contains no release"); + if (!installerReleaseVersions.includes(releaseVersion)) { + fail(`installer pin table has no assets for selected release ${releaseVersion}`); + } + const pins = [...installerPins, ...brevPins]; + const release = trustedRelease(releaseVersion); + const installerReleases = installerReleaseVersions.map(trustedRelease); const sandboxBuildPins = extractSandboxBuildPins(installerSource); - assertTrustedSandboxBuildPins(sandboxBuildPins, releaseVersion); - const supervisorManifestPins = extractSupervisorManifestPins(supervisorRuntimeSource); - assertTrustedSupervisorManifestPins(supervisorManifestPins, releaseVersion); + assertTrustedSandboxBuildPins(sandboxBuildPins, release); + const supervisor = + release.supervisor ?? + fail( + `release ${release.version} must have exactly one base-trusted supervisor manifest identity`, + ); + const supervisorManifestPins = extractSupervisorManifestPins(supervisorRuntimeSource, supervisor); + assertTrustedSupervisorManifestPins(supervisorManifestPins, release); assertTrustedTemplate( installerSource, ["openshell_pinned_sha256", "pinned_sandbox_build_version"], @@ -1215,14 +1494,14 @@ function runCli(): void { /^MAX_VERSION="([0-9]+\.[0-9]+\.[0-9]+)"$/gm, /^DEV_MIN_VERSION="([0-9]+\.[0-9]+\.[0-9]+)"$/gm, ], - TRUSTED_INSTALLER_TEMPLATE_SHA256_ALLOWLIST, + release.installerTemplateSha256, "installer", ); assertTrustedTemplate( brevInstallerSource, ["openshell_cli_pinned_sha256"], [/^\s*stable\s*\|\s*auto\)\s*OPENSHELL_VERSION="v([0-9]+\.[0-9]+\.[0-9]+)"\s*;;\s*$/gm], - TRUSTED_BREV_TEMPLATE_SHA256_ALLOWLIST, + release.brevTemplateSha256, "Brev launchable", ); for (const [label, runtimeVersion] of [ @@ -1240,6 +1519,27 @@ function runCli(): void { process.stdout.write(`${JSON.stringify(pins)}\n`); return; } + if (options.format === "release-tsv") { + process.stdout.write( + [ + ...installerReleases.flatMap((installerRelease) => + installerRelease.manifests.map( + (manifest) => + `manifest\t${installerRelease.version}\tOpenShell release\t${manifest.asset}\t${manifest.sha256}`, + ), + ), + ...installerReleases.map( + (installerRelease) => + `formula\t${installerRelease.version}\t${installerRelease.formula.url}\t${installerRelease.formula.asset}\t${installerRelease.formula.sha256}`, + ), + ...pins.map( + (pin) => `pin\t${pin.releaseVersion}\t${pin.source}\t${pin.asset}\t${pin.sha256}`, + ), + ].join("\n"), + ); + process.stdout.write("\n"); + return; + } process.stdout.write( pins .map((pin) => `${pin.releaseVersion}\t${pin.source}\t${pin.asset}\t${pin.sha256}`) diff --git a/test/dependency-pins-check.test.ts b/test/dependency-pins-check.test.ts index b6608a83456..83ffe987b69 100644 --- a/test/dependency-pins-check.test.ts +++ b/test/dependency-pins-check.test.ts @@ -48,15 +48,25 @@ function writeFixture(root: string, overrides: FixtureOverrides = {}): void { const hermesSemver = overrides.hermesSemver ?? HERMES_SEMVER; const credentialManifestName = `openshell-child-visible-credentials.v${openshellMax}.json`; const credentialVersion = overrides.credentialVersion ?? openshellMax; - const installerHashVersions = [ - overrides.installerHashExtraVersion, - overrides.installerHashVersion ?? openshellMax, + const installerTrustVersions = [ + overrides.releaseTrustExtraVersion, + overrides.releaseTrustVersion ?? openshellMax, ].filter((version): version is string => version !== undefined); - const installerHashAllowlist = installerHashVersions - .flatMap((version) => - OPENSHELL_RELEASE_MANIFESTS.filter( - (manifest) => manifest !== overrides.installerHashOmitManifest, - ).map((manifest) => ` "${version}|${manifest}|${MANIFEST_SHA256}"`), + const installerTrustRecords = installerTrustVersions + .map( + (version) => ` { + manifests: [ +${OPENSHELL_RELEASE_MANIFESTS.filter((manifest) => manifest !== overrides.releaseTrustOmitManifest) + .map( + (manifest) => ` { + asset: "${manifest}", + sha256: "${MANIFEST_SHA256}", + },`, + ) + .join("\n")} + ], + version: "${version}", + },`, ) .join("\n"); @@ -70,10 +80,11 @@ MIN_VERSION="${overrides.installerMin ?? openshellMin}" MAX_VERSION="${overrides.installerMax ?? openshellMax}" PIN_VERSION="${overrides.installerPinExpression ?? "$MAX_VERSION"}" `, - "scripts/check-installer-hash.sh": ` -readonly -a OPENSHELL_RELEASE_MANIFEST_ALLOWLIST=( -${installerHashAllowlist} -) + "scripts/checks/extract-installer-pins.mts": ` +type OpenShellReleaseTrust = unknown; +const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [ +${installerTrustRecords} +] as const; `, "scripts/brev-launchable-ci-cpu.sh": ` case "$NEMOCLAW_REF" in @@ -198,10 +209,10 @@ describe("dependency pin drift check", () => { ); }); - it("accepts the blueprint maximum in a multi-release manifest allowlist (#5242)", () => { + it("accepts the blueprint maximum in multiple release trust records (#5242)", () => { withFixture( "nemoclaw-dependency-pins-multi-release-", - { installerHashExtraVersion: "1.2.3" }, + { releaseTrustExtraVersion: "1.2.3" }, (root) => expect(verifyDependencyPins(root)).toEqual([]), ); }); @@ -213,7 +224,7 @@ describe("dependency pin drift check", () => { installerMin: "1.2.2", installerMax: "1.2.3", installerPinExpression: "1.2.4", - installerHashVersion: "1.2.3", + releaseTrustVersion: "1.2.3", fallbackVersion: "1.2.3", minFallbackVersion: "1.2.2", supervisorMapVersion: "1.2.3", @@ -239,7 +250,7 @@ describe("dependency pin drift check", () => { "OpenShell installer MIN_VERSION: expected 1.2.3, found 1.2.2", "OpenShell installer MAX_VERSION: expected 1.2.4, found 1.2.3", "OpenShell installer PIN_VERSION: expected $MAX_VERSION, found 1.2.4", - "OpenShell release-manifest allowlist: expected one complete entry for 1.2.4", + "OpenShell release trust: expected one complete record for 1.2.4", "OpenShell supported fallback version: expected 1.2.4, found 1.2.3", "OpenShell minimum fallback version: expected 1.2.3, found 1.2.2", "OpenShell supervisor manifest digest map: expected a reference to 1.2.4", @@ -292,13 +303,13 @@ describe("dependency pin drift check", () => { }); }); - it("rejects an incomplete manifest allowlist entry for the blueprint maximum (#5242)", () => { + it("rejects an incomplete release trust record for the blueprint maximum (#5242)", () => { withFixture( - "nemoclaw-dependency-pins-incomplete-openshell-allowlist-", - { installerHashOmitManifest: "openshell-sandbox-checksums-sha256.txt" }, + "nemoclaw-dependency-pins-incomplete-openshell-trust-", + { releaseTrustOmitManifest: "openshell-sandbox-checksums-sha256.txt" }, (root) => { expect(verifyDependencyPins(root)).toEqual([ - "OpenShell release-manifest allowlist: expected one complete entry for 1.2.4", + "OpenShell release trust: expected one complete record for 1.2.4", ]); }, ); diff --git a/test/helpers/openshell-release-fixtures.ts b/test/helpers/openshell-release-fixtures.ts index 2671ad1c1ee..bd8fbdca846 100644 --- a/test/helpers/openshell-release-fixtures.ts +++ b/test/helpers/openshell-release-fixtures.ts @@ -11,6 +11,12 @@ export const V00103_SANDBOX_BUILD_DIGESTS = [ ] as const; export const V00103_SUPERVISOR_MANIFEST_DIGEST = "sha256:96228f110362ffd415bb12d3b7f584063c3c52c0c93f3ccf59faada1dc2dd5d3"; +export const V00106_SANDBOX_BUILD_DIGESTS = [ + "0031c6b257a23ecc1a2333153918324f3af0005e68abde388858d682ec646c55", + "019301ec8618abbed8135e8d39dde7bea47e5e92813bbc17768550de34db59f8", +] as const; +export const V00106_SUPERVISOR_MANIFEST_DIGEST = + "sha256:722f44669722961b7f432b0b81de25b91a58f34a61d6403bef967acaf2b3af01"; export const V0099_CHECKSUM_MANIFESTS = new Map([ [ @@ -152,3 +158,50 @@ export const V00103_ASSET_DIGESTS = new Map([ ), ["openshell.rb", "95a290f0e0e2f57d7d46ba9171fca6e99e5226875cd12e12391b7338f6c219f9"], ]); + +export const V00106_CHECKSUM_MANIFESTS = new Map([ + [ + "openshell-checksums-sha256.txt", + `d1a885a91b3e5aaa006c36aca95dc78bed0638c1ba1a79b55f1da93211b8a0a0 openshell-x86_64-unknown-linux-musl.tar.gz +ce981904ae8febd9cd6b3fbceb04e1dcfb48da6042bac08eadf0c2211f83fe55 openshell-aarch64-unknown-linux-musl.tar.gz +969493205e3d3462226ff613eaba0b9cde0f582e3026294169d533d41e87c905 openshell-aarch64-apple-darwin.tar.gz +1c86ad15a65b5997857443ffd737d549fe155432a5053b6102fd76829efc57aa openshell-driver-vm-x86_64-unknown-linux-gnu.tar.gz +b7b0fd93ce95a435b955d34b023128499ca8fc4b98228a0282c677fdb0168a01 openshell-driver-vm-aarch64-unknown-linux-gnu.tar.gz +a0ef279f4ab0998472feff0e5dea4cab0ae0906693472e5d0bfff6d331079b08 openshell-driver-vm-aarch64-apple-darwin.tar.gz +95ecf3919edc5f58939fee4acccc9728d6b5dee5cfd4ad652d132e6fa46937fd openshell_0.0.106-1_amd64.deb +4cafda6d703e5cd6a37dd6adc7da1877b5f99fb21c76786bc1067896260abfd1 openshell_0.0.106-1_arm64.deb +8512f4c1ec51fff1dfdf06363eb5355e7f7c5a57814c8244217bc9b4116c07f0 openshell-0.0.106-1.fc44.aarch64.rpm +59655e9233ebf90573ddfe066d313b0f0d1f5c4227800bc121886c168ae9628e openshell-0.0.106-1.fc44.x86_64.rpm +ba398a4b378e3071ad371cbf4c1f8730395288f6206657bf4f65220bfb8d31d2 openshell-gateway-0.0.106-1.fc44.aarch64.rpm +704112743a2f9e91bf2a749219da00da101753e80a10c98941ebd66b898a3904 openshell-gateway-0.0.106-1.fc44.x86_64.rpm +cd59c6ca6a3745a2afba1198ec390efbaf94d53b36a578467425643ff6195da0 openshell-0.0.106-py3-none-macosx_13_0_arm64.whl +7ece4d0a9305f0ab3cc902d9acdea4e0f2acc4952c2af9415ebf158123d2e8a0 openshell-0.0.106-py3-none-manylinux_2_39_aarch64.whl +c9938ebd64afdfcff8818ab696ce13e8398a89a71216a2bd688198d4497c3b95 openshell-0.0.106-py3-none-manylinux_2_39_x86_64.whl +`, + ], + [ + "openshell-gateway-checksums-sha256.txt", + `b7760cb752a4363c2f21d32298dd0c683dc438f6edfd16c2e4242bc0baefbb7c openshell-gateway-x86_64-unknown-linux-gnu.tar.gz +22b7781249e3487085694d0f0f3797a0e549018b81144cd24b2f1118c730d1c7 openshell-gateway-aarch64-unknown-linux-gnu.tar.gz +de8f90db9dd0d3b47855b2b6d2542660730917bd1249e53140300990a8690b94 openshell-gateway-aarch64-apple-darwin.tar.gz +`, + ], + [ + "openshell-sandbox-checksums-sha256.txt", + `559b8aaad3a8eeab45c511e7de531d9baa98a311282dcb0c2c5f38cc2d4ca355 openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz +5e5d758d53c6abc6d7a936be907dafa9dfce10423289536f39b50abe294dfafd openshell-sandbox-aarch64-unknown-linux-gnu.tar.gz +`, + ], +]); +export const V00106_ASSET_DIGESTS = new Map([ + ...[...V00106_CHECKSUM_MANIFESTS.values()].flatMap((contents) => + contents + .trim() + .split("\n") + .map((line) => { + const [digest, asset] = line.split(/\s+/); + return [asset, digest] as const; + }), + ), + ["openshell.rb", "f0f86519e227b3b326431410058ba690b1a7b83e5af7384014e4b96283d3a642"], +]); diff --git a/test/installer-hash-check.test.ts b/test/installer-hash-check.test.ts index e7d1f6d3672..d0d089ae421 100644 --- a/test/installer-hash-check.test.ts +++ b/test/installer-hash-check.test.ts @@ -20,6 +20,10 @@ import { V00103_CHECKSUM_MANIFESTS, V00103_SANDBOX_BUILD_DIGESTS, V00103_SUPERVISOR_MANIFEST_DIGEST, + V00106_ASSET_DIGESTS, + V00106_CHECKSUM_MANIFESTS, + V00106_SANDBOX_BUILD_DIGESTS, + V00106_SUPERVISOR_MANIFEST_DIGEST, } from "./helpers/openshell-release-fixtures"; const REPO_ROOT = path.join(import.meta.dirname, ".."); @@ -101,6 +105,7 @@ type FixtureMode = | "brev-mismatch" | "brev-sha-command-bypass" | "complete" + | "complete-multiple-installer-versions" | "duplicate-brev-pin" | "duplicate-installer-pin" | "failure" @@ -147,10 +152,12 @@ type FixtureMode = | "pr-parser-bypass" | "brev-stable-version-drift" | "runtime-consumers-newer-than-tables" + | "reviewed-release-cohorts" + | "reviewed-release-cohorts-url-drift" + | "secondary-installer-version-mismatch" | "symlink-installer-input" | "symlink-scripts-parent" - | "duplicate-trusted-formula" - | "trusted-sandbox-alternate-version" + | "duplicate-trusted-release" | "trusted-formula-mismatch"; type PinFormatting = | "canonical" @@ -363,6 +370,68 @@ const INSTALLER_MUTATIONS: Partial strin source.replace('MAX_VERSION="0.0.72"', 'MAX_VERSION="0.0.85"'), }; +function addInstallerReleaseTable( + source: string, + version: string, + assetDigests: ReadonlyMap, +): string { + const functionStart = source.indexOf("openshell_pinned_sha256() {"); + const fallback = " *)\n return 1\n ;;"; + const fallbackStart = source.indexOf(fallback, functionStart); + expect(functionStart, "installer pin function start").not.toBe(-1); + expect(fallbackStart, "installer pin function fallback").not.toBe(-1); + const cases = INSTALLER_ASSETS.map((asset) => { + const digest = assetDigests.get(asset) ?? "missing"; + return ` v${version}:${asset}) + printf '%s\\n' "${digest}" + ;;`; + }).join("\n"); + return `${source.slice(0, fallbackStart)}${cases}\n${source.slice(fallbackStart)}`; +} + +function applyReviewedReleaseCohorts(source: string): string { + const currentComment = `# regressionTest: test/install-openshell-version-check.test.ts exercises all +# nine mappings, and scripts/check-installer-hash.sh compares them with the +# GitHub release API on every PR, main push, weekly run, and manual dispatch. +# removalCondition: remove these entries only when NemoClaw drops that +# supported release or replaces them with independently verified newer pins.`; + const reviewedComment = `# OpenShell 0.0.101 is the supported product cohort. OpenShell 0.0.106 is the +# qualification-only cohort. The base-trusted verifier validates each complete +# nine-asset release cohort independently against GitHub release metadata. +# removalCondition: remove a cohort when its product support or qualification +# ends, or replace it with an independently verified release cohort.`; + expect(source.includes(currentComment) || source.includes(reviewedComment)).toBe(true); + return addInstallerReleaseTable( + source.replace(currentComment, reviewedComment), + "0.0.106", + V00106_ASSET_DIGESTS, + ); +} + +INSTALLER_MUTATIONS["reviewed-release-cohorts"] = applyReviewedReleaseCohorts; +INSTALLER_MUTATIONS["reviewed-release-cohorts-url-drift"] = (source) => + applyReviewedReleaseCohorts(source).replace( + "https://github.com/NVIDIA/OpenShell/releases/download/${RELEASE_TAG}/$name", + "https://attacker.invalid/openshell/${RELEASE_TAG}/$name", + ); + +for (const mode of [ + "complete-multiple-installer-versions", + "secondary-installer-version-mismatch", +] as const) { + INSTALLER_MUTATIONS[mode] = (source) => { + const withSecondRelease = addInstallerReleaseTable(source, "0.0.106", V00106_ASSET_DIGESTS); + return mode === "secondary-installer-version-mismatch" + ? withSecondRelease.replace( + `v0.0.106:${ASSETS[0]}) + printf '%s\\n' "${V00106_ASSET_DIGESTS.get(ASSETS[0]) ?? "missing"}"`, + `v0.0.106:${ASSETS[0]}) + printf '%s\\n' "${"0".repeat(64)}"`, + ) + : withSecondRelease; + }; +} + type InputMutationContext = { blueprint: string; brevInstaller: string; @@ -436,80 +505,84 @@ const CHECKSUM_MANIFESTS_BY_VERSION = new Map([ ["0.0.99", V0099_CHECKSUM_MANIFESTS], ["0.0.101", V00101_CHECKSUM_MANIFESTS], ["0.0.103", V00103_CHECKSUM_MANIFESTS], + ["0.0.106", V00106_CHECKSUM_MANIFESTS], ]); const ASSET_DIGESTS_BY_VERSION = new Map([ ["0.0.99", V0099_ASSET_DIGESTS], ["0.0.101", V00101_ASSET_DIGESTS], ["0.0.103", V00103_ASSET_DIGESTS], + ["0.0.106", V00106_ASSET_DIGESTS], ]); -const CHECKER_MUTATIONS: Partial string>> = { - "allowlisted-alternate-version": (source) => { - const alternateEntries = [...CHECKSUM_MANIFESTS.entries()] - .map( - ([manifest, contents]) => - ` "9.9.9|${manifest}|${createHash("sha256").update(contents).digest("hex")}"`, - ) - .join("\n"); - const manifests = source.replace( - "readonly -a OPENSHELL_RELEASE_MANIFEST_ALLOWLIST=(\n", - `readonly -a OPENSHELL_RELEASE_MANIFEST_ALLOWLIST=(\n${alternateEntries}\n`, - ); - const formula = manifests.replace( - "readonly -a OPENSHELL_RELEASE_FORMULA_ALLOWLIST=(\n", - `readonly -a OPENSHELL_RELEASE_FORMULA_ALLOWLIST=(\n "9.9.9|openshell.rb|https://github.com/NVIDIA/OpenShell/releases/download/v9.9.9/openshell.rb|${FORMULA_DIGEST}"\n`, - ); - expect(manifests !== source && formula !== manifests, "alternate anchors").toBe(true); - return formula; +const trustAlternateRelease = (source: string): string => { + const digests = SYNTHETIC_SANDBOX_BUILD_DIGESTS; + const manifests = [...CHECKSUM_MANIFESTS.entries()] + .map( + ([asset, contents]) => ` { + asset: "${asset}", + sha256: "${createHash("sha256").update(contents).digest("hex")}", + },`, + ) + .join("\n"); + const marker = "const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [\n"; + const result = source.replace( + marker, + `${marker} { + brevTemplateSha256: [ + "c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a", + ], + formula: { + asset: "openshell.rb", + sha256: "${FORMULA_DIGEST}", + url: "https://github.com/NVIDIA/OpenShell/releases/download/v9.9.9/openshell.rb", + }, + installerTemplateSha256: [ + "6226811887cc5c1a721a96fbf062f5ce5f75b09d3a8a1de49ed4dadc3236eb0c", + ], + manifests: [ +${manifests} + ], + sandboxBuilds: [ + { required: false, sha256: "${digests[0]}" }, + { required: false, sha256: "${digests[1]}" }, + ], + supervisor: { + image: "ghcr.io/nvidia/openshell/supervisor", + manifestDigest: "${SYNTHETIC_SUPERVISOR_MANIFEST_DIGEST}", + required: false, + runtimeTemplateSha256: [ + "c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068", + ], + }, + version: "9.9.9", }, - "duplicate-trusted-formula": (source) => - source.replace( - "readonly -a OPENSHELL_RELEASE_FORMULA_ALLOWLIST=(\n", - `readonly -a OPENSHELL_RELEASE_FORMULA_ALLOWLIST=(\n "0.0.72|openshell.rb|https://github.com/NVIDIA/OpenShell/releases/download/v0.0.72/openshell.rb|${FORMULA_DIGEST}"\n`, - ), +`, + ); + expect(result, "alternate release trust anchor").not.toBe(source); + return result; +}; +const removeFirstReleaseFormula = (source: string): string => + source.replace( + / formula: \{\n asset: "openshell\.rb",\n sha256: "[a-f0-9]{64}",\n url: "https:\/\/github\.com\/NVIDIA\/OpenShell\/releases\/download\/v0\.0\.72\/openshell\.rb",\n \},/u, + " formula: undefined as never,", + ); +const PARSER_MUTATIONS: Partial string>> = { + "allowlisted-alternate-version": trustAlternateRelease, + "duplicate-trusted-release": (source) => + source.replace(' version: "0.0.82",', ' version: "0.0.72",'), "incomplete-trusted-allowlist": (source) => source.replace( - /^\s*"0\.0\.72\|openshell-sandbox-checksums-sha256\.txt\|[a-f0-9]{64}"\s*$/m, + / \{\n asset: "openshell-sandbox-checksums-sha256\.txt",\n sha256: "d38507501338576437cf3e554df71fefe927dc0d72758f88e260069527ed9ccc",\n \},/u, "", ), - "malformed-trusted-formula": (source) => - source.replace(`v0.0.72/openshell.rb|${FORMULA_DIGEST}`, "v0.0.72/openshell.rb|invalid"), + "malformed-trusted-formula": (source) => source.replace(FORMULA_DIGEST, "invalid"), "mismatched-trusted-formula-url": (source) => source.replace( "https://github.com/NVIDIA/OpenShell/releases/download/v0.0.72/openshell.rb", "https://attacker.invalid/openshell.rb", ), - "missing-trusted-formula": (source) => - source.replace( - /^\s*"0\.0\.72\|openshell\.rb\|https:\/\/github\.com\/NVIDIA\/OpenShell\/releases\/download\/v0\.0\.72\/openshell\.rb\|[a-f0-9]{64}"\s*$/m, - "", - ), + "missing-trusted-formula": removeFirstReleaseFormula, "trusted-formula-mismatch": (source) => source.replace(FORMULA_DIGEST, "0".repeat(64)), }; -const trustAlternateRelease = (source: string): string => { - const digests = SYNTHETIC_SANDBOX_BUILD_DIGESTS; - const sandbox = source.replace( - "const TRUSTED_SANDBOX_BUILD_PINS: readonly TrustedSandboxBuildPin[] = [\n", - `const TRUSTED_SANDBOX_BUILD_PINS: readonly TrustedSandboxBuildPin[] = [ - { required: false, sha256: "${digests[0]}", version: "9.9.9" }, - { required: false, sha256: "${digests[1]}", version: "9.9.9" }, -`, - ); - return sandbox.replace( - "const TRUSTED_SUPERVISOR_MANIFEST_PINS: readonly TrustedSupervisorManifestPin[] = [\n", - `const TRUSTED_SUPERVISOR_MANIFEST_PINS: readonly TrustedSupervisorManifestPin[] = [ - { - image: OPENSHELL_SUPERVISOR_IMAGE, - manifestDigest: "${SYNTHETIC_SUPERVISOR_MANIFEST_DIGEST}", - required: false, - version: "9.9.9", - }, -`, - ); -}; -const PARSER_MUTATIONS: Partial string>> = { - "allowlisted-alternate-version": trustAlternateRelease, - "trusted-sandbox-alternate-version": trustAlternateRelease, -}; const tempDirs: string[] = []; afterEach(() => { @@ -579,6 +652,46 @@ 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 renderInstallerTemplate(openshellVersion: string, pinFunction: string): string { const selected = INSTALLER_TEMPLATE.replace( /^MIN_VERSION="[0-9]+\.[0-9]+\.[0-9]+"$/m, @@ -595,27 +708,31 @@ function renderInstallerTemplate(openshellVersion: string, pinFunction: string): "openshell_checksum_line() {", pinFunction, ); - const sandboxFunctionStart = withPinFunction.indexOf("pinned_sandbox_build_version() {"); - const sandboxFunctionEnd = withPinFunction.indexOf( + const operationalTemplate = + openshellVersion === "0.0.106" ? addV00106OperationalTrust(withPinFunction) : withPinFunction; + const sandboxFunctionStart = operationalTemplate.indexOf("pinned_sandbox_build_version() {"); + const sandboxFunctionEnd = operationalTemplate.indexOf( "\ncomponent_build_version() {", sandboxFunctionStart, ); expect(sandboxFunctionStart, "sandbox build map template start").not.toBe(-1); expect(sandboxFunctionEnd, "sandbox build map template end").not.toBe(-1); - const sandboxFunction = withPinFunction.slice(sandboxFunctionStart, sandboxFunctionEnd); + const sandboxFunction = operationalTemplate.slice(sandboxFunctionStart, sandboxFunctionEnd); const hasSandboxBuild = sandboxFunction.includes(`printf '%s\\n' "${openshellVersion}"`); const selectedDigests = openshellVersion === "0.0.101" ? V00101_SANDBOX_BUILD_DIGESTS : openshellVersion === "0.0.103" ? V00103_SANDBOX_BUILD_DIGESTS - : openshellVersion === "9.9.9" - ? SYNTHETIC_SANDBOX_BUILD_DIGESTS - : undefined; + : openshellVersion === "0.0.106" + ? V00106_SANDBOX_BUILD_DIGESTS + : openshellVersion === "9.9.9" + ? SYNTHETIC_SANDBOX_BUILD_DIGESTS + : undefined; expect(hasSandboxBuild || selectedDigests, `sandbox fixture ${openshellVersion}`).toBeTruthy(); return hasSandboxBuild - ? withPinFunction - : addSandboxBuildPins(withPinFunction, openshellVersion, selectedDigests!); + ? operationalTemplate + : addSandboxBuildPins(operationalTemplate, openshellVersion, selectedDigests!); } function renderBrevTemplate(openshellVersion: string, pinFunction: string): string { @@ -638,9 +755,11 @@ function renderSupervisorRuntime(openshellVersion: string): string { const manifestDigest = openshellVersion === "0.0.103" ? V00103_SUPERVISOR_MANIFEST_DIGEST - : openshellVersion === "9.9.9" - ? SYNTHETIC_SUPERVISOR_MANIFEST_DIGEST - : undefined; + : openshellVersion === "0.0.106" + ? V00106_SUPERVISOR_MANIFEST_DIGEST + : openshellVersion === "9.9.9" + ? SYNTHETIC_SUPERVISOR_MANIFEST_DIGEST + : undefined; expect( hasManifestIdentity || manifestDigest, `supervisor fixture ${openshellVersion}`, @@ -731,6 +850,22 @@ while [ "$#" -gt 0 ]; do esac done case "$url" in + *releases/download/v0.0.106/*) + case "\${url##*/}" in + openshell-checksums-sha256.txt) + printf '%s' '${V00106_CHECKSUM_MANIFESTS.get("openshell-checksums-sha256.txt")}' >"$output" + ;; + openshell-gateway-checksums-sha256.txt) + printf '%s' '${V00106_CHECKSUM_MANIFESTS.get("openshell-gateway-checksums-sha256.txt")}' >"$output" + ;; + openshell-sandbox-checksums-sha256.txt) + printf '%s' '${V00106_CHECKSUM_MANIFESTS.get("openshell-sandbox-checksums-sha256.txt")}' >"$output" + ;; + openshell.rb) + printf '%s\n' '# OpenShell v0.0.106 formula fixture' >"$output" + ;; + esac + ;; *releases/download/v${openshellVersion}/*) case "\${NEMOCLAW_TEST_CURL_MODE}" in failure) exit 22 ;; @@ -771,7 +906,13 @@ case "\${1:-}" in */openshell.rb) case "\${NEMOCLAW_TEST_CURL_MODE:-}" in formula-mismatch | formula-self-authorized) digest='${"0".repeat(64)}' ;; - *) digest='${assetDigests.get(FORMULA_ASSET)}' ;; + *) + if grep -q 'OpenShell v0.0.106' "$1"; then + digest='${V00106_ASSET_DIGESTS.get(FORMULA_ASSET)}' + else + digest='${assetDigests.get(FORMULA_ASSET)}' + fi + ;; esac printf '%s %s\\n' "$digest" "$1" ;; @@ -823,11 +964,6 @@ function runFixture( : fs.readFileSync(targetChecker, "utf8"), ); const checker = trustedChecker ? trustedCheckerPath : targetChecker; - const checkerSource = fs.readFileSync(checker, "utf8"); - const mutateChecker = CHECKER_MUTATIONS[mode]; - const checkerResult = mutateChecker?.(checkerSource) ?? checkerSource; - expect(checkerResult === checkerSource, `checker ${mode}`).toBe(mutateChecker === undefined); - fs.writeFileSync(checker, checkerResult); const installer = path.join(fixtureRoot, "scripts", "install-openshell.sh"); const blueprint = path.join(fixtureRoot, "nemoclaw-blueprint", "blueprint.yaml"); const installerSource = fs.readFileSync(installer, "utf8"); @@ -860,6 +996,22 @@ function runFixture( }); } +function expectTrustedRelease( + result: ReturnType, + version: string, + manifests: ReadonlyMap, + assets: ReadonlyMap, +): void { + expect(result.status, result.stdout).toBe(0); + expect(result.stdout).toContain(`Checking OpenShell v${version} release assets`); + manifests.forEach((contents, manifest) => { + const digest = createHash("sha256").update(contents).digest("hex"); + expect(result.stdout).toContain(`OK: ${manifest} (${digest})`); + }); + expect(result.stdout).toContain(`OK: installer openshell.rb (${assets.get(FORMULA_ASSET)})`); + expect(result.stdout).toContain("All installer hashes are current"); +} + describe("installer hash verification", () => { it("verifies all installer and Brev pins from token-free checksum manifests", () => { const result = runFixture("complete"); @@ -868,65 +1020,72 @@ describe("installer hash verification", () => { expect(result.stdout).toContain("All installer hashes are current"); }); - it("verifies the pinned Homebrew formula", () => { - const result = runFixture("complete", undefined, true); + it("verifies each complete installer release table independently", () => { + const result = runFixture("complete-multiple-installer-versions", undefined, true); expect(result.status).toBe(0); - expect(result.stdout).toContain(`OK: installer ${FORMULA_ASSET} (${FORMULA_DIGEST})`); + expect(result.stdout).toContain("Checking OpenShell v0.0.72 release assets"); + expect(result.stdout).toContain("Checking OpenShell v0.0.106 release assets"); expect(result.stdout).toContain("All installer hashes are current"); }); - it("rejects drift from the reviewed Homebrew trust transition", () => { - const result = runFixture("installer-homebrew-trust-transition-drift", undefined, true); + it("accepts the reviewed product and qualification release cohorts", () => { + const result = runFixture("reviewed-release-cohorts", "0.0.101", true); - expect(result.status).toBe(1); - expect(result.stdout).toContain("installer operational template is not base-trusted"); - expect(result.stdout).not.toContain("All installer hashes are current"); + expect(result.status).toBe(0); + expect(result.stdout).toContain("Checking OpenShell v0.0.101 release assets"); + expect(result.stdout).toContain("Checking OpenShell v0.0.106 release assets"); + expect(result.stdout).toContain("All installer hashes are current"); }); - it("rejects the prior template that leaves stable formula trust behind", () => { - const result = runFixture("installer-homebrew-trust-transition-stable-leak", undefined, true); + it("rejects download drift from the reviewed release cohorts", () => { + const result = runFixture("reviewed-release-cohorts-url-drift", "0.0.101", true); expect(result.status).toBe(1); expect(result.stdout).toContain("installer operational template is not base-trusted"); expect(result.stdout).not.toContain("All installer hashes are current"); }); - it("rejects the legacy installer template after completing the trust transition (#7451)", () => { - const legacy = runFixture( - "installer-homebrew-trust-transition-complete-current", - undefined, - true, - ); + it("fails closed when a secondary installer release pin differs from its manifest", () => { + const result = runFixture("secondary-installer-version-mismatch", undefined, true); - expect(legacy.status).toBe(1); - expect(legacy.stdout).toContain("installer operational template is not base-trusted"); + expect(result.status).toBe(1); + expect(result.stdout).toContain( + `installer ${ASSETS[0]} does not match exactly one v0.0.106 checksum entry`, + ); + expect(result.stdout).not.toContain("All installer hashes are current"); }); - it("rejects cleanup that deletes the formula after untrust fails", () => { - const result = runFixture("installer-homebrew-untrust-cleanup-drift", undefined, true); + it("verifies the pinned Homebrew formula", () => { + const result = runFixture("complete", undefined, true); - expect(result.status).toBe(1); - expect(result.stdout).toContain("installer operational template is not base-trusted"); - expect(result.stdout).not.toContain("All installer hashes are current"); + expect(result.status).toBe(0); + expect(result.stdout).toContain(`OK: installer ${FORMULA_ASSET} (${FORMULA_DIGEST})`); + expect(result.stdout).toContain("All installer hashes are current"); }); - it("fails closed when the Homebrew formula digest does not match", () => { - const result = runFixture("formula-mismatch", undefined, true); + it.each([ + "installer-homebrew-trust-transition-drift", + "installer-homebrew-trust-transition-stable-leak", + "installer-homebrew-trust-transition-complete-current", + "installer-homebrew-untrust-cleanup-drift", + ] as const)("rejects Homebrew trust-transition drift in %s", (mode) => { + const result = runFixture(mode, undefined, true); expect(result.status).toBe(1); - expect(result.stdout).toContain( - "STALE: installer openshell.rb does not match the base-trusted v0.0.72 formula digest", - ); + expect(result.stdout).toContain("installer operational template is not base-trusted"); expect(result.stdout).not.toContain("All installer hashes are current"); }); - it("fails closed when the installer formula pin differs from the base-trusted digest", () => { - const result = runFixture("formula-pin-mismatch", undefined, true); + it.each([ + ["formula-mismatch", "does not match the base-trusted"], + ["formula-pin-mismatch", "pin does not match the base-trusted"], + ] as const)("fails closed for %s", (mode, diagnostic) => { + const result = runFixture(mode, undefined, true); expect(result.status).toBe(1); expect(result.stdout).toContain( - "STALE: installer openshell.rb pin does not match the base-trusted v0.0.72 formula digest", + `STALE: installer openshell.rb ${diagnostic} v0.0.72 formula digest`, ); expect(result.stdout).not.toContain("All installer hashes are current"); }); @@ -951,62 +1110,17 @@ describe("installer hash verification", () => { expect(result.stdout).toContain("All installer hashes are current"); }); - it("accepts the allowlisted OpenShell 0.0.99 release manifests (#8499)", () => { - const result = runFixture("complete", "0.0.99", true); - - expect(result.status).toBe(0); - expect(result.stdout).toContain("Checking OpenShell v0.0.99 release assets"); - expect(result.stdout).toContain( - "OK: openshell-checksums-sha256.txt (ea3e2c1a583e5ea00332c3b65a18068bd1f9b090f7ff0f5e24b29762cfc3b4c7)", - ); - expect(result.stdout).toContain( - "OK: openshell-gateway-checksums-sha256.txt (7f84f728412548720c8ef51993c58414c4f04598451c282b26ead233185e40c5)", - ); - expect(result.stdout).toContain( - "OK: openshell-sandbox-checksums-sha256.txt (9e67af6bab9f975432a1045fcfea5ab182ab585b17886c8c290c1eb77232b87a)", - ); - expect(result.stdout).toContain("All installer hashes are current"); - }); - - it("accepts the reviewed OpenShell 0.0.101 release manifests (#8598)", () => { - const result = runFixture("complete", "0.0.101", true); - - expect(result.status).toBe(0); - expect(result.stdout).toContain("Checking OpenShell v0.0.101 release assets"); - expect(result.stdout).toContain( - "OK: openshell-checksums-sha256.txt (9c90869d00b109b5ac1062b1a9808a592c2311d3c0c4926bae44d136b979d8a9)", - ); - expect(result.stdout).toContain( - "OK: openshell-gateway-checksums-sha256.txt (dcb3f1917713bf2a8e8e1803ac42c5e39d9dd41e644136b05def32b077082777)", - ); - expect(result.stdout).toContain( - "OK: openshell-sandbox-checksums-sha256.txt (d16f7d369c54d74d36c7df036565267a960e7ce6fb143012fe9d77f257d6e8b3)", - ); - expect(result.stdout).toContain( - "OK: installer openshell.rb (87fadc7b0c854aa44f71d5b3a206865070117cd27825d59c61da252a99f402a2)", - ); - expect(result.stdout).toContain("All installer hashes are current"); - }); - - it("accepts the base-trusted OpenShell 0.0.103 manifest, formula, sandbox, and supervisor identities (#8893)", () => { - const result = runFixture("complete", "0.0.103", true); - - expect(result.status).toBe(0); - expect(result.stdout).toContain("Checking OpenShell v0.0.103 release assets"); - expect(result.stdout).toContain( - "OK: openshell-checksums-sha256.txt (1a9016cfb9219ad6ea3dc623b3dfd517dbce062cba9484964a8ca9175c7d1c9d)", - ); - expect(result.stdout).toContain( - "OK: openshell-gateway-checksums-sha256.txt (800f8501329b27b79d260f21de088d8aea36de45021eaa3d29d189c433fc04b5)", - ); - expect(result.stdout).toContain( - "OK: openshell-sandbox-checksums-sha256.txt (ab7c77fe40e93b293e4d34e892824ed0cb131e8b973ba2660b155cdd0fa0f604)", - ); - expect(result.stdout).toContain( - "OK: installer openshell.rb (95a290f0e0e2f57d7d46ba9171fca6e99e5226875cd12e12391b7338f6c219f9)", - ); - expect(result.stdout).toContain("All installer hashes are current"); - }); + it.each([ + ["0.0.99", V0099_CHECKSUM_MANIFESTS, V0099_ASSET_DIGESTS], + ["0.0.101", V00101_CHECKSUM_MANIFESTS, V00101_ASSET_DIGESTS], + ["0.0.103", V00103_CHECKSUM_MANIFESTS, V00103_ASSET_DIGESTS], + ["0.0.106", V00106_CHECKSUM_MANIFESTS, V00106_ASSET_DIGESTS], + ] as const)( + "accepts the complete trusted OpenShell %s release identity", + (version, manifests, assets) => { + expectTrustedRelease(runFixture("complete", version, true), version, manifests, assets); + }, + ); it("selects a second complete trusted release from the allowlist", () => { const result = runFixture("allowlisted-alternate-version", "9.9.9", true); @@ -1016,27 +1130,24 @@ describe("installer hash verification", () => { expect(result.stdout).toContain("All installer hashes are current"); }); - it("fails closed when the derived release is not allowlisted", () => { - const result = runFixture("trusted-sandbox-alternate-version", "9.9.9", true); + it("fails closed when the derived release has no base-trusted release record", () => { + const result = runFixture("complete", "9.9.9", true); expect(result.status).toBe(1); - expect(result.stdout).toContain( - "OpenShell v9.9.9 is not in the trusted release-manifest allowlist", - ); + expect(result.stdout).toContain("OpenShell v9.9.9 is not in the base-trusted release records"); expect(result.stdout).not.toContain("Checking OpenShell v9.9.9 release assets"); expect(result.stdout).not.toContain("All installer hashes are current"); }); - it("requires the trusted allowlist prerequisite before a newer pin PR", () => { - // The first invocation deliberately keeps both trusted identity sets in - // their old base state while the separate target tree selects 9.9.9. The - // target cannot authorize itself. The second invocation models both trust - // prerequisites already present in base code; only then may the otherwise - // identical pin tree pass. + it("requires the trusted release-record prerequisite before a newer pin PR", () => { + // The first invocation keeps the base trust records unchanged while the + // separate target tree selects 9.9.9. The target cannot authorize itself. + // The second models the complete trust prerequisite already present in + // base code; only then may the otherwise identical pin tree pass. const beforePrerequisite = runFixture("complete", "9.9.9", true); expect(beforePrerequisite.status).toBe(1); expect(beforePrerequisite.stdout).toContain( - "no base-trusted standalone sandbox binary identities exist for release 9.9.9", + "OpenShell v9.9.9 is not in the base-trusted release records", ); expect(beforePrerequisite.stdout).not.toContain("PR_CHECKER_EXECUTED"); @@ -1047,43 +1158,49 @@ describe("installer hash verification", () => { expect(afterPrerequisite.stdout).not.toContain("PR_CHECKER_EXECUTED"); }); - it("fails closed when an allowlisted release lacks all three manifest digests", () => { + it("fails closed when a trusted release record lacks all three manifest digests", () => { const result = runFixture("incomplete-trusted-allowlist", undefined, true); expect(result.status).toBe(1); expect(result.stdout).toContain( - "OpenShell v0.0.72 does not have exactly three trusted release-manifest digests", + "OpenShell v0.0.72 must have exactly three trusted release-manifest digests", ); expect(result.stdout).not.toContain("Checking OpenShell v0.0.72 release assets"); expect(result.stdout).not.toContain("All installer hashes are current"); }); - it.each([ - "missing-trusted-formula", - "duplicate-trusted-formula", - ] as const)("fails closed when an allowlisted release has an invalid formula trust cardinality: %s", (mode) => { - const result = runFixture(mode, undefined, true); + it("fails closed when a trusted release record lacks its formula identity", () => { + const result = runFixture("missing-trusted-formula", undefined, true); expect(result.status).toBe(1); - expect(result.stdout).toContain( - "OpenShell v0.0.72 does not have exactly one trusted openshell.rb digest", - ); + expect(result.stdout).toContain("trusted OpenShell v0.0.72 formula record is invalid"); expect(result.stdout).not.toContain("Checking OpenShell v0.0.72 release assets"); expect(result.stdout).not.toContain("All installer hashes are current"); }); - it.each([ - "malformed-trusted-formula", - "mismatched-trusted-formula-url", - ] as const)("fails closed when a trusted formula allowlist tuple is invalid: %s", (mode) => { - const result = runFixture(mode, undefined, true); + it("fails closed when trusted release records contain a duplicate version", () => { + const result = runFixture("duplicate-trusted-release", undefined, true); expect(result.status).toBe(1); - expect(result.stdout).toContain("trusted OpenShell formula allowlist is invalid"); + expect(result.stdout).toContain( + "trusted OpenShell release records contain duplicate versions: 0.0.72", + ); expect(result.stdout).not.toContain("Checking OpenShell v0.0.72 release assets"); expect(result.stdout).not.toContain("All installer hashes are current"); }); + it.each(["malformed-trusted-formula", "mismatched-trusted-formula-url"] as const)( + "fails closed when a trusted formula record is invalid: %s", + (mode) => { + const result = runFixture(mode, undefined, true); + + expect(result.status).toBe(1); + expect(result.stdout).toContain("trusted OpenShell v0.0.72 formula record is invalid"); + expect(result.stdout).not.toContain("Checking OpenShell v0.0.72 release assets"); + expect(result.stdout).not.toContain("All installer hashes are current"); + }, + ); + it("fails closed when the live formula differs from its trusted release digest", () => { const result = runFixture("trusted-formula-mismatch", undefined, true); @@ -1185,12 +1302,9 @@ describe("installer hash verification", () => { ], [ "multiple-installer-versions", - "openshell_pinned_sha256 must contain exactly one release version, found 0.0.72, 0.0.73", - ], - [ - "mismatched-table-versions", - "installer and Brev launchable pin tables must use the same release version, found 0.0.72, 0.0.73", + `installer pin table for 0.0.72 must contain the exact consumed asset set; missing=[${ASSETS[0]}]`, ], + ["mismatched-table-versions", "installer pin table has no assets for selected release 0.0.73"], ] as const)("fails closed for %s", (mode, diagnostic) => { const result = runFixture(mode, undefined, true); @@ -1244,16 +1358,16 @@ describe("installer hash verification", () => { expect(result.stdout).toContain("All installer hashes are current"); }); - it.each([ - "missing-brev-pin", - "duplicate-brev-pin", - ] as const)("fails closed when the pull-request tree has a %s", (mode) => { - const result = runFixture(mode, undefined, true); + it.each(["missing-brev-pin", "duplicate-brev-pin"] as const)( + "fails closed when the pull-request tree has a %s", + (mode) => { + const result = runFixture(mode, undefined, true); - expect(result.status).toBe(1); - expect(result.stdout).toContain("unable to extract the OpenShell installer pin tables"); - expect(result.stdout).not.toContain("All installer hashes are current"); - }); + expect(result.status).toBe(1); + expect(result.stdout).toContain("unable to extract the OpenShell installer pin tables"); + expect(result.stdout).not.toContain("All installer hashes are current"); + }, + ); it("fails closed when the installer pin table contains a duplicate asset", () => { const result = runFixture("duplicate-installer-pin", undefined, true); diff --git a/test/installer-sandbox-build-trust.test.ts b/test/installer-sandbox-build-trust.test.ts index ccc62119f11..59b42a09d12 100644 --- a/test/installer-sandbox-build-trust.test.ts +++ b/test/installer-sandbox-build-trust.test.ts @@ -9,7 +9,10 @@ import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; -import { V00103_SANDBOX_BUILD_DIGESTS } from "./helpers/openshell-release-fixtures"; +import { + V00103_SANDBOX_BUILD_DIGESTS, + V00106_SANDBOX_BUILD_DIGESTS, +} from "./helpers/openshell-release-fixtures"; const REPO_ROOT = path.join(import.meta.dirname, ".."); const PARSER = path.join(REPO_ROOT, "scripts/checks/extract-installer-pins.mts"); @@ -73,6 +76,40 @@ function addSandboxBuildPins( ); } +function sandboxBuildPins(version: string, digests: readonly [string, string]): string { + return ` ${digests[0]} | \\ + ${digests[1]}) + printf '%s\\n' "${version}" + ;;`; +} + +function ensureSandboxBuildPins( + source: string, + version: string, + digests: readonly [string, string], +): string { + return source.includes(sandboxBuildPins(version, digests)) + ? source + : addSandboxBuildPins(source, version, digests); +} + +function remapSandboxBuildPins( + source: string, + currentVersion: string, + remappedVersion: string, + digests: readonly [string, string], +): string { + const currentPins = sandboxBuildPins(currentVersion, digests); + const remappedPins = sandboxBuildPins(remappedVersion, digests); + expect(source).toContain(currentPins); + const result = mutateSandboxBuildFunction(source, (functionSource) => + functionSource.replace(currentPins, remappedPins), + ); + expect(result).not.toContain(currentPins); + expect(result).toContain(remappedPins); + return result; +} + function runParser(mutate: (source: string) => string = (source) => source) { const root = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-sandbox-build-trust-")); const scriptsDir = path.join(root, "scripts"); @@ -120,12 +157,31 @@ describe("standalone sandbox build trust", () => { expect(runParser().status).toBe(0); }); - it("accepts the base-trusted OpenShell 0.0.103 sandbox identities before version selection (#8893)", () => { + it.each([ + ["0.0.103", V00103_SANDBOX_BUILD_DIGESTS], + ["0.0.106", V00106_SANDBOX_BUILD_DIGESTS], + ] as const)( + "accepts the base-trusted OpenShell %s sandbox identities before version selection (#8893)", + (version, digests) => { + const result = runParser((source) => ensureSandboxBuildPins(source, version, digests)); + + expect(result.status, result.stderr).toBe(0); + }, + ); + + it("rejects the OpenShell 0.0.106 sandbox identities when they are remapped", () => { const result = runParser((source) => - addSandboxBuildPins(source, "0.0.103", V00103_SANDBOX_BUILD_DIGESTS), + remapSandboxBuildPins( + ensureSandboxBuildPins(source, "0.0.106", V00106_SANDBOX_BUILD_DIGESTS), + "0.0.106", + "0.0.105", + V00106_SANDBOX_BUILD_DIGESTS, + ), ); - expect(result.status, result.stderr).toBe(0); + expect(result.status).toBe(1); + expect(result.stderr).toContain("must use only base-trusted binary identities"); + expect(result.stderr).toContain(`unexpected=[0.0.105:${V00106_SANDBOX_BUILD_DIGESTS[0]}`); }); it("rejects an arbitrary structurally valid identity addition", () => { @@ -189,6 +245,16 @@ describe("standalone sandbox build trust", () => { ); }); + it("rejects OpenShell 0.0.106 sandbox identities remapped to another release", () => { + const result = runParser((source) => + addSandboxBuildPins(source, "0.0.105", V00106_SANDBOX_BUILD_DIGESTS), + ); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("must use only base-trusted binary identities"); + expect(result.stderr).toContain(`unexpected=[0.0.105:${V00106_SANDBOX_BUILD_DIGESTS[0]}`); + }); + it.each([ ["altered control flow", (source: string) => source.replace("return 1", "return 0")], [ diff --git a/test/installer-supervisor-manifest-trust.test.ts b/test/installer-supervisor-manifest-trust.test.ts index 1b5ce4311c6..72daf0b1167 100644 --- a/test/installer-supervisor-manifest-trust.test.ts +++ b/test/installer-supervisor-manifest-trust.test.ts @@ -12,6 +12,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { V00103_SANDBOX_BUILD_DIGESTS, V00103_SUPERVISOR_MANIFEST_DIGEST, + V00106_SUPERVISOR_MANIFEST_DIGEST, } from "./helpers/openshell-release-fixtures"; const REPO_ROOT = path.join(import.meta.dirname, ".."); @@ -191,10 +192,24 @@ describe("OpenShell supervisor manifest trust", () => { expect(result.status, result.stderr).toBe(0); }); - it("accepts the base-trusted OpenShell 0.0.103 supervisor identity before version selection (#8893)", () => { + it.each([ + ["0.0.103", V00103_SUPERVISOR_MANIFEST_DIGEST], + ["0.0.106", V00106_SUPERVISOR_MANIFEST_DIGEST], + ] as const)( + "accepts the base-trusted OpenShell %s supervisor identity before version selection (#8893)", + (version, digest) => { + const result = runParser({ + transformSupervisor: (source) => addSupervisorManifestPin(source, version, digest), + }); + + expect(result.status, result.stderr).toBe(0); + }, + ); + + it("accepts the exact OpenShell 0.0.106 supervisor identity before version selection", () => { const result = runParser({ transformSupervisor: (source) => - addSupervisorManifestPin(source, "0.0.103", V00103_SUPERVISOR_MANIFEST_DIGEST), + addSupervisorManifestPin(source, "0.0.106", V00106_SUPERVISOR_MANIFEST_DIGEST), }); expect(result.status, result.stderr).toBe(0); @@ -222,6 +237,17 @@ describe("OpenShell supervisor manifest trust", () => { expect(result.stderr).toContain("|0.0.104|"); }); + it("rejects the OpenShell 0.0.106 supervisor identity remapped to another release", () => { + const result = runParser({ + transformSupervisor: (source) => + addSupervisorManifestPin(source, "0.0.105", V00106_SUPERVISOR_MANIFEST_DIGEST), + }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("must use only base-trusted identities"); + expect(result.stderr).toContain(`|0.0.105|${V00106_SUPERVISOR_MANIFEST_DIGEST}`); + }); + it("rejects selecting OpenShell 0.0.103 without its supervisor manifest identity (#8893)", () => { const result = runParser({ selectV00103: true });