diff --git a/agents/hermes/Dockerfile.base b/agents/hermes/Dockerfile.base index 1364b7a0e1d..ef5da59a5ed 100644 --- a/agents/hermes/Dockerfile.base +++ b/agents/hermes/Dockerfile.base @@ -412,7 +412,9 @@ RUN printf '%s\n' \ # skip rather than doing a nondeterministic dependency resolve during image # build. RUN pip3 install --no-cache-dir --break-system-packages "uv==${UV_VERSION}" \ - && test "$(uv --version)" = "uv ${UV_VERSION}" + && uv_version_output="$(uv --version)" \ + && uv_version="${uv_version_output#uv }" \ + && test "${uv_version%% *}" = "${UV_VERSION}" # Upstream tests are not part of the production runtime and can contain # intentionally hostile security-test fixtures. Remove them in the extraction # RUN so their bytes never enter a published image layer. diff --git a/test/hermes-dependency-review.test.ts b/test/hermes-dependency-review.test.ts index 3a35357291a..8273c2aa0c7 100644 --- a/test/hermes-dependency-review.test.ts +++ b/test/hermes-dependency-review.test.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; @@ -32,6 +33,28 @@ function arg(name: string): string { return match?.[1] ?? ""; } +function uvVersionCheckStatus(output: string, expectedVersion: string): number | null { + const dockerfileLines = dockerfileBase.split("\n"); + const installIndex = dockerfileLines.findIndex( + (line) => line.startsWith("RUN pip3 install ") && line.includes('"uv==${UV_VERSION}"'), + ); + expect(installIndex, "Missing Dockerfile uv install command").toBeGreaterThanOrEqual(0); + + const commandLines = dockerfileLines.slice(installIndex); + const commandEndIndex = commandLines.findIndex((line) => !line.endsWith("\\")); + const versionCheckLines = commandLines.slice(1, commandEndIndex + 1); + expect(versionCheckLines, "Missing Dockerfile uv version check").not.toHaveLength(0); + + const script = [ + 'uv() { printf "%s\\n" "$UV_OUTPUT"; }', + "set -e", + ...versionCheckLines.map((line) => line.replace(/^\s*&&\s*/u, "").replace(/\s*\\$/u, "")), + ].join("\n"); + return spawnSync("/bin/sh", ["-c", script], { + env: { ...process.env, UV_OUTPUT: output, UV_VERSION: expectedVersion }, + }).status; +} + describe("Hermes 0.19.0 dependency review", () => { it("binds every active source identity to the reviewed release", () => { expect(arg("HERMES_VERSION")).toBe("v2026.7.20"); @@ -79,6 +102,22 @@ describe("Hermes 0.19.0 dependency review", () => { } }); + it("accepts uv build metadata and rejects a different semantic version", () => { + const expectedVersion = arg("UV_VERSION"); + const differentVersion = expectedVersion.replace(/\d+$/u, (patch) => + String(Number.parseInt(patch, 10) + 1), + ); + expect( + uvVersionCheckStatus( + `uv ${expectedVersion} (fece32fc5 2026-07-28 aarch64-unknown-linux-gnu)`, + expectedVersion, + ), + ).toBe(0); + expect( + uvVersionCheckStatus(`uv ${differentVersion} (different build metadata)`, expectedVersion), + ).toBe(1); + }); + it("ships the reviewed Python dependency remediations and records residual debt", () => { expect(dockerfileBase).toContain( "COPY agents/hermes/security-dependencies.patch /tmp/hermes-security-dependencies.patch",