-
-
Notifications
You must be signed in to change notification settings - Fork 57.4k
Description
Summary
Severity: P1/High (Score: 85/150)
CWE: CWE-400 - Uncontrolled Resource Consumption
OWASP: A05:2021 - Security Misconfiguration
File: src/gateway/client.ts:382-407
The GatewayClient.request() method returns a Promise that has no timeout mechanism. If the gateway server never responds (network partition, crash, packet loss), the promise hangs forever, leaking memory and blocking callers indefinitely.
Why this is critical: Gateway client requests are used throughout the codebase for inter-service communication. Network partitions, server crashes, and packet loss are inevitable in distributed systems. Without timeouts, a single unresponsive gateway causes cascading hangs—callers block waiting for responses that never come, the pending Map accumulates entries that leak memory, and upstream timeouts (if any) don't clean up downstream state. The codebase already has a good example of timeout handling in IMessageRpcClient—this should follow the same pattern.
Triage Assessment
| Factor | Value | Score |
|---|---|---|
| Reachability | Network conditions trigger (not attacker-controlled) | 20/40 |
| Impact | Memory leak, workflow stalls | 25/50 |
| Exploitability | Passive (network instability) | 15/30 |
| Verification | file:line ✓, code ✓, positive example cited | 25/30 |
| Total | — | 85/150 |
Steps to reproduce
- Establish a gateway client connection
- Make a request via
client.request('method', params) - Kill the gateway server without closing the connection cleanly
- Observe the request promise never resolves or rejects
- Memory in
this.pendingMap grows with each stuck request
Expected behavior
Requests should timeout after a configurable duration (e.g., 30 seconds), rejecting with a timeout error and cleaning up the pending entry.
Actual behavior
Promises stored in this.pending map have no timeout mechanism:
Affected code location:
Gateway Client (src/gateway/client.ts:382-407):
async request<T = unknown>(
method: string,
params?: unknown,
opts?: { expectFinal?: boolean },
): Promise<T> {
// ...
const p = new Promise<T>((resolve, reject) => {
this.pending.set(id, {
resolve: (value) => resolve(value as T),
reject,
expectFinal,
});
});
this.ws.send(JSON.stringify(frame));
return p; // <-- No timeout!
}Environment
- Version: latest (main branch)
- OS: Any
- Install method: Any
Positive example in codebase
The IMessageRpcClient.request() (src/imessage/client.ts:127-163) has proper timeout handling:
const timeout = setTimeout(() => {
this.pending.delete(id);
reject(new Error(`RPC timeout for ${method}`));
}, this.timeoutMs);Impact
- Memory leak: Each hung request leaks a pending Map entry
- Workflow stalls: Callers awaiting these promises block indefinitely
- Resource exhaustion: Under partial network failures, many requests can accumulate
- No visibility: No error or log indicates the hang
Recommended fix
Add timeout to GatewayClient.request():
const timeout = setTimeout(() => {
this.pending.delete(id);
reject(new Error(`Gateway request timeout for ${method}`));
}, this.timeoutMs ?? 30000);
// In the resolve/reject handlers:
clearTimeout(timeout);