fix(gateway): bound websocket auth after valid connect#73925
fix(gateway): bound websocket auth after valid connect#73925openclaw-clownfish[bot] wants to merge 1 commit into
Conversation
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🟠 Connect-auth watchdog can be reset by repeated `connect` frames (pre-auth DoS)
DescriptionThe websocket handshake path arms a new As a result, a client can send additional
This allows an unauthenticated peer to indefinitely postpone the auth-timeout by repeatedly sending Vulnerable code: const connectParams = frame.params as ConnectParams;
clearHandshakeTimer();
armConnectAuthTimer();
// ... await resolveConnectAuthState(...)Because the handler awaits auth before calling RecommendationPrevent handshake state-machine re-entry while auth is in progress. Options:
Example (sketch): let connectInProgress = false;
if (!client) {
if (connectInProgress) {
send({ type: "res", id: parsed.id, ok: false, error: errorShape(ErrorCodes.INVALID_REQUEST, "connect already in progress") });
close(1008, "duplicate connect");
return;
}
connectInProgress = true;
clearHandshakeTimer();
armConnectAuthTimer();
try {
const state = await resolveConnectAuthState(...);
// ... continue handshake
} finally {
connectInProgress = false;
}
}Also consider not clearing/rearming the watchdog if it is already armed, so repeated frames cannot extend the deadline. Analyzed PR: #73925 at commit Last updated on: 2026-04-29T01:38:16Z |
Greptile SummaryThis PR repairs contributor PR #49751 to split the single preauth handshake timer into a two-phase design: the original timer fires only if no valid
Confidence Score: 3/5The auth watchdog's core invariant — that stalled auth is bounded — can be bypassed by re-sending valid connect frames before merging. A single P1 finding directly undermines the security property this PR introduces. The rest of the implementation (timer lifecycle, isClosed guards, test coverage) is correct, so this is not a full stop, but the watchdog bypass should be addressed before merging. src/gateway/server/ws-connection.ts — armConnectAuthTimer needs a one-shot guard; src/gateway/server/ws-connection/message-handler.ts — call site at lines 425–426. Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/gateway/server/ws-connection/message-handler.ts
Line: 425-426
Comment:
**Auth watchdog can be reset by repeated valid connect frames**
`armConnectAuthTimer` calls `clearConnectAuthTimer()` internally before arming a new timer (see `ws-connection.ts:408`). The `handleMessage` handler is `async` and has no concurrency guard, so while the first auth is suspended at `await resolveConnectAuthState(...)`, a second valid `connect` frame that arrives will re-enter this block (`!client` is still true), call `armConnectAuthTimer()` again, and silently reset the watchdog. A client can repeat this every `handshakeTimeoutMs - ε` ms to hold the socket open indefinitely, which defeats the PR's core goal of bounding connect-auth duration.
A simple guard is to arm the timer only once — e.g. check `connectAuthTimer` before overwriting it:
```ts
const armConnectAuthTimer = () => {
if (connectAuthTimer) {
return; // already armed; do not reset
}
clearHandshakeTimer();
// ...rest of the existing body
};
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(gateway): bound websocket auth after..." | Re-trigger Greptile |
| clearHandshakeTimer(); | ||
| armConnectAuthTimer(); |
There was a problem hiding this comment.
Auth watchdog can be reset by repeated valid connect frames
armConnectAuthTimer calls clearConnectAuthTimer() internally before arming a new timer (see ws-connection.ts:408). The handleMessage handler is async and has no concurrency guard, so while the first auth is suspended at await resolveConnectAuthState(...), a second valid connect frame that arrives will re-enter this block (!client is still true), call armConnectAuthTimer() again, and silently reset the watchdog. A client can repeat this every handshakeTimeoutMs - ε ms to hold the socket open indefinitely, which defeats the PR's core goal of bounding connect-auth duration.
A simple guard is to arm the timer only once — e.g. check connectAuthTimer before overwriting it:
const armConnectAuthTimer = () => {
if (connectAuthTimer) {
return; // already armed; do not reset
}
clearHandshakeTimer();
// ...rest of the existing body
};Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/server/ws-connection/message-handler.ts
Line: 425-426
Comment:
**Auth watchdog can be reset by repeated valid connect frames**
`armConnectAuthTimer` calls `clearConnectAuthTimer()` internally before arming a new timer (see `ws-connection.ts:408`). The `handleMessage` handler is `async` and has no concurrency guard, so while the first auth is suspended at `await resolveConnectAuthState(...)`, a second valid `connect` frame that arrives will re-enter this block (`!client` is still true), call `armConnectAuthTimer()` again, and silently reset the watchdog. A client can repeat this every `handshakeTimeoutMs - ε` ms to hold the socket open indefinitely, which defeats the PR's core goal of bounding connect-auth duration.
A simple guard is to arm the timer only once — e.g. check `connectAuthTimer` before overwriting it:
```ts
const armConnectAuthTimer = () => {
if (connectAuthTimer) {
return; // already armed; do not reset
}
clearHandshakeTimer();
// ...rest of the existing body
};
```
How can I resolve this? If you propose a fix, please make it concise.|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Keep open, but not merge-ready: the branch targets a real Gateway auth-timeout gap, yet the proposed connect-auth watchdog can be reset before a client is registered, the PR is currently conflicting with main, and after-fix real behavior proof is absent. Canonical path: Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review. So I’m closing this here because the remaining work is already tracked in the canonical issue. Review detailsBest possible solution: Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review. Do we have a high-confidence way to reproduce the issue? Yes by source inspection: while connect auth awaits, getClient() remains null and another valid connect frame can re-enter the PR's pre-client path and rearm the watchdog. I did not run a live socket repro in this read-only review. Is this the best way to solve the issue? No as submitted; the connection/message-handler boundary is plausible, but the deadline must be one-shot or duplicate connect frames must be rejected before this is the best fix. The branch also needs rebase and real behavior proof. Security review: Security review needs attention: The diff introduces a concrete Gateway preauth DoS bypass unless duplicate in-flight connect frames cannot reset the auth watchdog.
AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 738b2be4b49b. |
|
This pull request has been automatically marked as stale due to inactivity. |
|
This pull request has been automatically marked as stale due to inactivity. |
|
ClawSweeper applied the proposed close for this PR.
|
Repair contributor PR #49751 in place.
Plan:
Credit: based on @sashakhar1's original PR #49751, with ProjectClownfish repair commits preserving the contributor path.
ProjectClownfish replacement details:
! [remote rejected] HEAD -> fix/ws-handshake-timeout-loopback-token-auth (refusing to allow a GitHub App to create or update workflow
.github/workflows/ci.ymlwithoutworkflowspermission)error: failed to push some refs to 'https://github.com/sashakhar1/openclaw.git'