Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions packages/openclaw/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,20 @@ Confirm what appears auto-injected in your UI stream:
- Prefer explicit env exports in hosted/sandbox deployments.
- If available in your deployment, use a lockfile/PID strategy for relay gateway singleton enforcement.

### WS auth version-compat matrix

The relay gateway automatically selects the right device auth payload version based on the detected environment. If the selected version is rejected, it falls back to the alternate version once before giving up.

| Environment | Auth Profile | Primary Payload | Fallback | Notes |
|---|---|---|---|---|
| `~/.openclaw/` (standard) | `default` | v3 (with platform/deviceFamily) | v2 | Current OpenClaw server supports v3 natively |
| `~/.clawdbot/` (marketplace image) | `clawdbot-v1` | v2 (no platform/deviceFamily) | v3 | Older gateway only supports v2; v3↔v2 fallback handles upgrades |
| `OPENCLAW_WS_AUTH_COMPAT=clawdbot` | `clawdbot-v1` | v2 | v3 | Manual override for non-standard installations |

**When upgrading a Clawdbot marketplace image** to a newer OpenClaw server that supports v3, the fallback mechanism handles the transition automatically — v2 is tried first, and if the new server rejects it (unlikely, since servers accept both), v3 is tried as fallback.

**Debug logging**: Set `OPENCLAW_WS_DEBUG=1` to see the full canonicalization matrix, field hashes, and self-verification output during auth.

---

## 11b) Advanced Troubleshooting: Execution Policy Lockdown
Expand Down
72 changes: 72 additions & 0 deletions packages/openclaw/src/__tests__/ws-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,78 @@ describe('OpenClawGatewayClient', () => {
await client.disconnect();
});

it('should fallback to alternate payload version on signature rejection', async () => {
let connectAttempts = 0;
server = new MockOpenClawServer({ sendChallenge: false });
const origWss = (server as unknown as { wss: WebSocketServer }).wss;
origWss.removeAllListeners('connection');
origWss.on('connection', (ws) => {
connectAttempts++;
// Send challenge
ws.send(JSON.stringify({
type: 'event',
event: 'connect.challenge',
payload: { nonce: `nonce-fallback-${connectAttempts}`, ts: Date.now() },
}));
ws.on('message', (data) => {
const msg = JSON.parse(data.toString()) as Record<string, unknown>;
if (msg.method === 'connect') {
if (connectAttempts === 1) {
// First attempt: reject with signature invalid
ws.send(JSON.stringify({
type: 'res',
id: 'connect-1',
ok: false,
error: { code: 'auth_failed', message: 'device signature invalid' },
}));
} else {
// Second attempt (fallback): accept
ws.send(JSON.stringify({ type: 'res', id: 'connect-1', ok: true }));
}
}
});
});

const client = new OpenClawGatewayClient('test-token', server.port);
await client.connect();
// Should have connected on the fallback attempt
expect(connectAttempts).toBe(2);
await client.disconnect();
});

it('should not retry fallback more than once', async () => {
let connectAttempts = 0;
server = new MockOpenClawServer({ sendChallenge: false });
const origWss = (server as unknown as { wss: WebSocketServer }).wss;
origWss.removeAllListeners('connection');
origWss.on('connection', (ws) => {
connectAttempts++;
ws.send(JSON.stringify({
type: 'event',
event: 'connect.challenge',
payload: { nonce: `nonce-nofallback-${connectAttempts}`, ts: Date.now() },
}));
ws.on('message', (data) => {
const msg = JSON.parse(data.toString()) as Record<string, unknown>;
if (msg.method === 'connect') {
// Always reject with signature invalid
ws.send(JSON.stringify({
type: 'res',
id: 'connect-1',
ok: false,
error: { code: 'auth_failed', message: 'device signature invalid' },
}));
}
});
});

const client = new OpenClawGatewayClient('test-token', server.port);
await expect(client.connect()).rejects.toThrow(/auth failed|signature invalid|closed before/i);
// Should have tried exactly 2 times: primary + one fallback
expect(connectAttempts).toBe(2);
await client.disconnect();
});

it('should silently ignore unrecognized event messages', async () => {
server = new MockOpenClawServer();
const origWss = (server as unknown as { wss: WebSocketServer }).wss;
Expand Down
Loading
Loading