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
4 changes: 2 additions & 2 deletions agents/hermes/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ FROM scratch AS hermes-runtime-payload
COPY --from=mcp-tool-discovery-runtime /opt/mcp-tool-discovery-runtime/dist/ /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime/
COPY nemoclaw-blueprint/ /opt/nemoclaw-blueprint/
COPY scripts/lib/sandbox-init.sh /usr/local/lib/nemoclaw/sandbox-init.sh
COPY --chmod=0444 scripts/lib/corporate-ca-runtime.sh /usr/local/lib/nemoclaw/corporate-ca-runtime.sh
COPY scripts/lib/corporate-ca-runtime.sh /usr/local/lib/nemoclaw/corporate-ca-runtime.sh
COPY scripts/lib/entrypoint-env-wrapper.sh /usr/local/lib/nemoclaw/entrypoint-env-wrapper.sh
COPY scripts/lib/gateway-supervisor.sh /usr/local/lib/nemoclaw/gateway-supervisor.sh
COPY scripts/lib/sandbox-rlimits.sh /usr/local/lib/nemoclaw/sandbox-rlimits.sh
Expand Down Expand Up @@ -491,7 +491,7 @@ RUN chmod 755 /usr/local/bin/nemoclaw-start /usr/local/bin/nemoclaw-managed-star
&& chmod 700 /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/hermes-cron-restore-control.py \
&& chmod 500 /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/runtime-state-mutation-control.py /usr/local/lib/nemoclaw/runtime_state_mutation_hermes_publisher.py /usr/local/lib/nemoclaw/managed-gateway-control.py \
&& chmod 555 /usr/local/lib/nemoclaw/runtime-state-mutation-startup-gate.py \
&& chmod 444 /usr/local/lib/nemoclaw/entrypoint-env-wrapper.sh /usr/local/share/nemoclaw/state-lock-plan.json /usr/local/share/nemoclaw/runtime-state-mutation-publisher-v1.json /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/managed_policy.py \
&& chmod 444 /usr/local/lib/nemoclaw/corporate-ca-runtime.sh /usr/local/lib/nemoclaw/entrypoint-env-wrapper.sh /usr/local/share/nemoclaw/state-lock-plan.json /usr/local/share/nemoclaw/runtime-state-mutation-publisher-v1.json /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/managed_policy.py \
&& chmod 444 /usr/local/lib/nemoclaw/patch-hermes-langfuse-credentials.mts \
&& chmod 444 /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.106.json \
&& /opt/hermes/.venv/bin/python3 -I -c 'import runpy, yaml; assert yaml.safe_load("ready: true")["ready"] is True; runpy.run_path("/usr/local/lib/nemoclaw/runtime-state-mutation-control.py", run_name="nemoclaw_runtime_state_mutation_control_probe"); runpy.run_path("/usr/local/lib/nemoclaw/runtime_state_mutation_hermes_publisher.py", run_name="nemoclaw_runtime_state_mutation_publisher_probe"); runpy.run_path("/usr/local/lib/nemoclaw/runtime-state-mutation-startup-gate.py", run_name="nemoclaw_runtime_state_mutation_gate_probe"); runpy.run_path("/usr/local/lib/nemoclaw/hermes-runtime-config-guard.py", run_name="nemoclaw_runtime_config_guard_probe")' \
Expand Down
29 changes: 29 additions & 0 deletions src/lib/onboard/experimental/hermes-portable-build-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ describe("Hermes portable staged build context", testTimeoutOptions(30_000), ()
expect(stagedDockerfile).toContain("ARG NEMOCLAW_TOOL_DISCLOSURE=direct");
expect(stagedDockerfile).toContain("ARG CHAT_UI_URL=");
expect(stagedDockerfile).not.toContain("ARG CHAT_UI_URL=http://127.0.0.1:18789");
const finalStage = stagedDockerfile.slice(stagedDockerfile.lastIndexOf("FROM ${BASE_IMAGE}"));
const payloadCopyIndex = finalStage.indexOf("COPY --from=hermes-runtime-payload / /");
const permissionNormalizationIndex = finalStage.search(
/chmod 444 [^\n]*\/usr\/local\/lib\/nemoclaw\/corporate-ca-runtime[.]sh/u,
);
expect(payloadCopyIndex).toBeGreaterThanOrEqual(0);
expect(permissionNormalizationIndex).toBeGreaterThan(payloadCopyIndex);
expect(fs.existsSync(path.join(first.buildContextPath, ".git"))).toBe(false);
expect(fs.existsSync(path.join(first.buildContextPath, "node_modules"))).toBe(false);
expect(
Expand Down Expand Up @@ -340,6 +347,28 @@ describe("Hermes portable staged build context", testTimeoutOptions(30_000), ()
);
});

it("rejects BuildKit-only local COPY options before reservation (#9921)", () => {
const source = primaryCloneFixture();
const dockerfile = path.join(source, "agents/hermes/Dockerfile");
const reservationRoot = path.join(stateDir, "hermes-portable-build-context");
fs.writeFileSync(
dockerfile,
fs
.readFileSync(dockerfile, "utf8")
.replace(
"COPY scripts/lib/corporate-ca-runtime.sh",
"COPY --chmod=0444 scripts/lib/corporate-ca-runtime.sh",
),
{ mode: 0o644 },
);
const reservationExistedBefore = fs.existsSync(reservationRoot);

expect(() => createHermesPortableBuildContextPlan(source, BUILD_SETTINGS)).toThrow(
"non-Portable local COPY option",
);
expect(fs.existsSync(reservationRoot)).toBe(reservationExistedBefore);
});

