-
-
Notifications
You must be signed in to change notification settings - Fork 69.5k
#BUG gateway.controlUi.allowInsecureAuth does not prevent device signature validation - all dashboard connections fail with "device signature expired" #2248
Description
[Bug]: gateway.controlUi.allowInsecureAuth does not prevent device signature validation - all dashboard connections fail with "device signature expired"
Summary
The Control UI dashboard cannot connect to the gateway despite having gateway.controlUi.allowInsecureAuth: true configured. All connection attempts fail with error code 1008 "device signature expired" (reason: device-signature-stale). The gateway correctly validates device signatures but rejects them as stale, and does NOT fall back to password/token-only authentication as documented, even when allowInsecureAuth is enabled.
This issue affects ALL dashboard access methods (localhost, Tailscale HTTPS, etc.) while other authentication methods (Telegram bot, CLI) work perfectly.
Environment
- ClawdBot version: 2026.1.25 (commit 300cda5)
- OS: WSL2 Ubuntu on Windows (Linux 6.6.87.2-microsoft-standard-WSL2)
- Node.js: v22.22.0
- Installation method: From source (git install)
- Browser tested: Chrome 144 on Windows 11, Safari on iOS 18.5
- Service manager: systemd (user service)
Current Configuration
{
"gateway": {
"port": 18789,
"mode": "local",
"bind": "loopback",
"controlUi": {
"enabled": true,
"allowInsecureAuth": true
},
"auth": {
"mode": "password",
"password": "[REDACTED_64_CHAR_HEX_STRING]",
"allowTailscale": true
},
"trustedProxies": [
"100.64.0.0/10"
],
"tailscale": {
"mode": "serve",
"resetOnExit": true
}
}
}Note: Also tested with "mode": "token" - same result.
Steps to Reproduce
- Install ClawdBot from source (latest commit 300cda5)
- Configure
gateway.controlUi.allowInsecureAuth: trueandgateway.auth.mode: "password"(or "token") - Start gateway:
systemctl --user start clawdbot-gateway.service - Verify gateway is running:
pnpm clawdbot status(shows gateway reachable, Telegram working) - Access dashboard at
http://127.0.0.1:18789/ - Enter password in "Password (not stored)" field
- Click "Connect"
Alternative attempts that also failed:
- Accessing via Tailscale HTTPS:
https://[TAILSCALE_HOSTNAME].ts.net/?password=... - Clearing all browser localStorage and cache
- Using incognito/private browsing mode
- Passing password/token via URL parameters
- Testing from multiple devices (Windows desktop, iPhone)
Expected Behavior
According to the v2026.1.21 release notes:
"Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set
gateway.controlUi.allowInsecureAuth: trueto allow token-only auth."
And the Security docs:
"If you enable gateway.controlUi.allowInsecureAuth, the UI falls back to token-only auth on plain HTTP and skips device pairing."
With allowInsecureAuth: true, the Control UI should:
- Accept password/token authentication without requiring device pairing
- Skip device signature validation or treat stale signatures as non-fatal
- Fall back to password/token-only authentication
Actual Behavior
The Control UI consistently returns:
disconnected (1008): device signature expired
Gateway logs show repeated rejections:
{
"cause": "device-auth-invalid",
"handshake": "failed",
"reason": "device-signature-stale",
"client": "clawdbot-control-ui",
"deviceId": "587feb26193b24d5343899c81210b0a1f4da0aad9c4e77f898fbb2c3f8a72e4b"
}The connection is closed with:
code=1008 reason=device signature expired
Analysis
What Works
- ✅ Gateway service running (pid verified, responds to RPC probes)
- ✅ Telegram bot authentication and messaging (works perfectly)
- ✅ CLI commands (
pnpm clawdbot status, etc.) - ✅ Gateway reachable (19-25ms response time)
What Fails
- ❌ Dashboard connection via
http://127.0.0.1:18789/ - ❌ Dashboard connection via
https://[TAILSCALE_HOSTNAME].ts.net/ - ❌ All browser device identity authentication attempts
Root Cause
After examining the source code in src/gateway/server/ws-connection/message-handler.ts, the gateway checks device signature timestamps:
if (
typeof signedAt !== "number" ||
Math.abs(Date.now() - signedAt) > DEVICE_SIGNATURE_SKEW_MS
) {
setHandshakeState("failed");
setCloseCause("device-auth-invalid", {
reason: "device-signature-stale",
client: connectParams.client.id,
deviceId: device.id,
});
// ... reject connection
}The UI code in ui/src/ui/gateway.ts shows:
// crypto.subtle is only available in secure contexts (HTTPS, localhost).
// Over plain HTTP, we skip device identity and fall back to token-only auth.
// Gateways may reject this unless gateway.controlUi.allowInsecureAuth is enabled.
const isSecureContext = typeof crypto !== "undefined" && !!crypto.subtle;The problem:
- On localhost (
http://127.0.0.1),isSecureContext = true, so the UI generates device identity - On Tailscale HTTPS,
isSecureContext = true, so the UI generates device identity - The generated device signatures are immediately rejected as "stale"
- The gateway does NOT fall back to password/token authentication despite
allowInsecureAuth: true
The allowInsecureAuth setting appears to only control whether the gateway accepts token-only auth from non-secure contexts, but it does NOT skip device signature validation when device identity IS present. This creates a deadlock where:
- Secure contexts (localhost, HTTPS) → UI generates device identity → Gateway validates it → Fails as stale → No fallback
- Non-secure contexts → Would skip device identity, but are rejected for being non-secure
Complete Gateway Logs
Localhost connection attempt:
15:16:36 warn gateway/ws {"subsystem":"gateway/ws"} {"cause":"device-auth-invalid","handshake":"failed","durationMs":7,"lastFrameType":"req","lastFrameMethod":"connect","lastFrameId":"67211823-6e63-4cfb-931b-43296c22b068","host":"127.0.0.1:18789","origin":"http://127.0.0.1:18789","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36","reason":"device-signature-stale","client":"clawdbot-control-ui","deviceId":"587feb26193b24d5343899c81210b0a1f4da0aad9c4e77f898fbb2c3f8a72e4b"} closed before connect conn=58d164cf-52a3-466a-a409-d458a08ab2f7 remote=127.0.0.1 fwd=n/a origin=http://127.0.0.1:18789 host=127.0.0.1:18789 ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 code=1008 reason=device signature expired
Tailscale HTTPS connection attempt:
15:28:32 warn gateway/ws {"subsystem":"gateway/ws"} Proxy headers detected from untrusted address. Connection will not be treated as local. Configure gateway.trustedProxies to restore local client detection behind your proxy.
15:28:32 warn gateway/ws {"subsystem":"gateway/ws"} {"cause":"device-auth-invalid","handshake":"failed","durationMs":22,"lastFrameType":"req","lastFrameMethod":"connect","lastFrameId":"a0189e24-1c24-4eae-9781-61b6b207af9c","host":"[REDACTED].ts.net","origin":"https://[REDACTED].ts.net","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36","forwardedFor":"100.67.7.98","reason":"device-signature-stale","client":"clawdbot-control-ui","deviceId":"b397e1d2ff0a27d898dec7c7ea9e9e3fba76f6cae2cd3f1247306d3cc9cc1a4c"} closed before connect conn=e43e669b-0c38-4d3d-9332-edaadb915ac4 remote=127.0.0.1 fwd=100.67.7.98 origin=https://[REDACTED].ts.net host=[REDACTED].ts.net ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 code=1008 reason=device signature expired
Note: Different deviceId values show the UI is generating new device identities for each domain, and all are rejected as stale.
Gateway Status Output
🦞 Clawdbot 2026.1.25 (300cda5d7)
Gateway │ local · ws://127.0.0.1:18789 (local loopback) · reachable 19ms
│ · auth token+password · running (pid 14102, state active)
Security audit
Summary: 0 critical · 2 warn · 2 info
WARN Control UI allows insecure HTTP auth
gateway.controlUi.allowInsecureAuth=true allows token-only auth over HTTP and skips device identity.
Fix: Disable it or switch to HTTPS (Tailscale Serve) or localhost.
Channels
│ Telegram │ ON │ OK │ token env · accounts 1/1
Troubleshooting Attempts
1. Cleared all browser storage
localStorage.clear();
indexedDB.databases().then(dbs => {
dbs.forEach(db => indexedDB.deleteDatabase(db.name));
});Result: Still fails with same error, new deviceId generated
2. Switched from token to password mode
Changed gateway.auth.mode from "token" to "password"
Result: No change, same error
3. Accessed via different domains
http://127.0.0.1:18789/- Failshttps://[TAILSCALE_HOSTNAME].ts.net/- Failshttp://127.0.0.1:18789/?password=...- Fails
Result: All fail with identical error
4. Multiple browsers and devices
- Chrome 144 (Windows)
- Safari (iOS 18.5)
- Incognito/Private mode
Result: All fail identically
Related Issues
This appears to be related to but distinct from #1679, which was supposedly fixed in v2026.1.24. The fix in #1679 aimed to:
"allow Control UI token-only auth to skip device pairing even when device identity is present (gateway.controlUi.allowInsecureAuth)"
However, the current behavior suggests this fix is not working as intended, at least in certain configurations.
Proposed Solution
The gateway's device signature validation logic should check gateway.controlUi.allowInsecureAuth and:
-
If
allowInsecureAuth: trueAND device signature is stale/invalid:- Log a warning but continue authentication
- Fall back to password/token validation
- Allow connection if password/token is valid
-
OR the UI should skip device identity generation entirely when:
allowInsecureAuth: trueis detected from the gateway- Password/token auth is provided
Workaround
Currently, the only workaround is to not use the dashboard and rely on:
- CLI commands (
pnpm clawdbot status,pnpm clawdbot logs, etc.) - Telegram bot for messaging
- Direct API/RPC calls
The dashboard is completely inaccessible regardless of configuration.
Additional Context
- This is a fresh installation following the official documentation
- All other ClawdBot features work correctly (Telegram, CLI, agent execution)
- The gateway is healthy and responding to all non-dashboard requests
- Security audit shows 0 critical issues
- The issue persists across gateway restarts and config changes
Impact
- Severity: High - Dashboard is completely unusable
- Scope: Affects all users trying to use the Control UI with secure contexts (localhost, HTTPS)
- Workaround: Available (use CLI/Telegram instead)
- Regression: Possibly - fix from [Bug]: gateway.controlUi.allowInsecureAuth: true does not bypass device pairing for Docker/reverse proxy deployments` #1679 may have introduced this or failed to fully address it