From 5864d97517f4310e02346942cb4fe0b8a70d922d Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Sun, 22 Mar 2026 19:58:01 -0700 Subject: [PATCH 1/2] fix(security): limit auto-pair to one-shot with identity check and token cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-pair unconditionally approved ALL pending device pairing requests for 10 minutes on every sandbox start. On headless/cloud installs this is an open window for unauthorized device pairing. Changes: - Clear all paired devices on sandbox start (revokes stale tokens from previous sessions — addresses token persistence concern) - Reduce initial timeout from 600s to 180s - Only approve devices with clientId 'openclaw-control-ui' (the chat UI); reject all other pending requests immediately - After first approval, reset deadline to now+5s (one-shot exit) - Clear remaining pending requests on exit - has_browser convergence logic unchanged --- scripts/nemoclaw-start.sh | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/nemoclaw-start.sh b/scripts/nemoclaw-start.sh index f98d4c6f..b01ae362 100755 --- a/scripts/nemoclaw-start.sh +++ b/scripts/nemoclaw-start.sh @@ -72,7 +72,7 @@ import json import subprocess import time -DEADLINE = time.time() + 600 +DEADLINE = time.time() + 180 QUIET_POLLS = 0 APPROVED = 0 @@ -80,6 +80,9 @@ def run(*args): proc = subprocess.run(args, capture_output=True, text=True) return proc.returncode, proc.stdout.strip(), proc.stderr.strip() +# Revoke all device tokens from previous sessions so they don't persist across restarts +run('openclaw', 'devices', 'clear', '--yes') + while time.time() < DEADLINE: rc, out, err = run('openclaw', 'devices', 'list', '--json') if rc != 0 or not out: @@ -98,15 +101,25 @@ while time.time() < DEADLINE: if pending: QUIET_POLLS = 0 for device in pending: - request_id = (device or {}).get('requestId') + if not isinstance(device, dict): + continue + request_id = device.get('requestId') if not request_id: continue + # Only approve the chat UI — reject unknown clients. + client_id = device.get('clientId', '') + if client_id != 'openclaw-control-ui': + print(f'[auto-pair] skipping non-UI device request={request_id} clientId={client_id!r}') + run('openclaw', 'devices', 'reject', request_id, '--json') + continue arc, aout, aerr = run('openclaw', 'devices', 'approve', request_id, '--json') if arc == 0: APPROVED += 1 - print(f'[auto-pair] approved request={request_id}') + print(f'[auto-pair] approved request={request_id} clientId={client_id}') elif aout or aerr: print(f'[auto-pair] approve failed request={request_id}: {(aerr or aout)[:400]}') + if APPROVED >= 1: + DEADLINE = time.time() + 5 time.sleep(1) continue @@ -123,6 +136,9 @@ while time.time() < DEADLINE: time.sleep(1) else: print(f'[auto-pair] watcher timed out approvals={APPROVED}') + +# Clean up: reject any remaining pending requests so no stale requests linger +run('openclaw', 'devices', 'clear', '--pending', '--yes') PYAUTOPAIR echo "[gateway] auto-pair watcher launched (pid $!)" } From ccdd7c3dea834c0fea7beccbade5fb564ab414f8 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Sun, 22 Mar 2026 21:07:17 -0700 Subject: [PATCH 2/2] fix: prevent one-shot bypass via repeated deadline extension --- scripts/nemoclaw-start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nemoclaw-start.sh b/scripts/nemoclaw-start.sh index b01ae362..732af9c7 100755 --- a/scripts/nemoclaw-start.sh +++ b/scripts/nemoclaw-start.sh @@ -98,7 +98,7 @@ while time.time() < DEADLINE: paired = data.get('paired') or [] has_browser = any((d.get('clientId') == 'openclaw-control-ui') or (d.get('clientMode') == 'webchat') for d in paired if isinstance(d, dict)) - if pending: + if pending and APPROVED == 0: QUIET_POLLS = 0 for device in pending: if not isinstance(device, dict):