Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
815e2d7
fix(channels): include Slack guard in proxy-env.sh and make gateway l…
ericksoa Apr 25, 2026
2a26f26
fix(channels): add diagnostics to Slack guard installation path
ericksoa Apr 25, 2026
7cbf0a9
fix(channels): read Docker container logs for guard diagnostics
ericksoa Apr 25, 2026
79455ed
fix(channels): dump /tmp contents in guard diagnostic
ericksoa Apr 25, 2026
c97fda7
fix(channels): quote glob in /tmp diagnostic to expand inside sandbox
ericksoa Apr 25, 2026
8d03c40
fix(channels): add entrypoint execution trace to /tmp
ericksoa Apr 25, 2026
2d1c431
fix(channels): skip read-only .bashrc in install_configure_guard
ericksoa Apr 25, 2026
a9bea1a
fix(channels): make lock_rc_files tolerant of Landlock read-only home
ericksoa Apr 25, 2026
5b1b711
fix(channels): update validate_tmp_permissions for 644 gateway.log
ericksoa Apr 25, 2026
5823e23
fix(channels): add finer trace between non-root path entry and guard
ericksoa Apr 25, 2026
0924018
fix(channels): wrap all rc-file writes in || true for Landlock
ericksoa Apr 25, 2026
3591b31
fix(channels): fix non-root gateway.log perms and add crash diagnostic
ericksoa Apr 25, 2026
4694310
fix(sandbox): add ciao/networkInterfaces guard for restricted namespaces
ericksoa Apr 25, 2026
111b760
fix(sandbox): disable OpenClaw channel health monitor in sandbox
ericksoa Apr 25, 2026
d5d2478
fix(sandbox): use per-channel healthMonitor disable instead of global
ericksoa Apr 25, 2026
714c03d
fix(sandbox): add global catch-all safety net for gateway crashes
ericksoa Apr 25, 2026
a3c2a78
fix(sandbox): intercept process.exit during swallowed rejections
ericksoa Apr 25, 2026
4866add
ci: remove cloud-experimental-e2e from nightly workflow
ericksoa Apr 25, 2026
f659eb3
chore: remove diagnostic traces and clean up PR
ericksoa Apr 25, 2026
ae4d5d9
fix(test): apply shfmt formatting and fix nemotron validate_tmp test
ericksoa Apr 25, 2026
ca7f824
Merge branch 'main' into fix/slack-guard-loading
ericksoa Apr 25, 2026
5699ac7
fix(test): fix http-proxy-fix validate_tmp_permissions regex and add …
ericksoa Apr 25, 2026
8cd6259
chore: apply shfmt formatting from pre-commit hooks
ericksoa Apr 25, 2026
d27f0dd
fix(sandbox): address CodeRabbit review feedback
ericksoa Apr 25, 2026
36f3f89
fix(sandbox): accept both 600 and 644 for gateway.log in validator
ericksoa Apr 25, 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
106 changes: 1 addition & 105 deletions .github/workflows/nightly-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
# Nightly E2E tests:
#
# cloud-e2e Cloud inference (NVIDIA Endpoint API) on ubuntu-latest.
# cloud-experimental-e2e Experimental cloud inference test (main script skips embedded
# check-docs + final cleanup; follow-up steps run check-docs,
# skip/05-network-policy.sh, then cleanup.sh --verify with if: always()).
# messaging-providers-e2e Validates messaging credential provider/placeholder/L7-proxy chain
# for Telegram + Discord. Uses fake tokens. See PR #1081.
# token-rotation-e2e Validates that rotating a messaging token and re-running onboard
Expand All @@ -26,7 +23,7 @@
# Runs directly on the runner (not inside Docker) because OpenShell bootstraps
# a K3s cluster inside a privileged Docker container — nesting would break networking.
#
# NVIDIA_API_KEY for cloud-e2e and cloud-experimental-e2e:
# NVIDIA_API_KEY for cloud-e2e:
# - Repository secret: Settings → Secrets and variables → Actions → Repository secrets.
# - Environment secret: only available if the job sets `environment: <that environment name>`.
# (Storing the key under Environments / NVIDIA_API_KEY without `environment:` here leaves the
Expand Down Expand Up @@ -74,106 +71,6 @@ jobs:
path: /tmp/nemoclaw-e2e-install.log
if-no-files-found: ignore

