diff --git a/agents/hermes/Dockerfile b/agents/hermes/Dockerfile index 175e54201be..324034c5deb 100644 --- a/agents/hermes/Dockerfile +++ b/agents/hermes/Dockerfile @@ -716,6 +716,12 @@ RUN if [ "$NEMOCLAW_DARWIN_VM_COMPAT" = "1" ]; then \ # container and retains evidence keyed to the final image ID. COPY --from=hermes-scan-payload / / +# These executables are git-tracked 0755 but check out group-writable (0775) +# under umask 002, and COPY carries that source mode into the image. Normalize +# before the check_metadata gate below asserts root:root 755. A RUN chmod is +# builder-independent; COPY --chmod requires BuildKit. +RUN chmod 755 /usr/local/lib/nemoclaw/hermes-wrapper.py /scripts/checks/node-tar-image-scan.mts + RUN check_metadata() { \ path="$1"; \ expected="$2"; \ diff --git a/test/hermes-final-image-layout.test.ts b/test/hermes-final-image-layout.test.ts index 669a78c4761..bfc97eaa473 100644 --- a/test/hermes-final-image-layout.test.ts +++ b/test/hermes-final-image-layout.test.ts @@ -204,6 +204,10 @@ describe("Hermes final image layout", () => { 'RUN if [ "$NEMOCLAW_DARWIN_VM_COMPAT" = "1" ]', ); const metadataCheck = indexOfRequired(finalStage, "RUN check_metadata()"); + const modeNormalize = indexOfRequired( + finalStage, + "RUN chmod 755 /usr/local/lib/nemoclaw/hermes-wrapper.py /scripts/checks/node-tar-image-scan.mts", + ); const imageScan = indexOfRequired( finalStage, "node --experimental-strip-types /scripts/checks/node-tar-image-scan.mts", @@ -218,6 +222,8 @@ describe("Hermes final image layout", () => { expect(wrapper).toBeLessThan(pythonCheck); expect(scan).toBeGreaterThan(darwinCompatibility); expect(scan).toBeLessThan(metadataCheck); + expect(modeNormalize).toBeGreaterThan(scan); + expect(modeNormalize).toBeLessThan(metadataCheck); for (const metadataContract of [ "/scripts/patch-bundled-npm-brace-expansion.mts 'root:root 444'", "/scripts/patch-bundled-npm-tar.mts 'root:root 444'",