Skip to content

key improvements#493

Merged
khaliqgant merged 8 commits intomainfrom
pr-followup
Mar 5, 2026
Merged

key improvements#493
khaliqgant merged 8 commits intomainfrom
pr-followup

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Mar 5, 2026

Summary

Test Plan

  • Tests added/updated
  • Manual testing completed

Screenshots


Open with Devin

@khaliqgant khaliqgant requested a review from willwashburn as a code owner March 5, 2026 08:18
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

devin-ai-integration[bot]

This comment was marked as resolved.

khaliqgant and others added 4 commits March 5, 2026 00:35
- resolveAuthProfile(): match both '.clawdbot' and 'clawdbot' suffixes
  to handle OPENCLAW_HOME=/opt/clawdbot (no dot prefix)
- setNestedValue(): reject __proto__/prototype/constructor keys (prototype
  pollution guard)
- Add canonicalization matrix with 6 payload variants for WS auth debugging
- Add per-field hash logging and raw challenge capture behind OPENCLAW_WS_DEBUG=1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After reading the actual server-side verifier (openclaw/openclaw
src/gateway/device-auth.ts, src/infra/device-identity.ts), confirmed:
- Server accepts both PEM and raw-base64url public keys
- Server decodes signatures in both base64url and standard base64
- Payload format (v3|deviceId|...) matches our v3-default-ms exactly

