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
123 changes: 90 additions & 33 deletions .github/workflows/sandbox-images-and-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,28 +160,18 @@ jobs:

build-hermes-sandbox-image:
runs-on: ubuntu-latest
# Preserve the former 30-minute image-build, 60-minute secret-boundary,
# and 45-minute root-entrypoint budgets, plus orchestration/cleanup time.
timeout-minutes: 150
timeout-minutes: 30
steps:
- *checkout

- *dockerhub-auth

- &setup-node
name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.19.0
cache: npm

- &install-root-dependencies
name: Install root dependencies
run: npm ci --ignore-scripts

- name: Resolve Hermes base image
uses: ./.github/actions/resolve-hermes-base-image

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0

- &hermes-export-swap
name: Add swap for Hermes image export
shell: bash
Expand All @@ -200,14 +190,33 @@ jobs:
df -h / /mnt
docker system df

- name: Build Hermes production image
- name: Validate Hermes production build args
env:
HERMES_BASE_IMAGE: ${{ env.HERMES_BASE_IMAGE }}
run: |
set -euo pipefail
build_args=(-f agents/hermes/Dockerfile --build-arg "BASE_IMAGE=${HERMES_BASE_IMAGE}")
scripts/check-production-build-args.sh "${build_args[@]}"
docker build "${build_args[@]}" -t nemoclaw-hermes-production .

- name: Record resources before Hermes image build
shell: bash
run: |
set -u
free -h || true
df -h || true
docker system df || true

- name: Build Hermes production image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: agents/hermes/Dockerfile
load: true
push: false
tags: nemoclaw-hermes-production
build-args: BASE_IMAGE=${{ env.HERMES_BASE_IMAGE }}
cache-from: type=gha,scope=hermes-production-${{ runner.os }}-${{ runner.arch }}
cache-to: type=gha,mode=max,scope=hermes-production-${{ runner.os }}-${{ runner.arch }}

- name: Scan completed Hermes image for node-tar
id: node-tar-scan
Expand Down Expand Up @@ -254,9 +263,66 @@ jobs:
docker run --rm --user sandbox nemoclaw-hermes-production \
test -x /usr/local/bin/nemoclaw-start

- name: Save Hermes production image
run: |
set -euo pipefail
docker save nemoclaw-hermes-production | gzip > /tmp/hermes-isolation-image.tar.gz
gzip -t /tmp/hermes-isolation-image.tar.gz

- name: Upload Hermes isolation image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: hermes-isolation-image
path: /tmp/hermes-isolation-image.tar.gz
retention-days: 1

- name: Record resources after Hermes image build
if: always()
shell: bash
run: |
set -u
free -h || true
df -h || true
docker system df || true

- name: Clean up Docker auth
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh

test-hermes-sandbox-image:
runs-on: ubuntu-latest
needs: build-hermes-sandbox-image
timeout-minutes: 90
steps:
- *checkout

- &setup-node
name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.19.0
cache: npm

- &install-root-dependencies
name: Install root dependencies
run: npm ci --ignore-scripts

- name: Download Hermes production image
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: hermes-isolation-image
path: /tmp

- name: Load Hermes production image
run: |
set -euo pipefail
gunzip -c /tmp/hermes-isolation-image.tar.gz | docker load
docker image inspect nemoclaw-hermes-production >/dev/null

- name: Run Hermes sandbox secret boundary test
id: hermes-secret-boundary
timeout-minutes: 60
timeout-minutes: 45
env:
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-sandbox-secret-boundary
NEMOCLAW_HERMES_TEST_IMAGE: nemoclaw-hermes-production
Expand All @@ -277,7 +343,7 @@ jobs:

- name: Run Hermes root entrypoint smoke Vitest test
if: ${{ !cancelled() && (steps.hermes-secret-boundary.outcome == 'success' || steps.hermes-secret-boundary.outcome == 'failure') }}
timeout-minutes: 45
timeout-minutes: 30
env:
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/hermes-root-entrypoint-smoke
NEMOCLAW_HERMES_TEST_IMAGE: nemoclaw-hermes-production
Expand All @@ -298,23 +364,14 @@ jobs:
if-no-files-found: ignore
retention-days: 14

