Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
71ad9e7
test(e2e): finish sandbox image suite migration
cv Sep 3, 2026
3ca7149
ci(images): rename sandbox image contract workflow
cv Sep 3, 2026
7e791b0
test(e2e): register managed image mock parity
cv Sep 3, 2026
378e1d9
Merge branch 'main' into refactor/finish-image-e2e-migration
cv Sep 3, 2026
976541f
test(e2e): preserve image security qualification
cv Sep 3, 2026
c29f312
test(e2e): retain fail-closed image recovery
cv Sep 3, 2026
25da558
test(e2e): simplify security helper ownership
cv Sep 3, 2026
87d6dad
test(e2e): qualify image entrypoint lifecycle
cv Sep 3, 2026
3b31472
test(e2e): close image qualification gaps
cv Sep 3, 2026
ee3d683
test(e2e): bind final image evidence
cv Sep 3, 2026
fd508f2
test(e2e): sequence protected resource cleanup
cv Sep 3, 2026
959ef7f
test(e2e): harden cleanup recovery
cv Sep 3, 2026
1172514
ci(images): consolidate final image qualification
cv Sep 3, 2026
9eea87d
Merge remote-tracking branch 'origin/main' into refactor/finish-image…
cv Sep 3, 2026
ce2fa73
test(e2e): align image proof with assertion ratchet
cv Sep 3, 2026
7094d07
test(images): retain packaged entrypoint boundaries
cv Sep 3, 2026
67a3567
test(images): close entrypoint evidence gaps
cv Sep 3, 2026
dc1d102
test(images): complete cleanup reconciliation
cv Sep 3, 2026
45f5439
test(images): preserve packaged migration contract
cv Sep 3, 2026
8aabd24
ci(images): preserve independent diagnostics
cv Sep 3, 2026
c2c0bfe
Merge branch 'main' into refactor/finish-image-e2e-migration
cv Sep 3, 2026
38d8e23
test(e2e): align deterministic parity ownership
cv Sep 3, 2026
e0bfad7
test(images): harden qualification ownership
cv Sep 3, 2026
4371d11
merge: resolve conflicts with main
github-actions[bot] Sep 3, 2026
c2a49c5
test(images): bind protected recovery evidence
cv Sep 3, 2026
e2b312e
Merge remote-tracking branch 'origin/main' into refactor/finish-image…
cv Sep 3, 2026
32b5370
Merge remote-tracking branch 'origin/main' into refactor/finish-image…
cv Sep 3, 2026
6dc872e
test(package): install registry transport as consumer
rsliter Sep 3, 2026
593d940
test(images): close local advisor findings
cv Sep 3, 2026
cd17540
test(images): retain privileged recovery proof
cv Sep 3, 2026
0f6def4
test(images): enforce reusable security consumer
cv Sep 3, 2026
64436f5
Merge remote-tracking branch 'origin/main' into refactor/finish-image…
cv Sep 3, 2026
4e24adb
test(images): type workflow mutation values
cv Sep 3, 2026
242899c
test(images): retain packaged apply proof
cv Sep 3, 2026
1aef895
test(images): bind packaged apply receipts
cv Sep 3, 2026
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
74 changes: 59 additions & 15 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4222,7 +4222,7 @@ jobs:
E2E_AGENT_RUNTIME: "openclaw + hermes + langchain-deepagents-code"
E2E_OBSERVABLE_OUTCOME: "Exact managed images start directly on the native architecture"
RELEASE_E2E_ACTIVATION_PATH: ci/protected-managed-image-multiarch-activation-v1.json
NEMOCLAW_E2E_EXPECTED_SHA: ${{ inputs.checkout_sha }}
NEMOCLAW_E2E_EXPECTED_SHA: ${{ inputs.checkout_sha || github.sha }}
NEMOCLAW_PROTECTED_MANAGED_IMAGE_HEAD_SHA: ${{ inputs.checkout_sha || github.sha }}
NEMOCLAW_E2E_SHARD: ${{ matrix.shard }}
NEMOCLAW_PROTECTED_MANAGED_IMAGE_BASE_SHA: ${{ inputs.base_sha || github.event.before || github.sha }}
Expand All @@ -4236,6 +4236,20 @@ jobs:
NEMOCLAW_PROTECTED_REGISTRY_NAME: nemoclaw-managed-${{ matrix.shard }}-${{ github.run_id }}-${{ github.run_attempt }}
NEMOCLAW_RUN_LIVE_E2E: "1"
steps:
- name: Bind protected E2E correlation identity
shell: bash
env:
REQUESTED_CORRELATION_ID: ${{ inputs.correlation_id }}
run: |
set -euo pipefail
correlation_id="$REQUESTED_CORRELATION_ID"
if [[ -z "$correlation_id" ]]; then
correlation_id="$(node --input-type=module -e \
'import { randomUUID } from "node:crypto"; console.log(randomUUID())')"
fi
[[ "$correlation_id" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$ ]]
printf 'NEMOCLAW_E2E_CORRELATION_ID=%s\n' "$correlation_id" >>"$GITHUB_ENV"

