GHSA-4r25-35qc-fr6j · CWE-400, CWE-404 · CVSS 3.1 base score 7.5 (High)
| Package | openclaw (npm) |
| Vulnerability type | Denial of service / resource exhaustion |
| Attack vector | Remote, unauthenticated |
| Affected versions | < 2026.7.2-beta.1, >= 2026.7.2-beta.1, < 2026.7.2-beta.7 |
| Patched version | 2026.7.2-beta.7 |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
| CVE | Not assigned (see Disclosure note) |
| Reporter | Yongyeop Cho (@BrandonChoKR), STEALIEN |
| Published | 2026-09-01 |
Summary
The openclaw Gateway's HTTP request handler decides whether an incoming request is a WebSocket upgrade by looking at the Upgrade header alone. A request that carries Upgrade: websocket without the matching Connection: upgrade token is not routed as a WebSocket upgrade by Node's HTTP server, but the handler still treats it as one and returns early without calling res.end().
The socket is left open indefinitely. Because the request never completes and is never counted against the pre-authentication connection budget, an unauthenticated remote attacker can open sockets in a loop and hold Gateway resources until the process stops serving legitimate traffic. No credentials, no user interaction, and no special configuration are required — only network reachability to the Gateway.
Affected versions
At the time of reporting, three of the four published npm dist-tags shipped the vulnerable code, and the fix existed only on beta:
dist-tag Version Status
| latest | 2026.7.1-2 | Affected |
| extended-stable | 2026.6.34 | Affected |
| alpha | 2026.5.19-alpha.1 | Affected |
| beta | 2026.7.2-beta.7 | Patched |
Note the version range is split in two. 2026.7.1-2 is a prerelease identifier, and under semver prerelease comparison rules a single range of < 2026.7.2-beta.7 does not match it. Tooling that consumes the advisory needs both < 2026.7.2-beta.1 and >= 2026.7.2-beta.1, < 2026.7.2-beta.7 to cover every affected release.
Details
The vulnerable guard is in src/gateway/server-http.ts (around lines 537–547 of the version analyzed):
if ((req.headers.upgrade ?? "").toLowerCase() === "websocket") {
return;
}
The intent is to hand WebSocket upgrades off to the upgrade handler and stop processing them as ordinary HTTP. The problem is the condition.
Node's HTTP server emits the upgrade event only when both Upgrade and Connection: upgrade are present. A request that sets Upgrade: websocket but sends Connection: keep-alive is delivered to the normal request handler instead. The guard above matches it anyway, returns, and never writes a response or destroys the socket.
Three consequences follow:
- No response is ever sent. The return happens before any res.end(), so the connection stays open with the request half-processed.
- The connection is invisible to the budget. The pre-authentication connection budget counts completed request cycles, so these sockets are never accounted for and never trigger throttling.
- No timeout closes it. The Gateway does not set requestTimeout, headersTimeout, or a socket timeout, so nothing reclaims the socket.
Each of the three alone would be recoverable. Together they mean a single malformed header combination converts into unbounded, unauthenticated resource retention.
Proof of concept
Verified in a local, isolated environment. The observations below are measured, not inferred.
Target: src/gateway/server-http.ts (Gateway HTTP request handler)
Environment: Windows 11, Node.js v24.18.0, local build
Preconditions: Network reachability to the Gateway port. No authentication.
Input: Raw HTTP/1.1 request, repeated across parallel sockets:
GET /hooks/wake HTTP/1.1
Host: <gateway-host>
Upgrade: websocket
Connection: keep-alive
Procedure: 1. Start the Gateway with default configuration.
2. Open 25 TCP sockets and write the request above on each.
3. Send nothing further and read no response.
4. Observe socket state over time.
Observation: All 25 sockets remained open for the full 45-second observation
window. No response bytes were returned on any socket, and no
socket was closed by the server. The connections did not appear
against the pre-auth connection budget.
Impact: Availability. Sockets are retained indefinitely without
authentication and without accounting, so the retention scales
with the number of connections an attacker chooses to open.
The demonstration was deliberately limited to establishing that the condition exists. No attempt was made to exhaust the target or to measure a failure threshold.
The same request shape now appears in the project's own test suite. The fix commit adds server-http.upgrade-claim.test.ts, whose case rejects websocket upgrade headers that Node routes as ordinary HTTP sends Upgrade: websocket with Connection: keep-alive and asserts a 400 response.
Impact
Availability only. There is no read or write of application data, and no authentication bypass.
Anyone who can reach the Gateway over the network can hold its resources without credentials. That makes the exposure a direct function of deployment: a Gateway bound to a loopback interface is reachable only locally, while one exposed to a network or the internet is reachable by anyone. Deployments that place the Gateway behind a shared reverse proxy are also affected, since the proxy forwards the malformed request unchanged.
The relevant boundary here is authentication, not the project's trusted-operator model. This is reachable before any operator identity is established, so it is not covered by the assumption that authenticated Gateway operators are trusted.
Mitigation
Upgrade. The fix is in 2026.7.2-beta.7 and later on the beta channel.
As of publication no patched release exists on latest or extended-stable. Users on those channels have no upgrade path within their channel and should apply the workarounds below.
Workarounds.
- Validate the header pair at the reverse proxy: reject any request that carries an Upgrade header without a matching upgrade token in Connection.
- Set a socket idle timeout in front of the Gateway so half-open connections are reclaimed.
- Restrict network exposure of the Gateway to trusted sources until a patched release is available on your channel.
The applied patch. The upstream fix replaces the header-only check with one that verifies both headers, and explicitly rejects requests that claim an upgrade without satisfying it:
if (isWebSocketUpgradeRequest(req)) {
return;
}
if (req.headers.upgrade !== undefined) {
res.statusCode = 400;
res.setHeader("Connection", "close");
res.end("Bad Request");
return;
}
isWebSocketUpgradeRequest splits Connection on commas and compares trimmed tokens, so combinations such as Connection: keep-alive, Upgrade are handled correctly. I reviewed the patch and found no remaining bypass for this condition.
Two defence-in-depth measures were not included and remain worth considering: request and header timeouts on the HTTP server, and extending the pre-authentication connection budget to cover connections that never complete a request cycle. Neither is required to close this specific issue.
Timeline
Date Event
| 2026-07-28 | Reported privately through GitHub Security Advisory GHSA-4r25-35qc-fr6j. CVE assignment requested in the same thread. |
| 2026-08-01 | Fix merged publicly in PR #115038 (commit beab295) and released in 2026.7.2-beta.7. Advisory remained in draft. |
| 2026-08-05 | Followed up in the advisory thread, including a direct mention of a repository owner, since publishing requires owner permissions. No response. |
| 2026-09-01 | Advisory still unpublished, 35 days after the report. This write-up published. |
Disclosure note
The fix has been public since 2026-08-01, and the regression test shipped with it spells out the reproduction, so this vulnerability has been publicly known for a month. The advisory nonetheless remains in draft, which leaves users on the stable channels running vulnerable code with no notice that a fix exists elsewhere.
I do not read the delay as specific to this report. The repository has published no security advisories since 2026-06-30, and advisories it has published carry no CVE identifier — for example GHSA-jhfx-v2j8-x3m6 (CVSS 7.6, High) lists "No known CVE." Requesting CVE identifiers does not appear to be part of this project's advisory workflow.
On that basis I am publishing this write-up so affected users have actionable information, and requesting a CVE identifier from the MITRE CNA of Last Resort. I would still welcome publication of the upstream advisory, and will update this document with a CVE identifier if one is issued.
Credit
Reported by Yongyeop Cho (@BrandonChoKR), Senior Researcher at STEALIEN. Reporter credit was accepted on the upstream advisory. The fix was implemented and merged by the openclaw maintainers.
References
- Upstream advisory (draft): GHSA-4r25-35qc-fr6j
- Fix: PR #115038, commit beab295
- Repository advisories: https://github.com/openclaw/openclaw/security/advisories
- CWE-400: Uncontrolled Resource Consumption
- CWE-404: Improper Resource Shutdown or Release
'Information Technology > write-up' 카테고리의 다른 글
| SQL Injection 5 blind SQLi (0) | 2025.05.28 |
|---|---|
| SQL Injection 4 error based SQLi (0) | 2025.05.28 |
| SQL Injection 3 (error based) (0) | 2025.05.28 |
| SQL Injection (Blind Practice) (0) | 2025.05.28 |
| SQL Injection (Error Based SQLi Basic) - extractvalue (0) | 2025.05.28 |




























