Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions test/e2e/support/sandbox-images-workflow-boundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,21 @@ describe("sandbox image workflow boundary", () => {
);
});

it("rejects a continued Buildx rebuild in the Hermes image consumer", () => {
const { imageWorkflow, mainWorkflow } = readWorkflows();
imageWorkflow.jobs["test-hermes-sandbox-image"].steps!.push({
name: "Rebuild Hermes production image",
run: [
"docker buildx \\",
" build --load -f agents/hermes/Dockerfile -t nemoclaw-hermes-production .",
].join("\n"),
});

expect(validateSandboxImagesWorkflow(imageWorkflow, mainWorkflow)).toContain(
"Hermes image test consumer must not rebuild the prebuilt image",
);
});

it("rejects a duplicate Hermes production-image build", () => {
const { imageWorkflow, mainWorkflow } = readWorkflows();
const producer = imageWorkflow.jobs["build-hermes-sandbox-image"];
Expand Down Expand Up @@ -256,6 +271,54 @@ describe("sandbox image workflow boundary", () => {
);
});

it("rejects an assigned Buildx push flag in an image consumer", () => {
const { imageWorkflow, mainWorkflow } = readWorkflows();
imageWorkflow.jobs["state-dir-guard-metadata"].steps!.push({
name: "Publish from metadata consumer",
run: "docker buildx build --push=true -t registry.example.invalid/nemoclaw .",
});

expect(validateSandboxImagesWorkflow(imageWorkflow, mainWorkflow)).toContain(
"state-dir-guard-metadata step \x27Publish from metadata consumer\x27 must not write images to a registry",
);
});

it("rejects a mixed-case assigned Buildx push flag in an image consumer", () => {
const { imageWorkflow, mainWorkflow } = readWorkflows();
imageWorkflow.jobs["state-dir-guard-metadata"].steps!.push({
name: "Publish from metadata consumer",
run: "docker buildx build --push=True -t registry.example.invalid/nemoclaw .",
});

expect(validateSandboxImagesWorkflow(imageWorkflow, mainWorkflow)).toContain(
"state-dir-guard-metadata step \x27Publish from metadata consumer\x27 must not write images to a registry",
);
});

it("rejects a shell-expanded Buildx push flag in an image consumer", () => {
const { imageWorkflow, mainWorkflow } = readWorkflows();
imageWorkflow.jobs["state-dir-guard-metadata"].steps!.push({
name: "Publish from expanded metadata consumer",
run: 'docker buildx build --push="${PUSH_IMAGES}" -t registry.example.invalid/nemoclaw .',
});

expect(validateSandboxImagesWorkflow(imageWorkflow, mainWorkflow)).toContain(
"state-dir-guard-metadata step \x27Publish from expanded metadata consumer\x27 must not write images to a registry",
);
});

it("treats an explicit false Buildx push assignment as non-writing", () => {
const { imageWorkflow, mainWorkflow } = readWorkflows();
imageWorkflow.jobs["state-dir-guard-metadata"].steps!.push({
name: "Disable publishing from metadata consumer",
run: "docker buildx build --push=false -t nemoclaw-metadata-consumer .",
});

expect(validateSandboxImagesWorkflow(imageWorkflow, mainWorkflow)).not.toContain(
"state-dir-guard-metadata step \x27Disable publishing from metadata consumer\x27 must not write images to a registry",
);
});

it("requires the Hermes artifact download before its load", () => {
const { imageWorkflow, mainWorkflow } = readWorkflows();
const consumer = imageWorkflow.jobs["test-hermes-sandbox-image"];
Expand Down
5 changes: 3 additions & 2 deletions tools/e2e/sandbox-images-workflow-boundary.mts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ const EXPECTED_AUTH_ENV = {
DOCKERHUB_TOKEN: `\${{ ${TRUSTED_PREDICATE} && secrets.DOCKERHUB_TOKEN || '' }}`,
};
const FULL_SHA_ACTION = /^[^\s@]+@[0-9a-f]{40}$/u;
// Shell-expanded values are unknown; only literal `--push=false` is statically non-writing.
const REGISTRY_WRITE =
/(?:\bdocker\s+(?:image\s+)?push\b|\bdocker\s+buildx\s+build\b[^\n]*\s--push(?:\s|$)|\b(?:oras|crane)\s+push\b|\bskopeo\s+copy\b)/u;
/(?:\bdocker\s+(?:image\s+)?push\b|\bdocker\s+buildx\s+build\b[^\n]*\s--push(?:=(?!false(?=$|[\s;&|<>()]))[^\s;&|<>()]+)?(?=$|[\s;&|<>()])|\b(?:oras|crane)\s+push\b|\bskopeo\s+copy\b)/u;

function normalizeShellContinuations(run: string): string {
return run.replace(/\\\r?\n[ \t]*/gu, " ");
Expand Down Expand Up @@ -883,7 +884,7 @@ function validateHermesImageReuse(errors: string[], workflow: SandboxImagesWorkf
const consumerBuilds = steps(testJob).filter(
(step) =>
String(step.uses ?? "").startsWith("docker/build-push-action@") ||
/\bdocker\s+(?:build|buildx\s+build)\b/u.test(step.run ?? ""),
/\bdocker\s+(?:build|buildx\s+build)\b/u.test(normalizeShellContinuations(step.run ?? "")),
);
if (consumerBuilds.length !== 0) {
errors.push("Hermes image test consumer must not rebuild the prebuilt image");
Expand Down
Loading