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
119 changes: 112 additions & 7 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1160,13 +1160,6 @@ jobs:
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/mcp-bridge.test.ts
if [[ "$NEMOCLAW_MCP_BRIDGE_AGENT" == "deepagents" ]]; then
npx vitest run --project e2e-live \
test/e2e/live/openshell-credential-generation-window.test.ts \
-t '^openshell-credential-generation-window$' \
--no-file-parallelism \
--silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts
fi

- name: Finalize runner comparison telemetry
if: ${{ always() && github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && inputs.checkout_sha == '' && (matrix.agent == 'hermes' || matrix.agent == 'deepagents') }}
Expand All @@ -1193,6 +1186,117 @@ jobs:
shell: bash
run: bash .github/scripts/docker-auth-cleanup.sh

openshell-credential-generation-window:
needs: generate-matrix
if: ${{ (github.event_name != 'workflow_dispatch' || (inputs.jobs == '' && inputs.targets == '')) || contains(format(',{0},', inputs.jobs), ',mcp-bridge,') || contains(format(',{0},', inputs.targets), ',mcp-bridge,') || contains(format(',{0},', inputs.jobs), ',openshell-credential-generation-window,') || contains(format(',{0},', inputs.targets), ',openshell-credential-generation-window,') }}
runs-on: ubuntu-latest
permissions:
contents: read
# Keep the credential-generation lifecycle on a fresh runner so it can
# execute in parallel with, and fail independently from, the Deep Agents
# MCP lifecycle without sharing destructive sandbox state.
timeout-minutes: 90
env:
E2E_JOB: "1"
E2E_TARGET_ID: "openshell-credential-generation-window"
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openshell-credential-generation-window
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_OPENSHELL_CHANNEL: stable
NEMOCLAW_OPENSHELL_EXACT_MAIN_PROOF: "1"
NEMOCLAW_RUN_LIVE_E2E: "1"
OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:f4226253a3525c3832adac5b38b419a0f27d1e915effe565b5885e20f93cd5e9
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.checkout_repository || github.repository }}
ref: ${{ inputs.checkout_sha || github.sha }}
persist-credentials: false

- *dockerhub-auth

- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75

- name: Install and verify cloudflared prerequisite
env:
CLOUDFLARED_VERSION: "2026.6.1"
CLOUDFLARED_DEB_SHA256: "ccd02ec216c62bfa573395d8f72cb2e91e95cbdf8726a8acc06b3e2d9aa31526"
run: |
set -euo pipefail
cloudflared_deb="${RUNNER_TEMP}/cloudflared-${CLOUDFLARED_VERSION}-linux-amd64.deb"
curl -fL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb" -o "${cloudflared_deb}"
printf '%s %s\n' "${CLOUDFLARED_DEB_SHA256}" "${cloudflared_deb}" | sha256sum -c -
package="$(dpkg-deb -f "${cloudflared_deb}" Package)"
version="$(dpkg-deb -f "${cloudflared_deb}" Version)"
architecture="$(dpkg-deb -f "${cloudflared_deb}" Architecture)"
if [[ "${package}" != "cloudflared" || "${version}" != "${CLOUDFLARED_VERSION}" || "${architecture}" != "amd64" ]]; then
printf 'Unexpected cloudflared package metadata: package=%s version=%s architecture=%s\n' "${package}" "${version}" "${architecture}" >&2
exit 1
fi
sudo dpkg -i "${cloudflared_deb}"
cloudflared --version | grep -F "cloudflared version ${CLOUDFLARED_VERSION}"

- name: Generate MCP test TLS
run: bash test/e2e/setup-mcp-test-tls.sh

- name: Install OpenShell CLI
env:
NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1"
run: |
set -euo pipefail
bash scripts/install-openshell.sh
mkdir -p "$E2E_ARTIFACT_DIR/openshell-credential-generation-window"
jq -n '{
schemaVersion: 1,
sourceRepository: "NVIDIA/OpenShell",
releaseTag: "v0.0.85",
sourceSha: "3dee5570a46076a57a3b056f35f35ebc0861ac85",
artifacts: {
cli: {binarySha256: "222d9d53a142691d7a7de2c692f38e52d24066f9f633d53746c5fef775861bc8"},
gateway: {binarySha256: "33bb479d936c3c1b17dd475df05747be9de74564fb67d69a4c33cdd01181d02f"},
standaloneSandbox: {binarySha256: "863ef21ab7ef623f5e7a8728c4e5532b46bfbae3ace3b800665a1c6353a1f7d2"}
}
}' > "$E2E_ARTIFACT_DIR/openshell-credential-generation-window/openshell-exact-main-provenance.json"