cloud-experimental-e2e:
# Runs the full cloud-experimental E2E suite with docs parity isolated below.
# Landlock fix: OpenShell#810 (v0.0.32+). Docs parity: #2388 (typed registry).
if: github.repository == 'NVIDIA/NemoClaw'
runs-on: ubuntu-latest
# Main suite + check-docs + network-policy skip script can exceed 45m on cold runners.
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v6

# Split Phase 5f (check-docs) and Phase 6 (cleanup) out of the main script so CI shows
# failures in dedicated steps; tear-down always runs last (if: always()).
- name: Run cloud-experimental E2E test
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
GITHUB_TOKEN: ${{ github.token }}
# Non-interactive install (expect-driven Phase 3 optional). Runner has no expect; Phase 5e TUI skips if expect is absent.
RUN_E2E_CLOUD_EXPERIMENTAL_INTERACTIVE_INSTALL: "0"
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_RECREATE_SANDBOX: "1"
NEMOCLAW_POLICY_MODE: "custom"
NEMOCLAW_POLICY_PRESETS: "npm,pypi"
RUN_E2E_CLOUD_EXPERIMENTAL_SKIP_CHECK_DOCS: "1"
RUN_E2E_CLOUD_EXPERIMENTAL_SKIP_FINAL_CLEANUP: "1"
run: bash test/e2e/test-e2e-cloud-experimental.sh

- name: Documentation checks (check-docs.sh)
if: always()
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [ -f "$HOME/.bashrc" ]; then
# shellcheck source=/dev/null
source "$HOME/.bashrc" 2>/dev/null || true
fi
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
fi
if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
bash test/e2e/e2e-cloud-experimental/check-docs.sh

- name: Network policy checks (skip/05-network-policy.sh)
if: always()
env:
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
GITHUB_TOKEN: ${{ github.token }}
SANDBOX_NAME: e2e-cloud-experimental
NEMOCLAW_SANDBOX_NAME: e2e-cloud-experimental
run: |
set -euo pipefail
if [ -f "$HOME/.bashrc" ]; then
# shellcheck source=/dev/null
source "$HOME/.bashrc" 2>/dev/null || true
fi
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
fi
if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
bash test/e2e/e2e-cloud-experimental/skip/05-network-policy.sh

- name: Tear down cloud-experimental sandbox (always)
if: always()
env:
SANDBOX_NAME: e2e-cloud-experimental
NEMOCLAW_SANDBOX_NAME: e2e-cloud-experimental
run: |
set -euo pipefail
if [ -f "$HOME/.bashrc" ]; then
# shellcheck source=/dev/null
source "$HOME/.bashrc" 2>/dev/null || true
fi
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
fi
if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
bash test/e2e/e2e-cloud-experimental/cleanup.sh --verify

- name: Upload install log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: install-log-cloud-experimental
path: /tmp/nemoclaw-e2e-cloud-experimental-install.log
if-no-files-found: ignore

# ── Messaging Providers E2E ──────────────────────────────────
# Validates the full provider/placeholder/L7-proxy chain for messaging
# credentials (Telegram, Discord). Uses fake tokens by default — the L7
Expand Down Expand Up @@ -669,7 +566,6 @@ jobs:
needs:
[
cloud-e2e,
cloud-experimental-e2e,
messaging-providers-e2e,
token-rotation-e2e,
sandbox-survival-e2e,
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ _allowed_ids = json.loads(base64.b64decode(os.environ.get('NEMOCLAW_MESSAGING_AL
_discord_guilds = json.loads(base64.b64decode(os.environ.get('NEMOCLAW_DISCORD_GUILDS_B64', 'e30=') or 'e30=').decode('utf-8')); \
_token_keys = {'discord': 'token', 'telegram': 'botToken', 'slack': 'botToken'}; \
_env_keys = {'discord': 'DISCORD_BOT_TOKEN', 'telegram': 'TELEGRAM_BOT_TOKEN', 'slack': 'SLACK_BOT_TOKEN'}; \
_ch_cfg = {ch: {'accounts': {'default': {_token_keys[ch]: f'openshell:resolve:env:{_env_keys[ch]}', 'enabled': True, **({'appToken': 'openshell:resolve:env:SLACK_APP_TOKEN'} if ch == 'slack' else {}), **({'proxy': proxy_url} if ch in ('telegram', 'discord') else {}), **({'groupPolicy': 'open'} if ch == 'telegram' else {}), **({'dmPolicy': 'allowlist', 'allowFrom': _allowed_ids[ch]} if ch in _allowed_ids and _allowed_ids[ch] else {})}}} for ch in msg_channels if ch in _token_keys}; \
_ch_cfg = {ch: {'accounts': {'default': {_token_keys[ch]: f'openshell:resolve:env:{_env_keys[ch]}', 'enabled': True, 'healthMonitor': {'enabled': False}, **({'appToken': 'openshell:resolve:env:SLACK_APP_TOKEN'} if ch == 'slack' else {}), **({'proxy': proxy_url} if ch in ('telegram', 'discord') else {}), **({'groupPolicy': 'open'} if ch == 'telegram' else {}), **({'dmPolicy': 'allowlist', 'allowFrom': _allowed_ids[ch]} if ch in _allowed_ids and _allowed_ids[ch] else {})}}} for ch in msg_channels if ch in _token_keys}; \
_ch_cfg['discord'].update({'groupPolicy': 'allowlist', 'guilds': _discord_guilds}) if 'discord' in _ch_cfg and _discord_guilds else None; \
parsed = urlparse(chat_ui_url); \
chat_origin = f'{parsed.scheme}://{parsed.netloc}' if parsed.scheme and parsed.netloc else 'http://127.0.0.1:18789'; \
Expand Down
27 changes: 20 additions & 7 deletions scripts/lib/sandbox-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ _SANDBOX_INIT_LOADED=1
#
# File Owner Mode Writer Reader Sourced?
# /tmp/nemoclaw-proxy-env.sh root 444 root sandbox YES (.bashrc/.profile)
# /tmp/gateway.log gateway 600 gateway gateway no
# /tmp/gateway.log gateway 644 gateway all no (world-readable for diagnostics)
# /tmp/auto-pair.log sandbox 600 sandbox sandbox no
# /tmp/.npm-cache/ sandbox 755 sandbox sandbox no (tool data)
# /tmp/.cache/ sandbox 755 sandbox sandbox no (tool data)
Expand Down Expand Up @@ -118,15 +118,26 @@ validate_tmp_permissions() {
fi
done

# Restricted log files — must be 600
# Restricted log files — gateway.log may be 600 (Hermes) or 644 (OpenClaw,
# world-readable for diagnostics). auto-pair.log is 600.
for f in /tmp/gateway.log /tmp/auto-pair.log; do
[ -f "$f" ] || continue
local perms
perms="$(stat -c '%a' "$f" 2>/dev/null || stat -f '%Lp' "$f" 2>/dev/null || echo "unknown")"
if [ "$perms" != "600" ]; then
echo "[SECURITY] $f has unexpected permissions: mode=$perms (expected 600)" >&2
failed=1
fi
case "$f" in
*/gateway.log)
if [ "$perms" != "600" ] && [ "$perms" != "644" ]; then
echo "[SECURITY] $f has unexpected permissions: mode=$perms (expected 600 or 644)" >&2
failed=1
fi
;;
*)
if [ "$perms" != "600" ]; then
echo "[SECURITY] $f has unexpected permissions: mode=$perms (expected 600)" >&2
failed=1
fi
;;
esac
done

return $failed
Expand Down Expand Up @@ -206,7 +217,9 @@ lock_rc_files() {

for rc_file in "${home_dir}/.bashrc" "${home_dir}/.profile"; do
if [ -f "$rc_file" ]; then
chmod 444 "$rc_file"
if ! chmod 444 "$rc_file" 2>/dev/null; then
echo "[SECURITY] Could not lock ${rc_file} to 444 — continuing (best-effort, Landlock may enforce)" >&2
fi
fi
done
}
Expand Down
Loading
Loading