Comment thread
coderabbitai[bot] marked this conversation as resolved.
it("rejects source symlinks, hardlinks, and unreviewed secret paths (#9203)", () => {
const source = primaryCloneFixture();
const script = path.join(source, "agents/hermes/start.sh");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,7 @@ function parseDockerfileSources(bytes: Buffer): readonly string[] {
}
continue;
}
if (options.some((option) => option !== "--chmod=0444")) {
fail("Dockerfile has an unsupported local COPY option");
}
if (options.length > 0) fail("Dockerfile has a non-Portable local COPY option");
local.push(...sources);
}
const expected = [...LOCAL_COPY_SOURCES].sort();
Expand Down
1 change: 1 addition & 0 deletions test/hermes-doctor-config-hash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ describe("Hermes doctor and config hash boundary", () => {
path.join(binDir, "nemoclaw-managed-startup-hold"),
path.join(binDir, "nemoclaw-managed-bootstrap"),
path.join(binDir, "nemoclaw-gateway-control"),
path.join(libDir, "corporate-ca-runtime.sh"),
path.join(libDir, "entrypoint-env-wrapper.sh"),
path.join(libDir, "sandbox-init.sh"),
path.join(libDir, "gateway-supervisor.sh"),
Expand Down
3 changes: 3 additions & 0 deletions test/sandbox-provisioning.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1078,11 +1078,13 @@ describe("Hermes sandbox provisioning", () => {
);
const managedGatewayControlPath = path.join(localLib, "managed-gateway-control.py");
const hermesCronRestoreControlPath = path.join(localLib, "hermes-cron-restore-control.py");
const corporateCaRuntimePath = path.join(localLib, "corporate-ca-runtime.sh");
const files = [
path.join(localBin, "nemoclaw-start"),
path.join(localBin, "nemoclaw-managed-startup-hold"),
path.join(localBin, "nemoclaw-managed-bootstrap"),
gatewayControlPath,
corporateCaRuntimePath,
path.join(localLib, "entrypoint-env-wrapper.sh"),
path.join(localLib, "sandbox-init.sh"),
path.join(localLib, "validate-hermes-env-secret-boundary.py"),
Expand Down Expand Up @@ -1149,6 +1151,7 @@ describe("Hermes sandbox provisioning", () => {
expect((fs.statSync(buildMcpDigestPath).mode & 0o777).toString(8)).toBe("444");
expect((fs.statSync(managedPolicyReaderPath).mode & 0o777).toString(8)).toBe("444");
expect((fs.statSync(gatewaySupervisorPath).mode & 0o777).toString(8)).toBe("444");
expect((fs.statSync(corporateCaRuntimePath).mode & 0o777).toString(8)).toBe("444");
expect((fs.statSync(stateDirGuardPath).mode & 0o777).toString(8)).toBe("500");
expect((fs.statSync(runtimeStateMutationControlPath).mode & 0o777).toString(8)).toBe("500");
expect((fs.statSync(runtimeStateMutationStartupGatePath).mode & 0o777).toString(8)).toBe(
Expand Down
3 changes: 3 additions & 0 deletions test/sandbox-rlimit-hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,7 @@ describe("sandbox rlimit system hooks (#2173)", () => {
const managedStartupHold = path.join(tmp, "nemoclaw-managed-startup-hold");
const managedBootstrap = path.join(tmp, "nemoclaw-managed-bootstrap");
const gatewayControl = path.join(tmp, "nemoclaw-gateway-control");
const corporateCaRuntime = path.join(localLib, "corporate-ca-runtime.sh");
const entrypointEnvWrapper = path.join(localLib, "entrypoint-env-wrapper.sh");
const bashrc = path.join(tmp, "bash.bashrc");
const expectedRlimitShim = rlimitShim(rlimitLib);
Expand Down Expand Up @@ -682,6 +683,7 @@ describe("sandbox rlimit system hooks (#2173)", () => {
fs.writeFileSync(managedStartupHold, "#!/usr/bin/env bash\n");
fs.writeFileSync(managedBootstrap, "#!/usr/bin/env bash\n");
fs.writeFileSync(gatewayControl, "#!/usr/bin/env sh\n");
fs.writeFileSync(corporateCaRuntime, "# corporate CA runtime fixture\n");
fs.writeFileSync(entrypointEnvWrapper, "# entrypoint env wrapper fixture\n");
fs.writeFileSync(bashrc, "# stale hermes bashrc\n");
const fixtureOwner = fs.statSync(startBin);
Expand All @@ -694,6 +696,7 @@ describe("sandbox rlimit system hooks (#2173)", () => {
.replaceAll("/usr/local/bin/nemoclaw-managed-startup-hold", managedStartupHold)
.replaceAll("/usr/local/bin/nemoclaw-managed-bootstrap", managedBootstrap)
.replaceAll("/usr/local/bin/nemoclaw-gateway-control", gatewayControl)
.replaceAll("/usr/local/lib/nemoclaw/corporate-ca-runtime.sh", corporateCaRuntime)
.replaceAll("/usr/local/lib/nemoclaw/entrypoint-env-wrapper.sh", entrypointEnvWrapper)
.replaceAll("/usr/local/lib/nemoclaw/sandbox-init.sh", initLib)
.replaceAll("/usr/local/lib/nemoclaw/gateway-supervisor.sh", gatewaySupervisor)
Expand Down
Loading