Changes:
- clawdbot-v1 profile now uses raw-base64url + base64url (matches
  server's own signDevicePayload output) instead of PEM + base64
- Add self-verification diagnostic: verify signature locally before
  sending, check deviceId derivation, verify encode/decode round-trip
- Helps identify if the issue is key mismatch vs payload mismatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Clawdbot marketplace image may run an older gateway that only
supports v2 device auth payloads (no platform/deviceFamily fields).
The current server tries v3→v2 fallback, but older versions only
have v2.

Changes:
- clawdbot-v1 profile now uses v2 payload as primary
- Added v2 variants to canonicalization matrix (v2-default-ms,
  v2-default-sec, v2-no-token-ms)
- Default profile still uses v3 (unchanged for standard OpenClaw)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add safe v3↔v2 auth payload fallback: on signature rejection, retry
  once with alternate payload version before giving up
- Clean up diagnostic logging: consolidate to single production line,
  gate verbose output behind OPENCLAW_WS_DEBUG=1
- Add auth reject/fallback counters for observability
- Add fallback conformance tests (signature reject → retry → success,
  and double-reject → fail after one fallback)
- Add WS auth version-compat matrix to SKILL.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Move clearConnectTimeout() from the unconditional connect-response
path into each terminal branch (success, pairing rejection, final
rejection). The fallback branch intentionally keeps the original
30s timeout alive so a hanging fallback connection still triggers
the timeout instead of leaving the connect promise dangling forever.

Fixes Devin review finding on PR #493.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Guard close/error handlers with `this.ws !== ws` to ignore events
from superseded WebSocket instances. This fixes two Devin review
findings:

1. Error handler could reject connect promise during fallback if
   the old WS emitted an error during close handshake.

2. Old WS close handler could stomp `authenticated = false` on the
   new connection if it fired after the fallback succeeded, plus
   trigger a spurious scheduleReconnect().

The `this.ws !== ws` pattern replaces the `fallbackInProgress` flag
with a simpler, more robust guard. Also adds early `stopped` check
in sendChatMessage to prevent reconnect after explicit disconnect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@@ -423,7 +566,10 @@ export class OpenClawGatewayClient {

Check warning

Code scanning / CodeQL

Log injection Medium

Log entry depends on a
user-provided value
.

Copilot Autofix

AI about 2 months ago

In general, to fix log injection you should sanitize any untrusted input before interpolating it into log messages. For plain-text logs, a common minimal approach is to strip carriage return and newline characters so an attacker cannot break out onto new log lines, and optionally remove other control characters. It is also useful to clearly delimit or quote user-provided values in logs.

For this specific issue, the best minimally invasive fix is to sanitize requestId right before it is used in the log statement. Since we only see the problematic log at line 688 and the value is only used for display to the user, we can create a sanitized version (e.g., safeRequestId) by converting requestId to a string and removing \r and \n. Then we use safeRequestId in the console.error message. This preserves existing functionality (the user still sees the same identifier for normal values) while preventing forged log lines from maliciously crafted requestId values. No new imports or external libraries are needed; we can rely on String() and .replace with a suitable regular expression.

Concretely:

  • In packages/openclaw/src/gateway.ts, inside the if (requestId) { ... } block near line 688, introduce a const safeRequestId = String(requestId).replace(/[\r\n]/g, '');.
  • Replace the existing console.error line that logs requestId with one that logs safeRequestId instead.
  • Optionally, this pattern can be reused elsewhere if you later decide to sanitize other logged values, but for this fix we constrain changes only to the provided snippet.
Suggested changeset 1
packages/openclaw/src/gateway.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/openclaw/src/gateway.ts b/packages/openclaw/src/gateway.ts
--- a/packages/openclaw/src/gateway.ts
+++ b/packages/openclaw/src/gateway.ts
@@ -685,12 +685,13 @@
           const requestId = errObj?.requestId ?? errObj?.request_id ?? '';
           console.error('[openclaw-ws] Pairing rejected — device is not paired with the OpenClaw gateway.');
           if (requestId) {
-            console.error(`[openclaw-ws] Approve this device:  openclaw devices approve ${requestId}`);
+            const safeRequestId = String(requestId).replace(/[\r\n]/g, '');
+            console.error(`[openclaw-ws] Approve this device:  openclaw devices approve ${safeRequestId}`);
           }
           console.error(`[openclaw-ws] Device ID: ${this.device.deviceId.slice(0, 16)}...`);
           const configHint = getWsAuthCompat() === 'clawdbot'
             ? '~/.clawdbot/clawdbot.json'
-            : '~/.openclaw/openclaw.json';
+            : '~/.openclaw/openclaw/openclaw.json';
           console.error(`[openclaw-ws] Ensure OPENCLAW_GATEWAY_TOKEN matches ${configHint} gateway.auth.token`);
           this.pairingRejected = true;
         } else if (isSignatureInvalid && !this.fallbackAttempted) {
EOF
@@ -685,12 +685,13 @@
const requestId = errObj?.requestId ?? errObj?.request_id ?? '';
console.error('[openclaw-ws] Pairing rejected — device is not paired with the OpenClaw gateway.');
if (requestId) {
console.error(`[openclaw-ws] Approve this device: openclaw devices approve ${requestId}`);
const safeRequestId = String(requestId).replace(/[\r\n]/g, '');
console.error(`[openclaw-ws] Approve this device: openclaw devices approve ${safeRequestId}`);
}
console.error(`[openclaw-ws] Device ID: ${this.device.deviceId.slice(0, 16)}...`);
const configHint = getWsAuthCompat() === 'clawdbot'
? '~/.clawdbot/clawdbot.json'
: '~/.openclaw/openclaw.json';
: '~/.openclaw/openclaw/openclaw.json';
console.error(`[openclaw-ws] Ensure OPENCLAW_GATEWAY_TOKEN matches ${configHint} gateway.auth.token`);
this.pairingRejected = true;
} else if (isSignatureInvalid && !this.fallbackAttempted) {
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 12 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Fallback state (payloadVersionOverride, fallbackAttempted) never resets during auto-reconnect, locking retries to one payload version

When both v3 and v2 payload versions fail authentication, the connect() promise is rejected but the WebSocket close handler still calls scheduleReconnect() (gateway.ts:564-565). scheduleReconnect invokes doConnect() directly (gateway.ts:812,826) which does NOT reset payloadVersionOverride or fallbackAttempted — those are only reset in connect() (gateway.ts:474-476). This means all subsequent auto-reconnect attempts are locked to the last-tried fallback payload version and never cycle back to the original. If the first version was rejected due to a transient server issue (and the isSignatureInvalid regex at line 680 is fairly broad — device.signature matches any error containing "device signature" even without "invalid"), the client would be permanently stuck retrying only the wrong payload version until an explicit connect() call is made.

(Refers to lines 809-813)

Prompt for agents
In packages/openclaw/src/gateway.ts, the scheduleReconnect() method (around lines 797-828) calls doConnect() directly, which does not reset the fallback state (payloadVersionOverride, fallbackAttempted). After both v3 and v2 fail, auto-reconnects are stuck on one version. Consider resetting these fields before calling doConnect() in scheduleReconnect(), similar to how connect() resets them at lines 474-476. For example, in the two setTimeout callbacks that call this.doConnect() (around lines 811-812 and 825-826), add:

  this.payloadVersionOverride = null;
  this.fallbackAttempted = false;

This way each auto-reconnect cycle gets a fresh chance to try the primary version first and fall back if needed.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +528 to 530
ws.on('message', (data) => {
this.handleMessage(data.toString());
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Missing this.ws !== ws guard on WebSocket message handler allows stale messages during v3↔v2 fallback

The close and error handlers in doConnect() were given if (this.ws !== ws) return; guards (lines 535, 571) to ignore events from superseded WebSocket instances during v3↔v2 fallback. However, the message handler at line 528 was not given the same guard. During the fallback flow, this.ws is set to null and the old WebSocket is closed (gateway.ts:713-714), but if a buffered message is delivered on the old socket before the close completes, handleMessage would execute against the current (potentially new) connection state. For example, a stale connect.challenge from the old socket could cause a duplicate connect request to be sent on the newly created socket.

Inconsistent guard pattern in doConnect()

The close handler at line 532-567 and error handler at line 569-581 both check if (this.ws !== ws) return; but the message handler at line 528-530 does not:

ws.on('message', (data) => {
  this.handleMessage(data.toString()); // no guard
});
Suggested change
ws.on('message', (data) => {
this.handleMessage(data.toString());
});
ws.on('message', (data) => {
// Guard: ignore messages from superseded WebSocket instances.
if (this.ws !== ws) return;
this.handleMessage(data.toString());
});
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@khaliqgant khaliqgant merged commit 8c81aeb into main Mar 5, 2026
44 of 45 checks passed
@khaliqgant khaliqgant deleted the pr-followup branch March 5, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants