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
2 changes: 2 additions & 0 deletions .github/workflows/e2e-branch-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ name: e2e-branch-validation
# isolation, openclaw.json config patching, network reachability,
# and L7 proxy token rewriting for Telegram + Discord. Creates
# its own sandbox (e2e-msg-provider). (~15 min)
# dashboard-remote-bind — Verifies opt-in remote dashboard forwards bind 0.0.0.0.
# all — Runs credential-sanitization + telegram-injection (NOT full,
# which destroys the sandbox the security tests need).
#
Expand All @@ -65,6 +66,7 @@ on:
- credential-sanitization
- telegram-injection
- messaging-providers
- dashboard-remote-bind
- all
use_launchable:
description: "Use CI launchable (true) or bare brev create + brev-setup.sh (false)"
Expand Down
57 changes: 57 additions & 0 deletions .github/workflows/regression-e2e.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

name: regression-e2e

# Regression E2E holding pen.
#
# Jobs here are intentionally NOT part of scheduled nightly-e2e. They are
# failing-test-first coverage guards or high-signal regressions that should be
# easy to dispatch while the owning fix is in flight. Periodically review this
# workflow and promote stable/high-value jobs into nightly-e2e.

on:
workflow_dispatch:
inputs:
pr_number:
description: "PR number (optional; creates a check run on that PR)"
required: false
type: string
default: ""
jobs:
description: >-
Comma-separated regression job names to run (empty = all).
Valid: dashboard-remote-bind-e2e
required: false
type: string
default: ""
keep_alive:
description: "Keep Brev instance alive after tests (for SSH debugging)"
required: false
type: boolean
default: false

permissions:
contents: read
checks: write
pull-requests: write

concurrency:
group: regression-e2e-${{ github.event_name }}-${{ github.ref }}-${{ inputs.jobs || 'all' }}-${{ inputs.pr_number || github.run_id }}
cancel-in-progress: true
Comment thread
coderabbitai[bot] marked this conversation as resolved.

jobs:
dashboard-remote-bind-e2e:
if: >-
github.repository == 'NVIDIA/NemoClaw' &&
(github.event_name != 'workflow_dispatch' ||
inputs.jobs == '' ||
contains(format(',{0},', inputs.jobs), ',dashboard-remote-bind-e2e,'))
uses: ./.github/workflows/e2e-branch-validation.yaml
with:
branch: ${{ github.ref_name }}
pr_number: ${{ inputs.pr_number }}
test_suite: dashboard-remote-bind
use_launchable: true
keep_alive: ${{ inputs.keep_alive }}
secrets: inherit
12 changes: 11 additions & 1 deletion test/e2e/brev-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* Optional env vars:
* TEST_SUITE — which test to run: full (default), deploy-cli, credential-sanitization,
* telegram-injection, messaging-providers,
* messaging-compatible-endpoint, all
* messaging-compatible-endpoint, dashboard-remote-bind, all
* LAUNCHABLE_SETUP_SCRIPT — URL to setup script for launchable path (default: brev-launchable-ci-cpu.sh on main)
* BREV_MIN_VCPU — Minimum vCPUs for CPU instance (default: 4)
* BREV_MIN_RAM — Minimum RAM in GB for CPU instance (default: 16)
Expand Down Expand Up @@ -840,4 +840,14 @@ describe.runIf(hasRequiredVars && hasAuthenticatedBrev)("Brev E2E", () => {
},
900_000, // 15 min — creates a new sandbox with Telegram + compatible endpoint
);

it.runIf(TEST_SUITE === "dashboard-remote-bind")(
"dashboard forward binds to all interfaces for remote browser origins",
() => {
const output = runRemoteTest("test/e2e/test-dashboard-remote-bind.sh");
expect(output).toContain("PASS");
expect(output).not.toMatch(/FAIL:/);
},
300_000,
);
});
72 changes: 72 additions & 0 deletions test/e2e/test-dashboard-remote-bind.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -uo pipefail

section() { printf '\n=== %s ===\n' "$1"; }
pass() { echo "PASS: $1"; }
fail() {
echo "FAIL: $1"
exit 1
}
info() { echo "INFO: $1"; }

SANDBOX_NAME="${NEMOCLAW_SANDBOX_NAME:-e2e-test}"
DASHBOARD_PORT="${NEMOCLAW_DASHBOARD_PORT:-18789}"
REMOTE_HOST="${NEMOCLAW_E2E_REMOTE_HOST:-$(hostname -I 2>/dev/null | awk '{print $1}')}"
if [ -z "$REMOTE_HOST" ]; then
REMOTE_HOST="$(hostname -f 2>/dev/null || hostname)"
fi

section "Preconditions"
info "Sandbox: ${SANDBOX_NAME}"
info "Dashboard port: ${DASHBOARD_PORT}"
info "Remote host candidate: ${REMOTE_HOST}"

if ! command -v nemoclaw >/dev/null 2>&1; then
fail "nemoclaw CLI is not on PATH"
fi
if ! command -v openshell >/dev/null 2>&1; then
fail "openshell CLI is not on PATH"
fi
pass "Required CLIs are available"

section "Restart dashboard forward with explicit all-interface bind"
# The coverage guard mirrors issue #3259: remote SSH-deployed hosts need an
# explicit operator-controlled way to bind the dashboard forward on all
# interfaces. On main, NEMOCLAW_DASHBOARD_BIND is ignored and the forward stays
# localhost-only; the fix should make this opt-in produce 0.0.0.0:<port>.
openshell forward stop "${DASHBOARD_PORT}" >/dev/null 2>&1 || true
CONNECT_LOG="$(mktemp -t nemoclaw-dashboard-remote-bind.XXXXXX.log)"
trap 'rm -f "${CONNECT_LOG}"' EXIT
if NEMOCLAW_DASHBOARD_BIND=0.0.0.0 nemoclaw "${SANDBOX_NAME}" connect >"${CONNECT_LOG}" 2>&1; then
pass "nemoclaw connect completed with NEMOCLAW_DASHBOARD_BIND=0.0.0.0"
else
cat "${CONNECT_LOG}"
fail "nemoclaw connect failed with NEMOCLAW_DASHBOARD_BIND=0.0.0.0"
fi

section "Verify OpenShell forward bind"
FORWARD_LIST="$(openshell forward list 2>/dev/null || true)"
printf '%s\n' "${FORWARD_LIST}"
FORWARD_LINE="$(printf '%s\n' "${FORWARD_LIST}" | awk -v sandbox="${SANDBOX_NAME}" -v port="${DASHBOARD_PORT}" '$0 ~ sandbox && $0 ~ port {print; exit}')"
if [ -z "${FORWARD_LINE}" ]; then
fail "No OpenShell forward found for ${SANDBOX_NAME} on ${DASHBOARD_PORT}"
fi
info "Matched forward: ${FORWARD_LINE}"

case "${FORWARD_LINE}" in
*"0.0.0.0:${DASHBOARD_PORT}"* | *"*:""${DASHBOARD_PORT}"* | *"0.0.0.0 "*" ${DASHBOARD_PORT} "*)
pass "Dashboard forward binds all interfaces for remote origin (${DASHBOARD_PORT})"
;;
*"127.0.0.1:${DASHBOARD_PORT}"* | *"localhost:${DASHBOARD_PORT}"* | *"127.0.0.1 "*" ${DASHBOARD_PORT} "*)
fail "Dashboard forward is still localhost-only; expected 0.0.0.0:${DASHBOARD_PORT}"
;;
*)
fail "Could not prove dashboard forward uses 0.0.0.0:${DASHBOARD_PORT} from: ${FORWARD_LINE}"
;;
esac

section "Summary"
pass "Remote dashboard bind guard completed"
Loading