- name: Validate protected exact-head dispatch
env:
BASE_SHA: ${{ inputs.base_sha || github.event.before || github.sha }}
Expand Down Expand Up @@ -4444,7 +4458,7 @@ jobs:
fi
docker run --detach \
--name "$NEMOCLAW_PROTECTED_REGISTRY_NAME" \
--label "io.nvidia.nemoclaw.e2e-owner=${NEMOCLAW_PROTECTED_MANAGED_IMAGE_COHORT}" \
--label "io.nvidia.nemoclaw.managed-image.cohort=${NEMOCLAW_PROTECTED_MANAGED_IMAGE_COHORT}" \
--label "io.nvidia.nemoclaw.e2e-platform=${NEMOCLAW_PROTECTED_MANAGED_IMAGE_PLATFORM}" \
--publish 127.0.0.1:5000:5000 \
docker.io/library/registry@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373
Expand Down Expand Up @@ -4540,29 +4554,23 @@ jobs:
"${NEMOCLAW_PROTECTED_MANAGED_IMAGE_EVIDENCE}.tmp" \
"$NEMOCLAW_PROTECTED_MANAGED_IMAGE_EVIDENCE"


- name: Remove isolated protected managed-image registry
if: always()
shell: bash
run: |
set -euo pipefail
if docker container inspect "$NEMOCLAW_PROTECTED_REGISTRY_NAME" >/dev/null 2>&1; then
owner="$(
docker container inspect \
--format '{{index .Config.Labels "io.nvidia.nemoclaw.e2e-owner"}}' \
"$NEMOCLAW_PROTECTED_REGISTRY_NAME"
)"
owner="$(docker container inspect --format '{{index .Config.Labels "io.nvidia.nemoclaw.managed-image.cohort"}}' "$NEMOCLAW_PROTECTED_REGISTRY_NAME")"
[[ "$owner" == "$NEMOCLAW_PROTECTED_MANAGED_IMAGE_COHORT" ]] || {
echo "::error::Refusing to remove a registry not owned by this protected shard" >&2
exit 1
}
docker rm -f "$NEMOCLAW_PROTECTED_REGISTRY_NAME" >/dev/null
fi
if docker container inspect "$NEMOCLAW_PROTECTED_REGISTRY_NAME" >/dev/null 2>&1; then
echo "::error::Protected managed-image registry container remained after cleanup" >&2
exit 1
fi
if curl --fail --silent --show-error http://127.0.0.1:5000/v2/ >/dev/null 2>&1; then
echo "::error::Protected managed-image registry listener remained after cleanup" >&2
if docker container inspect "$NEMOCLAW_PROTECTED_REGISTRY_NAME" >/dev/null 2>&1 ||
curl --fail --silent --show-error http://127.0.0.1:5000/v2/ >/dev/null 2>&1; then
echo "::error::Protected managed-image registry remained after cleanup" >&2
exit 1
fi

Expand All @@ -4571,6 +4579,42 @@ jobs:
run: >-
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/managed-image-multiarch-startup.test.ts

- name: Validate OpenClaw managed-image security boundary
shell: bash
run: >-
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN npx vitest run --project integration test/e2e-runtime/managed-image-openclaw-security.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts

- name: Validate managed-image glibc probe lifecycle
if: ${{ !cancelled() }}
shell: bash
run: |
set -euo pipefail
export NEMOCLAW_RUN_GLIBC_PROBE_DOCKER_E2E=1
NEMOCLAW_TEST_IMAGE="$(jq -er '.[] | select(.agent == "openclaw") | .reference' "$NEMOCLAW_PROTECTED_MANAGED_IMAGE_CONTRACT")"
export NEMOCLAW_TEST_IMAGE
env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN npx vitest run --project integration test/e2e-runtime/image-compatibility-docker-lifecycle.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts

- name: Remove protected managed-image cohort resources
if: always()
shell: bash
run: |
set -euo pipefail
label="io.nvidia.nemoclaw.managed-image.cohort=${NEMOCLAW_PROTECTED_MANAGED_IMAGE_COHORT}"
cleanup_failed=0
mapfile -t owned_containers < <(docker ps -aq --filter "label=$label")
((${#owned_containers[@]} == 0)) || docker rm -f "${owned_containers[@]}" >/dev/null || cleanup_failed=1
mapfile -t owned_images < <(docker image ls -q --filter "label=$label" | sort -u)
((${#owned_images[@]} == 0)) || docker image rm -f "${owned_images[@]}" >/dev/null || cleanup_failed=1
mapfile -t owned_volumes < <(docker volume ls -q --filter "label=$label")
((${#owned_volumes[@]} == 0)) || docker volume rm -f "${owned_volumes[@]}" >/dev/null || cleanup_failed=1
if docker ps -aq --filter "label=$label" | grep -q . ||
docker image ls -q --filter "label=$label" | grep -q . ||
docker volume ls -q --filter "label=$label" | grep -q .; then
echo "::error::Protected managed-image cohort resources remained after cleanup" >&2
exit 1
fi
((cleanup_failed == 0)) || exit 1

- name: Publish exact amd64 protected runtime build cache
if: ${{ matrix.platform == 'linux/amd64' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down Expand Up @@ -5086,7 +5130,7 @@ jobs:
fi
docker run --detach \
--name "$NEMOCLAW_PROTECTED_REGISTRY_NAME" \
--label "io.nvidia.nemoclaw.e2e-owner=${NEMOCLAW_PROTECTED_MANAGED_IMAGE_COHORT}" \
--label "io.nvidia.nemoclaw.managed-image.cohort=${NEMOCLAW_PROTECTED_MANAGED_IMAGE_COHORT}" \
--label "io.nvidia.nemoclaw.e2e-platform=linux/amd64" \
--publish 127.0.0.1:5000:5000 \
docker.io/library/registry@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373
Expand Down Expand Up @@ -5146,7 +5190,7 @@ jobs:
if docker container inspect "$NEMOCLAW_PROTECTED_REGISTRY_NAME" >/dev/null 2>&1; then
owner="$(
docker container inspect \
--format '{{index .Config.Labels "io.nvidia.nemoclaw.e2e-owner"}}' \
--format '{{index .Config.Labels "io.nvidia.nemoclaw.managed-image.cohort"}}' \
"$NEMOCLAW_PROTECTED_REGISTRY_NAME"
)"
[[ "$owner" == "$NEMOCLAW_PROTECTED_MANAGED_IMAGE_COHORT" ]] || {
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ jobs:
- real-openclaw-dist-harness
- cli-tests
- plugin-tests
- sandbox-images-and-e2e
- sandbox-image-contracts
if: always()
permissions:
actions: read
Expand All @@ -366,7 +366,7 @@ jobs:
CLI_TESTS_RESULT: ${{ needs['cli-tests'].result }}
GH_TOKEN: ${{ github.token }}
PLUGIN_TESTS_RESULT: ${{ needs['plugin-tests'].result }}
SANDBOX_IMAGES_E2E_RESULT: ${{ needs['sandbox-images-and-e2e'].result }}
SANDBOX_IMAGE_CONTRACTS_RESULT: ${{ needs['sandbox-image-contracts'].result }}
RUN_ATTEMPT: ${{ github.run_attempt }}
RUN_ID: ${{ github.run_id }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
Expand Down Expand Up @@ -404,7 +404,7 @@ jobs:
local job_id=""
dependency_url="$RUN_URL"
case "$name" in
static-checks|build-typecheck|installer-integration|wechat-runtime-audit|reviewed-npm-audit|real-openclaw-dist-harness|cli-tests|plugin-tests|sandbox-images-and-e2e) ;;
static-checks|build-typecheck|installer-integration|wechat-runtime-audit|reviewed-npm-audit|real-openclaw-dist-harness|cli-tests|plugin-tests|sandbox-image-contracts) ;;
*) return ;;
esac
load_job_listing
Expand Down Expand Up @@ -447,13 +447,13 @@ jobs:
require_success "real-openclaw-dist-harness" "$REAL_OPENCLAW_DIST_HARNESS_RESULT"
require_success "cli-tests" "$CLI_TESTS_RESULT"
require_success "plugin-tests" "$PLUGIN_TESTS_RESULT"
require_success "sandbox-images-and-e2e" "$SANDBOX_IMAGES_E2E_RESULT"
require_success "sandbox-image-contracts" "$SANDBOX_IMAGE_CONTRACTS_RESULT"

[ "$failed" -eq 0 ]

sandbox-images-and-e2e:
sandbox-image-contracts:
needs: [static-checks, build-typecheck]
uses: ./.github/workflows/sandbox-images-and-e2e.yaml
uses: ./.github/workflows/sandbox-images.yaml
secrets:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
97 changes: 46 additions & 51 deletions .github/workflows/pr-self-hosted.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -204,21 +204,8 @@ jobs:
scripts/check-production-build-args.sh "${build_args[@]}"
docker build "${build_args[@]}" -t nemoclaw-production .

- name: Build sandbox test image (fixtures layered on production)
run: docker build -f test/Dockerfile.sandbox --build-arg BASE_IMAGE=nemoclaw-production -t nemoclaw-sandbox-test .

- name: Save images to tarballs
run: |
docker save nemoclaw-sandbox-test | gzip > /tmp/sandbox-test-image.tar.gz
docker save nemoclaw-production | gzip > /tmp/isolation-image.tar.gz

- name: Upload sandbox test image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sandbox-test-image
path: /tmp/sandbox-test-image.tar.gz
retention-days: 1
if-no-files-found: error
- name: Save production image
run: docker save nemoclaw-production | gzip > /tmp/isolation-image.tar.gz

- name: Upload isolation image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down Expand Up @@ -249,40 +236,29 @@ jobs:
scripts/check-production-build-args.sh "${build_args[@]}"
docker build "${build_args[@]}" -t nemoclaw-production-arm64 .

- name: Build sandbox test image on arm64
run: docker build -f test/Dockerfile.sandbox --build-arg BASE_IMAGE=nemoclaw-production-arm64 -t nemoclaw-sandbox-test-arm64 .

test-e2e-sandbox:
managed-image-openclaw-security:
runs-on: linux-amd64-cpu4
timeout-minutes: 15
timeout-minutes: 10
needs: build-sandbox-images
env:
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/managed-image-openclaw-security
E2E_TARGET_ID: managed-image-openclaw-security
NEMOCLAW_E2E_SHARD: default
NEMOCLAW_MANAGED_IMAGE_SECURITY_COHORT: pr-${{ github.run_id }}-${{ github.run_attempt }}
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_TEST_IMAGE: nemoclaw-production
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Download image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sandbox-test-image
path: /tmp

- name: Load image
run: gunzip -c /tmp/sandbox-test-image.tar.gz | docker load

- name: Run sandbox E2E tests
run: docker run --rm -v "${{ github.workspace }}/test:/opt/test" nemoclaw-sandbox-test /opt/test/e2e-test.sh

test-e2e-gateway-isolation:
runs-on: linux-amd64-cpu4
timeout-minutes: 15
needs: build-sandbox-images
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Bind managed-image risk signal identity
run: |
set -euo pipefail
echo "NEMOCLAW_E2E_EXPECTED_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
echo "NEMOCLAW_E2E_CORRELATION_ID=$(python3 -c 'import uuid; print(uuid.uuid4())')" >> "$GITHUB_ENV"

- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
Expand All @@ -291,7 +267,7 @@ jobs:
cache: npm

- name: Install root dependencies
run: npm ci --ignore-scripts
run: npm ci --ignore-scripts --no-audit --no-fund

- name: Download image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand All @@ -300,20 +276,39 @@ jobs:
path: /tmp

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

- name: Run glibc probe lifecycle regression
- name: Validate OpenClaw managed-image security boundary
run: >-
npx vitest run --project integration test/e2e-runtime/managed-image-openclaw-security.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts

- name: Validate glibc probe lifecycle
if: ${{ !cancelled() }}
env:
NEMOCLAW_RUN_GLIBC_PROBE_DOCKER_E2E: "1"
NEMOCLAW_TEST_IMAGE: nemoclaw-production
run: npx vitest run --project integration test/e2e-runtime/image-compatibility-docker-lifecycle.test.ts --silent=false --reporter=default

- name: Run gateway isolation E2E tests
run: NEMOCLAW_TEST_IMAGE=nemoclaw-production bash test/e2e-gateway-isolation.sh
run: >-
npx vitest run --project integration test/e2e-runtime/image-compatibility-docker-lifecycle.test.ts
--silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts

test-e2e-port-overrides:
- name: Remove managed-image security resources
if: ${{ always() }}
run: |
set -euo pipefail
label="io.nvidia.nemoclaw.managed-image.cohort=$NEMOCLAW_MANAGED_IMAGE_SECURITY_COHORT"
cleanup_failed=0
mapfile -t containers < <(docker ps -aq --filter "label=$label")
((${#containers[@]} == 0)) || docker rm -f "${containers[@]}" >/dev/null || cleanup_failed=1
mapfile -t volumes < <(docker volume ls -q --filter "label=$label")
((${#volumes[@]} == 0)) || docker volume rm -f "${volumes[@]}" >/dev/null || cleanup_failed=1
docker ps -aq --filter "label=$label" | grep -q . && cleanup_failed=1
docker volume ls -q --filter "label=$label" | grep -q . && cleanup_failed=1
exit "$cleanup_failed"

- name: Upload OpenClaw managed-image security evidence
if: ${{ always() }}
uses: ./.github/actions/upload-e2e-artifacts

port-override-image-contract:
runs-on: linux-amd64-cpu4
timeout-minutes: 10
needs: build-sandbox-images
Expand Down
Loading
Loading