Skip to content

fix(proxy): bound total request time to stop a slow-body permit DoS#161

Open
arseniycodes wants to merge 1 commit into
mainfrom
ash/sec-proxy-body-timeout
Open

fix(proxy): bound total request time to stop a slow-body permit DoS#161
arseniycodes wants to merge 1 commit into
mainfrom
ash/sec-proxy-body-timeout

Conversation

@arseniycodes

@arseniycodes arseniycodes commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Problem

An admission permit is acquired before the request body is read and released only after it's fully read/forwarded. There are only ~64 permits and they're shared across all tenants. The server set keepAliveTimeout and headersTimeout but no request/body-read timeout, and Node's default requestTimeout is a lax 300s — and headers complete instantly, so headersTimeout doesn't help against a slow body.

Impact

With one valid ingest key, an attacker opens 64 connections to /v1/traces and sends the body a few bytes at a time (slowloris). All 64 permits stay pinned and every other tenant's requests queue behind them — cross-tenant denial of service from a single low-privilege key.

Fix

Set server.requestTimeout (default 120s, overridable via INGEST_REQUEST_TIMEOUT_MS) so a stalled request is aborted and its permit released. Kept above headersTimeout (76s) and generous enough for a 64 MiB body over a slow link (~0.5 MB/s floor).

Note: this closes the slow-body vector. Per-tenant rate limiting (a separate noisy-neighbor concern) is intentionally out of scope here.


Summary by cubic

Bound total request time to stop slow-body DoS that pinned admission permits across tenants. Adds server.requestTimeout (120s default, configurable via INGEST_REQUEST_TIMEOUT_MS) so stalled bodies are aborted and permits released.

  • Bug Fixes
    • Covers body read to prevent a single key from holding ~64 permits by dribbling uploads.
    • Timeout stays above headersTimeout (76s) and is generous for ~64 MiB bodies on slow links.

Written for commit d290fc2. Summary will update on new commits.

Review in cubic

An admission permit (only ~64 exist, shared across all tenants) is held
from before the body read until the body is fully read/forwarded, and
there was no per-request/body-read timeout — only keepAliveTimeout and
headersTimeout, which don't cover a slow body. A single valid ingest key
can open 64 connections and dribble their bodies a few bytes at a time,
pinning every permit and starving all other tenants (cross-tenant DoS).

Set server.requestTimeout (default 120s, env INGEST_REQUEST_TIMEOUT_MS)
so a stalled request is aborted and its permit released. Kept above
headersTimeout and generous enough for a 64 MiB body over a slow link.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/proxy/src/index.ts">

<violation number="1" location="apps/proxy/src/index.ts:882">
P2: Setting `INGEST_REQUEST_TIMEOUT_MS=0` would disable `server.requestTimeout`, so one misconfigured deploy can restore the slow-body permit DoS. Consider validating this timeout as a positive value (or falling back on 0) instead of using `readNonNegativeIntEnv` here.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread apps/proxy/src/index.ts
Comment on lines +882 to +885
server.requestTimeout = readNonNegativeIntEnv(
process.env.INGEST_REQUEST_TIMEOUT_MS,
120_000,
);

@cubic-dev-ai cubic-dev-ai Bot Jul 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Setting INGEST_REQUEST_TIMEOUT_MS=0 would disable server.requestTimeout, so one misconfigured deploy can restore the slow-body permit DoS. Consider validating this timeout as a positive value (or falling back on 0) instead of using readNonNegativeIntEnv here.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/proxy/src/index.ts, line 882:

<comment>Setting `INGEST_REQUEST_TIMEOUT_MS=0` would disable `server.requestTimeout`, so one misconfigured deploy can restore the slow-body permit DoS. Consider validating this timeout as a positive value (or falling back on 0) instead of using `readNonNegativeIntEnv` here.</comment>

<file context>
@@ -872,6 +872,17 @@ const server = serve({ fetch: app.fetch, port: PORT });
+  // requestTimeout is a lax 300s; tighten it so a stalled body is aborted and
+  // its permit released. Kept well above headersTimeout and generous enough for
+  // a 64 MiB body over a slow link (~0.5 MB/s floor at the default).
+  server.requestTimeout = readNonNegativeIntEnv(
+    process.env.INGEST_REQUEST_TIMEOUT_MS,
+    120_000,
</file context>
Suggested change
server.requestTimeout = readNonNegativeIntEnv(
process.env.INGEST_REQUEST_TIMEOUT_MS,
120_000,
);
server.requestTimeout = Math.max(
1,
readNonNegativeIntEnv(
process.env.INGEST_REQUEST_TIMEOUT_MS,
120_000,
),
);
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant