Skip to content

security: harden gateway auth defaults and restrict auto-pair#123

Open
kakuteki wants to merge 1 commit intoNVIDIA:mainfrom
kakuteki:fix/insecure-auth-configuration
Open

security: harden gateway auth defaults and restrict auto-pair#123
kakuteki wants to merge 1 commit intoNVIDIA:mainfrom
kakuteki:fix/insecure-auth-configuration

Conversation

@kakuteki
Copy link

@kakuteki kakuteki commented Mar 17, 2026

Summary

Fixes #117 — Insecure authentication configuration in gateway setup.

The gateway's controlUi config unconditionally set dangerouslyDisableDeviceAuth: True and allowInsecureAuth: True. This was added as a temporary workaround (commit 5fb629f, message: "Disable deviceauth temporarily") but was never reverted. While the current main branch is protected by sandbox network isolation and gateway.mode = 'local', these settings become dangerous if combined with a LAN-bind change (e.g. PR #114) or a cloudflared tunnel in remote deployments — resulting in an unauthenticated, publicly reachable dashboard.

Changes

1. dangerouslyDisableDeviceAuth — default to false

Device-pairing auth is now enabled by default. For development or headless environments that genuinely need it disabled, set NEMOCLAW_DISABLE_DEVICE_AUTH=1 explicitly.

# Before
'dangerouslyDisableDeviceAuth': True,

# After
disable_device_auth = os.environ.get('NEMOCLAW_DISABLE_DEVICE_AUTH', '') == '1'
'dangerouslyDisableDeviceAuth': disable_device_auth,

2. allowInsecureAuth — derive from URL scheme

Instead of unconditionally allowing insecure auth, it is now enabled only when CHAT_UI_URL uses http:// (local development). When https:// is configured (production/remote), insecure auth is automatically disabled.

# Before
'allowInsecureAuth': True,

# After
allow_insecure = parsed.scheme != 'https'
'allowInsecureAuth': allow_insecure,

3. start_auto_pair — restrict to known client types

The auto-pair watcher previously approved every pending device request unconditionally. It now only approves devices with recognized clientId (openclaw-control-ui) or clientMode (webchat). Unknown clients are logged and rejected.

# Before
for device in pending:
    request_id = (device or {}).get('requestId')
    ...
    run('openclaw', 'devices', 'approve', request_id, '--json')

# After
ALLOWED_CLIENTS = {'openclaw-control-ui'}
ALLOWED_MODES = {'webchat'}
for device in pending:
    if client_id not in ALLOWED_CLIENTS and client_mode not in ALLOWED_MODES:
        print(f'[auto-pair] rejected unknown client={client_id} ...')
        continue
    ...

Test plan

  • Default onboarding: gateway starts with device auth enabled, Control UI pairing works via auto-pair
  • NEMOCLAW_DISABLE_DEVICE_AUTH=1: device auth is disabled (backward-compatible escape hatch)
  • CHAT_UI_URL=https://...: allowInsecureAuth is false
  • CHAT_UI_URL=http://127.0.0.1:18789 (default): allowInsecureAuth is true
  • Unknown device type connecting to gateway: auto-pair rejects and logs
  • openclaw-control-ui / webchat devices: auto-pair approves as before

Summary by CodeRabbit

  • New Features

    • Added environment variable configuration options to disable device authentication and whitelist approved clients and modes for enhanced security control.
  • Improvements

    • Enhanced device pairing validation to filter requests based on client and mode whitelist settings; improved logging for approved devices.

…VIDIA#117)

- dangerouslyDisableDeviceAuth now defaults to false; only enabled when
  NEMOCLAW_DISABLE_DEVICE_AUTH=1 is explicitly set (removes the
  "temporary" bypass from commit 5fb629f that was never reverted)
- allowInsecureAuth is now derived from the CHAT_UI_URL scheme: true
  for http (local dev), false for https (production/remote)
- auto-pair watcher now only approves known client types
  (openclaw-control-ui, webchat) instead of unconditionally approving
  every pending device request
@kakuteki kakuteki force-pushed the fix/insecure-auth-configuration branch from 0516af4 to 6f603c7 Compare March 18, 2026 18:55
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

The script now makes insecure authentication settings configurable via environment variables instead of hardcoded values, and introduces client/mode whitelisting for auto-pairing device approval to restrict which clients can self-register.

Changes

Cohort / File(s) Summary
Authentication & Device Authorization Config
scripts/nemoclaw-start.sh
Replaced hardcoded allowInsecureAuth and dangerouslyDisableDeviceAuth with dynamic configuration derived from NEMOCLAW_DISABLE_DEVICE_AUTH environment variable and URL scheme detection. Added client/mode whitelisting via ALLOWED_CLIENTS and ALLOWED_MODES environment variables with validation logic to reject non-whitelisted pending devices during auto-pair approval, including enriched logging with clientId on approval.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Security's now tuneable, not cast in stone,
With whitelists and env vars, we've grown!
No more fixed dangerously-True,
Auto-pair approves only the crew,
Safer gateways now, all our own! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'security: harden gateway auth defaults and restrict auto-pair' directly and clearly describes the main security-focused changes: hardening auth defaults and restricting auto-pairing behavior.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding requirements from issue #117: disabling insecure defaults by making dangerouslyDisableDeviceAuth configurable (defaulting to false), deriving allowInsecureAuth from URL scheme, and implementing client whitelisting in auto-pair logic.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the security hardening objectives: auth configuration modifications and auto-pair whitelisting logic align with issue #117 requirements without unrelated additions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/nemoclaw-start.sh`:
- Around line 145-151: The skipped entries are left in pending and reprocessed;
update the handling in the device validation branch (the checks using device,
clientId/clientMode, ALLOWED_CLIENTS and ALLOWED_MODES) to explicitly mark or
reject the request so it won't stay pending: call the existing rejection routine
(e.g. reject_request(requestId) or send a failure response) when device is not a
dict or when client_id/client_mode are not allowed, or alternatively record the
requestId into a handled_requests set and ensure the watcher filters
handled_requests out before the if pending: path; reference the variables
device, clientId/clientMode, client_id/client_mode, ALLOWED_CLIENTS,
ALLOWED_MODES, requestId and pending to locate where to insert the
rejection/marking logic.
- Around line 46-50: The current logic sets allow_insecure = parsed.scheme !=
'https', which enables insecure auth for malformed or unsupported schemes;
change this to only allow insecure when the parsed.scheme is explicitly 'http'
(e.g., allow_insecure = parsed.scheme == 'http'), and treat any other value
(empty or unknown scheme) as secure/closed (allow_insecure = False) or abort
early if you prefer stricter behavior; update the code that builds
gateway['controlUi'] (the allowInsecure and dangerouslyDisableDeviceAuth fields)
so only an explicit 'http' origin enables allowInsecure, referencing
disable_device_auth, parsed.scheme, gateway['controlUi'], allowInsecure, and
dangerouslyDisableDeviceAuth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5cc4f01e-c4ce-4b1b-b808-15ed22d53c8d

📥 Commits

Reviewing files that changed from the base of the PR and between 241ffb2 and 6f603c7.

📒 Files selected for processing (1)
  • scripts/nemoclaw-start.sh

Comment on lines +46 to +50
disable_device_auth = os.environ.get('NEMOCLAW_DISABLE_DEVICE_AUTH', '') == '1'
allow_insecure = parsed.scheme != 'https'
gateway['controlUi'] = {
'allowInsecureAuth': True,
'dangerouslyDisableDeviceAuth': True,
'allowInsecureAuth': allow_insecure,
'dangerouslyDisableDeviceAuth': disable_device_auth,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail closed for invalid CHAT_UI_URL values.

allow_insecure = parsed.scheme != 'https' enables insecure auth for any non-HTTPS input, including malformed values like example.com or unsupported schemes. That weakens the new default unless the caller gets the URL exactly right. Only an explicit http://... origin should enable this path; everything else should disable insecure auth or abort early.

🔐 Suggested fix
 chat_ui_url = os.environ.get('CHAT_UI_URL', 'http://127.0.0.1:18789')
 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'
 local_origin = f'http://127.0.0.1:{os.environ.get("PUBLIC_PORT", "18789")}'
+if parsed.scheme in {'http', 'https'} and parsed.netloc:
+    chat_origin = f"{parsed.scheme}://{parsed.netloc}"
+    allow_insecure = parsed.scheme == 'http'
+else:
+    chat_origin = local_origin
+    allow_insecure = False
 origins = [local_origin]
 if chat_origin not in origins:
     origins.append(chat_origin)

 gateway = cfg.setdefault('gateway', {})
 gateway['mode'] = 'local'
 disable_device_auth = os.environ.get('NEMOCLAW_DISABLE_DEVICE_AUTH', '') == '1'
-allow_insecure = parsed.scheme != 'https'
 gateway['controlUi'] = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/nemoclaw-start.sh` around lines 46 - 50, The current logic sets
allow_insecure = parsed.scheme != 'https', which enables insecure auth for
malformed or unsupported schemes; change this to only allow insecure when the
parsed.scheme is explicitly 'http' (e.g., allow_insecure = parsed.scheme ==
'http'), and treat any other value (empty or unknown scheme) as secure/closed
(allow_insecure = False) or abort early if you prefer stricter behavior; update
the code that builds gateway['controlUi'] (the allowInsecure and
dangerouslyDisableDeviceAuth fields) so only an explicit 'http' origin enables
allowInsecure, referencing disable_device_auth, parsed.scheme,
gateway['controlUi'], allowInsecure, and dangerouslyDisableDeviceAuth.

Comment on lines +145 to +151
if not isinstance(device, dict):
continue
client_id = device.get('clientId', '')
client_mode = device.get('clientMode', '')
if client_id not in ALLOWED_CLIENTS and client_mode not in ALLOWED_MODES:
print(f'[auto-pair] rejected unknown client={client_id} mode={client_mode}')
continue
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Skipped requests never leave pending.

These continue paths only ignore or log bad entries. Because those requests remain pending, the watcher reprocesses them every second and never reaches the convergence path while they exist. Please reject them explicitly, or at least record handled requestIds and filter them out before the if pending: branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/nemoclaw-start.sh` around lines 145 - 151, The skipped entries are
left in pending and reprocessed; update the handling in the device validation
branch (the checks using device, clientId/clientMode, ALLOWED_CLIENTS and
ALLOWED_MODES) to explicitly mark or reject the request so it won't stay
pending: call the existing rejection routine (e.g. reject_request(requestId) or
send a failure response) when device is not a dict or when client_id/client_mode
are not allowed, or alternatively record the requestId into a handled_requests
set and ensure the watcher filters handled_requests out before the if pending:
path; reference the variables device, clientId/clientMode,
client_id/client_mode, ALLOWED_CLIENTS, ALLOWED_MODES, requestId and pending to
locate where to insert the rejection/marking logic.

@kakuteki
Copy link
Author

Hi maintainers, could you please approve the CI workflow run for this PR? I've rebased onto the latest main. Thank you!

@wscurran wscurran added bug Something isn't working security Something isn't secure labels Mar 19, 2026
@wscurran
Copy link
Contributor

Thanks for addressing the security concern and providing a fix for the gateway auth defaults, this is a significant improvement to the security posture of NemoClaw.

@wscurran wscurran added the priority: high Important issue that should be resolved in the next release label Mar 19, 2026
@wscurran wscurran requested a review from drobison00 March 23, 2026 16:42
Copy link
Contributor

@cv cv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The security intent here is correct — device auth should default to enabled, and insecure auth should derive from the URL scheme. But the implementation is superseded by other in-flight work.

Stale against main

Based on the old inline Python config writer in nemoclaw-start.sh. Main has since locked openclaw.json to root-owned mode 444 — the inline config write will fail at runtime.

Auto-pair filtering superseded by #690

The clientId/clientMode allowlist is nearly identical to what #690 does. #690 also adds timeout reduction (600s→180s), one-shot exit after first approval, and token cleanup on start/exit. Note: we flagged in our #690 review that clientId is client-supplied and spoofable (the gateway stores connectParams.client.id verbatim — no server-side validation). The same issue applies to this PR's ALLOWED_CLIENTS check.

Contradicts #114

PR #114 unconditionally sets dangerouslyDisableDeviceAuth: true for remote access. This PR defaults it to false. These take opposite positions — they need to be reconciled before either merges.

Conflicts with #721

The gateway isolation PR restructures the entire entrypoint with gosu privilege separation, config integrity hashing, and PATH hardening. Both the inline config block and the auto-pair watcher are significantly changed in #721.

Recommendation

The auth hardening (points 1 and 2) is worth preserving but needs to be re-implemented against the current entrypoint structure. The auto-pair changes (point 3) should be dropped in favor of #690 which is more comprehensive. Consider rebasing and scoping this PR to just the auth defaults after #690 and #721 land.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority: high Important issue that should be resolved in the next release security Something isn't secure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Authentication Configuration

4 participants