- name: Run OpenShell credential generation-window live test
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
if command -v openshell >/dev/null 2>&1; then
OPENSHELL_BIN="$(command -v openshell)"
elif [ -x "$HOME/.local/bin/openshell" ]; then
OPENSHELL_BIN="$HOME/.local/bin/openshell"
else
echo "::error::OpenShell CLI not found after install"
exit 1
fi
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx vitest run --project e2e-live \
test/e2e/live/openshell-credential-generation-window.test.ts \
-t '^openshell-credential-generation-window$' \
--no-file-parallelism \
--silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts

- id: credential_window_artifact_secret_scan
name: Scan credential-window artifacts for fixture credentials
if: always()
run: >-
npx tsx tools/e2e/assert-mcp-artifact-secrets-absent.mts
e2e-artifacts/live/openshell-credential-generation-window

- name: Upload credential-window artifacts
if: ${{ always() && steps.credential_window_artifact_secret_scan.outcome == 'success' }}
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
with:
name: e2e-openshell-credential-generation-window
path: e2e-artifacts/live/openshell-credential-generation-window/

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

mcp-bridge-dev:
needs: generate-matrix
# Moving OpenShell dev artifacts are compatibility evidence only and must
Expand Down Expand Up @@ -5850,6 +5954,7 @@ jobs:
shared-e2e,
openshell-gateway-auth-contract,
mcp-bridge,
openshell-credential-generation-window,
mcp-bridge-dev,
skill-agent,
openclaw-skill-cli,
Expand Down
15 changes: 5 additions & 10 deletions docs/security/openshell-0.0.85-migration-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,16 +551,11 @@ usable. The target then reattaches through `mcp restart`, rebuilds without the
host MCP secret, and removes the bridge. Every request is identified independently
of its credential, and the upstream ledger is required to contain no literal
resolve placeholder.
The stable workflow keeps the OpenClaw, Hermes, and Deep Agents MCP lifecycles
on three fresh-runner shards with separate artifacts. It runs the
`openshell-credential-generation-window` live target once, after the Deep Agents
case, rather than repeating that proof in every shard.
The workflow first ran this bounded target against the exact reviewed development
artifacts and scanned its artifacts for the whole generated-secret prefix. The
stable-source review retains the upstream
`expired_retained_generation_does_not_resolve` unit. The default stable job now
binds the target to tag commit `3dee5570`, the extracted release binaries, and the
immutable supervisor index; its final result for the PR SHA remains a merge gate.
The stable workflow keeps the OpenClaw, Hermes, and Deep Agents MCP lifecycles on three fresh-runner shards with separate artifacts.
It runs the `openshell-credential-generation-window` live target once on a fourth fresh runner in parallel with the stable MCP agent matrix, rather than serializing or repeating that proof in the agent matrix.
The workflow first ran this bounded target against the exact reviewed development artifacts and scanned its artifacts for the whole generated-secret prefix.
The stable-source review retains the upstream `expired_retained_generation_does_not_resolve` unit.
The default stable job now binds the target to tag commit `3dee5570`, the extracted release binaries, and the immutable supervisor index; its final result for the PR SHA remains a merge gate.

### v0.0.82 to v0.0.83

Expand Down
18 changes: 9 additions & 9 deletions scripts/checks/check-cloudflared-update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# invalidState: the four reviewed E2E consumers drift to different cloudflared
# invalidState: the five reviewed E2E consumers drift to different cloudflared
# versions/digests, or their shared pin no longer matches the upstream asset.
# sourceBoundary: Cloudflare owns the release asset; NemoClaw owns all four
# sourceBoundary: Cloudflare owns the release asset; NemoClaw owns all five
# workflow pins and independently verifies the downloaded bytes.
# whyNotSourceFix: upstream cannot enforce which release NemoClaw workflows use.
# regressionTest: cloudflared-update-check-workflow.test.ts covers four-pin
# regressionTest: cloudflared-update-check-workflow.test.ts covers five-pin
# parity, asset URL identity, digest mismatch, and update instructions.
# removalCondition: remove this checker when the four consumers share one
# removalCondition: remove this checker when the five consumers share one
# machine-readable dependency manifest with equivalent live asset verification.

set -euo pipefail
Expand Down Expand Up @@ -48,10 +48,10 @@ done < <(
"${E2E_WORKFLOW}"
)

[[ "${#version_pins[@]}" -eq 4 ]] \
|| fail "expected exactly four CLOUDFLARED_VERSION pins in ${E2E_WORKFLOW}; found ${#version_pins[@]}"
[[ "${#sha_pins[@]}" -eq 4 ]] \
|| fail "expected exactly four CLOUDFLARED_DEB_SHA256 pins in ${E2E_WORKFLOW}; found ${#sha_pins[@]}"
[[ "${#version_pins[@]}" -eq 5 ]] \
|| fail "expected exactly five CLOUDFLARED_VERSION pins in ${E2E_WORKFLOW}; found ${#version_pins[@]}"
[[ "${#sha_pins[@]}" -eq 5 ]] \
|| fail "expected exactly five CLOUDFLARED_DEB_SHA256 pins in ${E2E_WORKFLOW}; found ${#sha_pins[@]}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

pinned_version="${version_pins[0]}"
pinned_sha="$(printf '%s' "${sha_pins[0]}" | tr '[:upper:]' '[:lower:]')"
Expand Down Expand Up @@ -127,7 +127,7 @@ print_update_instructions() {
'Update locations:' \
" ${workflow_display} CLOUDFLARED_VERSION lines: ${version_lines}" \
" ${workflow_display} CLOUDFLARED_DEB_SHA256 lines: ${sha_lines}" \
'Set all four version/SHA256 pairs to the latest reviewed values, then rerun this check.' >&2
'Set all five version/SHA256 pairs to the latest reviewed values, then rerun this check.' >&2
}

if [[ "${latest_version}" != "${pinned_version}" ]]; then
Expand Down
10 changes: 5 additions & 5 deletions test/cloudflared-update-check-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function pinValues(source: string, name: string): string[] {
function writePinFixture(file: string, version: string, sha256: string): void {
fs.writeFileSync(
file,
["one", "two", "three", "four"]
["one", "two", "three", "four", "five"]
.map(
(job) =>
` ${job}:\n env:\n CLOUDFLARED_VERSION: "${version}"\n CLOUDFLARED_DEB_SHA256: "${sha256}"`,
Expand Down Expand Up @@ -149,11 +149,11 @@ describe("cloudflared update-check workflow contract", () => {
expect(checkout?.with?.["persist-credentials"]).toBe(false);
});

it("extracts exactly four identical reviewed version and SHA256 pins", () => {
it("extracts exactly five identical reviewed version and SHA256 pins", () => {
const versions = pinValues(e2e, "CLOUDFLARED_VERSION");
const hashes = pinValues(e2e, "CLOUDFLARED_DEB_SHA256");
expect(versions).toHaveLength(4);
expect(hashes).toHaveLength(4);
expect(versions).toHaveLength(5);
expect(hashes).toHaveLength(5);
Comment thread
apurvvkumaria marked this conversation as resolved.
expect(new Set(versions).size).toBe(1);
expect(new Set(hashes).size).toBe(1);
expect(versions[0]).toMatch(/^[0-9]{4}\.[0-9]{1,2}\.[0-9]+$/u);
Expand Down Expand Up @@ -203,7 +203,7 @@ describe("cloudflared update-check workflow contract", () => {
);
expect(fixture.result.stderr).toContain("CLOUDFLARED_VERSION lines:");
expect(fixture.result.stderr).toContain("CLOUDFLARED_DEB_SHA256 lines:");
expect(fixture.result.stderr).toContain("Set all four version/SHA256 pairs");
expect(fixture.result.stderr).toContain("Set all five version/SHA256 pairs");
} finally {
fs.rmSync(fixture.tempDir, { recursive: true, force: true });
}
Expand Down
12 changes: 8 additions & 4 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,14 @@ lanes:
- `rebuild-hermes-stale-base`;
- the `hermes` and `deepagents` shards of `mcp-bridge`.

The OpenClaw shards of the matrix jobs, the `openclaw` MCP shard, and
`mcp-bridge-dev` remain on `ubuntu-latest`; unrelated jobs retain their
existing runner assignments. Before setting the variable, an organization
owner must:
The OpenClaw shards of the matrix jobs, the `openclaw` MCP shard,
`mcp-bridge-dev`, and `openshell-credential-generation-window` remain on
`ubuntu-latest`; unrelated jobs retain their existing runner assignments.
The credential-generation window runs as an independent fresh-runner job in
parallel with the stable MCP agent matrix. Default full-suite dispatches and
explicit `mcp-bridge` selections run both jobs, while the credential-window job
keeps its own exact-release provenance, secret scan, and artifact.
Before setting the variable, an organization owner must:

1. Create a GitHub-hosted Ubuntu x64 larger runner with 8 vCPU, 32 GB RAM, and
300 GB SSD in a dedicated runner group.
Expand Down
Loading
Loading