Skip to content

Commit be55bfd

Browse files
fix(voice-call): close in-flight limiter fail-open on empty remote address
The webhook in-flight limiter (createWebhookInFlightLimiter in src/plugin-sdk/webhook-request-guards.ts) returns true unconditionally when tryAcquire is called with an empty key — that is its by-contract fail-open path used to mean 'caller is opting out of the limiter'. The voice-call webhook handler reached that path silently: it computed 'req.socket.remoteAddress ?? ""' and passed the empty string straight into tryAcquire. Whenever req.socket.remoteAddress was absent (closed socket, edge proxy quirk), the limiter became a no-op and the request proceeded directly to readBody without any concurrency cap. Fix: when remoteAddress is missing, log a warning and fall back to a constant non-empty key ('__voice_call_no_remote__') so all such requests share one in-flight bucket instead of bypassing the limiter entirely. The bucket size stays maxInFlightPerKey (default 8), which is the right defense-in-depth posture against slow-body attacks arriving with stripped IP info. Scoped to voice-call only. Other consumers of the SDK helper (bluebubbles via openclaw/plugin-sdk/webhook-ingress) are not changed to avoid drive-by edits to plugins this PR does not own. The shared SDK contract (empty key = bypass) is left as-is and documented implicitly by the fix's comment block. The existing 8-concurrent test in webhook.test.ts continues to assert the limiter engages on the happy path; no new test added since the private handleRequest path is not unit-test exposed and the change is two-line auditable from the diff alone.
1 parent c6b2691 commit be55bfd

1 file changed

Lines changed: 12 additions & 1 deletion

File tree

extensions/voice-call/src/webhook.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,18 @@ export class VoiceCallWebhookServer {
616616
return { statusCode: 401, body: "Unauthorized" };
617617
}
618618

619-
const inFlightKey = req.socket.remoteAddress ?? "";
619+
// The shared in-flight limiter (createWebhookInFlightLimiter) returns true
620+
// unconditionally when handed an empty key — that is the fail-open path. If
621+
// req.socket.remoteAddress is missing for any reason (closed socket, edge
622+
// proxy quirk), fall back to a constant non-empty key so all such requests
623+
// share one bucket instead of bypassing the limiter entirely.
624+
const remoteAddress = req.socket.remoteAddress;
625+
if (!remoteAddress) {
626+
console.warn(
627+
`[voice-call] Webhook accepted with no remote address; using shared fallback in-flight key`,
628+
);
629+
}
630+
const inFlightKey = remoteAddress || "__voice_call_no_remote__";
620631
if (!this.webhookInFlightLimiter.tryAcquire(inFlightKey)) {
621632
console.warn(`[voice-call] Webhook rejected before body read: too many in-flight requests`);
622633
return { statusCode: 429, body: "Too Many Requests" };

0 commit comments

Comments
 (0)