From d4ecef230dc3b0e3c22ab7832d0ff11c9bc42d13 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 25 Jul 2026 19:06:10 -0700 Subject: [PATCH 1/4] test(e2e): reject continued Hermes consumer builds Signed-off-by: Carlos Villela --- .../sandbox-images-workflow-boundary.test.ts | 15 +++++++++++++++ tools/e2e/sandbox-images-workflow-boundary.mts | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test/e2e/support/sandbox-images-workflow-boundary.test.ts b/test/e2e/support/sandbox-images-workflow-boundary.test.ts index aff0bfb5919..0eb2933a34d 100644 --- a/test/e2e/support/sandbox-images-workflow-boundary.test.ts +++ b/test/e2e/support/sandbox-images-workflow-boundary.test.ts @@ -191,6 +191,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"]; diff --git a/tools/e2e/sandbox-images-workflow-boundary.mts b/tools/e2e/sandbox-images-workflow-boundary.mts index e47cf55505c..82accb03e6f 100644 --- a/tools/e2e/sandbox-images-workflow-boundary.mts +++ b/tools/e2e/sandbox-images-workflow-boundary.mts @@ -865,7 +865,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"); From e0aa2edb0a3fb8fb2e267c218094a87e4709ac6c Mon Sep 17 00:00:00 2001 From: Apurv Kumaria Date: Sat, 25 Jul 2026 20:07:17 -0700 Subject: [PATCH 2/4] fix(e2e): detect assigned Buildx push flags Signed-off-by: Apurv Kumaria --- .../support/sandbox-images-workflow-boundary.test.ts | 12 ++++++++++++ tools/e2e/sandbox-images-workflow-boundary.mts | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/e2e/support/sandbox-images-workflow-boundary.test.ts b/test/e2e/support/sandbox-images-workflow-boundary.test.ts index 0eb2933a34d..b7b7af3c799 100644 --- a/test/e2e/support/sandbox-images-workflow-boundary.test.ts +++ b/test/e2e/support/sandbox-images-workflow-boundary.test.ts @@ -247,6 +247,18 @@ 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("requires the Hermes artifact download before its load", () => { const { imageWorkflow, mainWorkflow } = readWorkflows(); const consumer = imageWorkflow.jobs["test-hermes-sandbox-image"]; diff --git a/tools/e2e/sandbox-images-workflow-boundary.mts b/tools/e2e/sandbox-images-workflow-boundary.mts index 82accb03e6f..c9a89693cfc 100644 --- a/tools/e2e/sandbox-images-workflow-boundary.mts +++ b/tools/e2e/sandbox-images-workflow-boundary.mts @@ -64,7 +64,7 @@ const EXPECTED_AUTH_ENV = { }; const FULL_SHA_ACTION = /^[^\s@]+@[0-9a-f]{40}$/u; 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(?:=(?:1|[tT](?:[rR][uU][eE])?))?(?=$|[\s;&|<>()])|\b(?:oras|crane)\s+push\b|\bskopeo\s+copy\b)/u; function normalizeShellContinuations(run: string): string { return run.replace(/\\\r?\n[ \t]*/gu, " "); From e4c1cd04fc4d26d0170f7c1c9910e8aaff017059 Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Sat, 25 Jul 2026 21:07:07 -0700 Subject: [PATCH 3/4] test(e2e): cover mixed-case Buildx push flags Signed-off-by: Charan Jagwani --- .../support/sandbox-images-workflow-boundary.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/e2e/support/sandbox-images-workflow-boundary.test.ts b/test/e2e/support/sandbox-images-workflow-boundary.test.ts index 0cabf792d9e..90969d29e9b 100644 --- a/test/e2e/support/sandbox-images-workflow-boundary.test.ts +++ b/test/e2e/support/sandbox-images-workflow-boundary.test.ts @@ -283,6 +283,18 @@ describe("sandbox image workflow boundary", () => { ); }); + 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("requires the Hermes artifact download before its load", () => { const { imageWorkflow, mainWorkflow } = readWorkflows(); const consumer = imageWorkflow.jobs["test-hermes-sandbox-image"]; From 6c9faeab5d89885d83c82037c7c83c623b73c23a Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Sat, 25 Jul 2026 21:20:17 -0700 Subject: [PATCH 4/4] fix(e2e): reject dynamic Buildx push values Signed-off-by: Charan Jagwani --- .../sandbox-images-workflow-boundary.test.ts | 24 +++++++++++++++++++ .../e2e/sandbox-images-workflow-boundary.mts | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/test/e2e/support/sandbox-images-workflow-boundary.test.ts b/test/e2e/support/sandbox-images-workflow-boundary.test.ts index 90969d29e9b..defcf647deb 100644 --- a/test/e2e/support/sandbox-images-workflow-boundary.test.ts +++ b/test/e2e/support/sandbox-images-workflow-boundary.test.ts @@ -295,6 +295,30 @@ describe("sandbox image workflow boundary", () => { ); }); + 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"]; diff --git a/tools/e2e/sandbox-images-workflow-boundary.mts b/tools/e2e/sandbox-images-workflow-boundary.mts index 8de47c5fe68..83830173735 100644 --- a/tools/e2e/sandbox-images-workflow-boundary.mts +++ b/tools/e2e/sandbox-images-workflow-boundary.mts @@ -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(?:=(?:1|[tT](?:[rR][uU][eE])?))?(?=$|[\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, " ");