- name: Save Hermes production image
run: |
set -euo pipefail
docker save nemoclaw-hermes-production | gzip > /tmp/hermes-isolation-image.tar.gz
gzip -t /tmp/hermes-isolation-image.tar.gz

- name: Upload Hermes isolation image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: hermes-isolation-image
path: /tmp/hermes-isolation-image.tar.gz
retention-days: 1

- name: Clean up Docker auth
- name: Record resources after Hermes image tests
if: always()
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh
run: |
set -u
free -h || true
df -h || true
docker system df || true

messaging-plan-image-boundary:
runs-on: ubuntu-latest
Expand Down
16 changes: 16 additions & 0 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ before those targets run; local runners must provide it themselves.
call their target E2E tests directly. The Ollama auth proxy target is
selected through `.github/workflows/e2e.yaml`.

## CI execution shape

The sandbox image workflow builds the Hermes production image in the dedicated
30-minute `build-hermes-sandbox-image` job. It uses full-SHA-pinned Buildx
actions and a GitHub Actions cache scoped to the runner OS and architecture.
The producer adds a bounded 32 GiB swap file and validates the guarded
production build arguments before the build. It loads the image locally with
registry writes disabled. After the build, it scans the completed image for
node-tar and verifies the sandbox-readable installed files. It then uploads the
compressed image as the one-day `hermes-isolation-image` artifact.

The 90-minute `test-hermes-sandbox-image` job and the
`state-dir-guard-metadata` job download and load that artifact instead of
rebuilding the image. Within the Hermes test job, the secret-boundary and
root-entrypoint steps have 45- and 30-minute budgets respectively.

The former top-level `test/e2e/test-*.sh` suite has been removed. Keep real
shell, installer, process, Docker, OpenShell, `/proc`, and sandbox boundaries in
E2E tests when those boundaries are the behavior under test.
Expand Down
15 changes: 9 additions & 6 deletions test/e2e/support/hermes-secret-boundary-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ describe("Hermes image workflow secret boundary", () => {

it("rejects broad Hermes job and test-step secret scope", () => {
const { imageWorkflow, mainWorkflow } = readWorkflows();
const job = imageWorkflow.jobs["build-hermes-sandbox-image"];
job.env = {
NVIDIA_INFERENCE_API_KEY: "${{ secrets.NVIDIA_INFERENCE_API_KEY }}",
const producer = imageWorkflow.jobs["build-hermes-sandbox-image"];
const job = imageWorkflow.jobs["test-hermes-sandbox-image"];
producer.env = {
DOCKERHUB_USERNAME: "${{ secrets.DOCKERHUB_USERNAME }}",
DOCKERHUB_TOKEN: "${{ secrets.DOCKERHUB_TOKEN }}",
};
job.env = {
NVIDIA_INFERENCE_API_KEY: "${{ secrets.NVIDIA_INFERENCE_API_KEY }}",
};
const secretBoundary = job.steps?.find(
(step) => step.name === "Run Hermes sandbox secret boundary test",
);
Expand All @@ -41,11 +44,11 @@ describe("Hermes image workflow secret boundary", () => {

expect(validateSandboxImagesWorkflow(imageWorkflow, mainWorkflow)).toEqual(
expect.arrayContaining([
"build-hermes-sandbox-image must not expose NVIDIA_INFERENCE_API_KEY at job scope",
"build-hermes-sandbox-image must not expose DOCKERHUB_USERNAME at job scope",
"build-hermes-sandbox-image must not expose DOCKERHUB_TOKEN at job scope",
"build-hermes-sandbox-image step 'Run Hermes sandbox secret boundary test' must not receive NVIDIA_INFERENCE_API_KEY",
"build-hermes-sandbox-image step 'Run Hermes sandbox secret boundary test' must not receive DOCKERHUB_TOKEN",
"test-hermes-sandbox-image must not expose NVIDIA_INFERENCE_API_KEY at job scope",
"test-hermes-sandbox-image step 'Run Hermes sandbox secret boundary test' must not receive NVIDIA_INFERENCE_API_KEY",
"test-hermes-sandbox-image step 'Run Hermes sandbox secret boundary test' must not receive DOCKERHUB_TOKEN",
]),
);
});
Expand Down
Loading
Loading