Skip to content

fix: make request-local rate limiter more robust#671

Open
nyannyacha wants to merge 1 commit intomainfrom
ny/hardening-rate-limit
Open

fix: make request-local rate limiter more robust#671
nyannyacha wants to merge 1 commit intomainfrom
ny/hardening-rate-limit

Conversation

@nyannyacha
Copy link
Contributor

What kind of change does this PR introduce?

Enhancement

Description

...WIP

@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Chores
    • Enhanced HTTP request tracing by introducing trace ID header tracking for improved observability of request flow
    • Refined rate-limiting behavior to consistently use trace ID for decision-making and error messaging

Walkthrough

These changes implement a trace ID header propagation mechanism across the HTTP stack. A new header constant FETCH_TRACE_ID_HEADER ("x-er-fetch-trace-id") is introduced and exported from the request context module. When tracing is active, this header is conditionally injected into fetch requests to carry the trace ID. The trace ID extraction logic is updated to prioritize this header value, falling back to parsing the traceparent header if absent. Rate-limit decision-making is adjusted to use the actual trace ID value for key selection and consistency in error messaging, decoupling rate-limit logic from OpenTelemetry traceparent handling.

Sequence Diagram

sequenceDiagram
    participant Client as HTTP Client
    participant Fetch as Fetch Handler<br/>(vendor/deno_fetch)
    participant Runtime as Runtime HTTP<br/>(ext/runtime/js)
    participant RequestCtx as Request Context<br/>(ext/runtime/js)
    participant RateLimit as Rate Limiter

    Client->>Fetch: Initiate HTTP Request
    Fetch->>Fetch: Check if tracing active<br/>(isTraced)
    alt Tracing Active
        Fetch->>Fetch: Inject FETCH_TRACE_ID_HEADER<br/>into request
    end
    Fetch->>RateLimit: Check rate limit<br/>(effectivelyTraced flag)
    RateLimit->>RateLimit: Use trace ID for<br/>rate-limit key
    RateLimit-->>Fetch: Rate limit decision
    Fetch->>Runtime: Forward request with headers
    Runtime->>RequestCtx: Extract traceId
    RequestCtx->>RequestCtx: Check FETCH_TRACE_ID_HEADER
    alt Header Present
        RequestCtx->>RequestCtx: Use header value
    else Header Absent
        RequestCtx->>RequestCtx: Parse traceparent header
    end
    Runtime->>Runtime: Enter request context<br/>with extracted traceId
Loading

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ext/runtime/js/http.js`:
- Around line 232-248: Do not trust the incoming FETCH_TRACE_ID_HEADER from
external requests: remove the unconditional read of
requestEvent.request.headers.get(FETCH_TRACE_ID_HEADER) in the trace-id
resolution logic and instead either (a) strip that header at the HTTP handler
boundary so it never reaches this code, or (b) only accept it after a proven
internal validation (e.g., an internal hop marker or request origin check)
before passing it into enterRequestContext; keep the existing traceparent
parsing fallback (the traceparent split/parts logic) as the safe default and
ensure only validated/internal trace IDs are handed to enterRequestContext.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7ca57b99-ff5b-49e4-82a5-95eef3422ae3

📥 Commits

Reviewing files that changed from the base of the PR and between bdf1893 and 6ba8279.

📒 Files selected for processing (4)
  • ext/node/polyfills/http.ts
  • ext/runtime/js/http.js
  • ext/runtime/js/request_context.js
  • vendor/deno_fetch/26_fetch.js

Comment on lines +232 to 248
// x-er-fetch-trace-id takes priority: injected by the runtime's fetch /
// node:http into every outbound request, so it is tamper-proof once set.
// On the very first hop it won't be present, so we fall back to parsing
// the W3C traceparent header.
let traceId =
requestEvent.request.headers.get(FETCH_TRACE_ID_HEADER) ?? null;
if (traceId === null) {
const traceParent = requestEvent.request.headers.get("traceparent");
if (traceParent) {
// traceparent format: 00-{trace-id}-{parent-id}-{flags}
const parts = traceParent.split("-");
if (parts.length >= 4 && parts[1].length === 32) {
traceId = parts[1];
}
}
}
const prevCtx = enterRequestContext(traceId);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for x-er-fetch-trace-id header usages and FETCH_TRACE_ID_HEADER
rg -n "x-er-fetch-trace-id" -i --type-list | head -20
rg -n "x-er-fetch-trace-id" -i

Repository: supabase/edge-runtime

Length of output: 775


🏁 Script executed:

#!/bin/bash
# Search for FETCH_TRACE_ID_HEADER definition and usage
rg -n "FETCH_TRACE_ID_HEADER" -A 3 -B 3

Repository: supabase/edge-runtime

Length of output: 3196


🏁 Script executed:

#!/bin/bash
# Check for any header stripping or validation logic in request handling
rg -n "headers.delete|headers.remove|strip" --type js --type ts -A 2 -B 2 | head -50

Repository: supabase/edge-runtime

Length of output: 3171


🏁 Script executed:

#!/bin/bash
# Look for rate limiting code and how traceId is used
rg -n "is_traced|bucket|rate.limit" --type rs --type js -A 3 -B 1 | head -80

Repository: supabase/edge-runtime

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Find where http.js is imported/used
rg -n "from.*http.js|import.*http.js" --type js --type ts

Repository: supabase/edge-runtime

Length of output: 481


🏁 Script executed:

#!/bin/bash
# Look for context about requestEvent - where does it come from?
rg -n "requestEvent" ext/runtime/js/http.js -B 5 -A 5 | head -40

Repository: supabase/edge-runtime

Length of output: 1678


🏁 Script executed:

#!/bin/bash
# Check if there's any middleware or edge handling that processes requests before http.js
rg -n "middleware|edge|gateway" --type js --type ts -i | head -30

Repository: supabase/edge-runtime

Length of output: 3116


🏁 Script executed:

#!/bin/bash
# Look for the HTTP server setup/listener to understand request origin
rg -n "listen|serve|Deno.serve|createServer" --type js --type ts -A 3 | head -50

Repository: supabase/edge-runtime

Length of output: 2997


🏁 Script executed:

#!/bin/bash
# Check the handleHttp function more completely to see how it processes connections
rg -n "handleHttp" ext/runtime/js/http.js -B 10 -A 30

Repository: supabase/edge-runtime

Length of output: 2100


🏁 Script executed:

#!/bin/bash
# Look for where the HTTP server is actually started to understand request flow
rg -n "function serve|export.*serve" ext/runtime/js/http.js -A 5 -B 2

Repository: supabase/edge-runtime

Length of output: 620


🏁 Script executed:

#!/antml/bash
# Check if there's any request validation or header filtering in the codebase
rg -n "request\.headers|headers\.delete|delete.*header|untrusted" --type js --type ts | grep -i "x-er\|header\|delete\|strip\|untrusted" | head -40

Repository: supabase/edge-runtime

Length of output: 1160


🏁 Script executed:

#!/bin/bash
# Look at the full respond function to understand context better
sed -n '227,300p' ext/runtime/js/http.js

Repository: supabase/edge-runtime

Length of output: 2430


Security risk: Incoming x-er-fetch-trace-id header is trusted without validation.

An external client can set this header on their request, allowing them to control the trace ID used for their request context. This header is intended for propagation between internal hops, but the code does not validate that it originated from an internal request—it accepts it directly from any incoming HTTP request.

The header should be stripped from incoming requests at the HTTP handler level, or validated to ensure it originates from an internal hop only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ext/runtime/js/http.js` around lines 232 - 248, Do not trust the incoming
FETCH_TRACE_ID_HEADER from external requests: remove the unconditional read of
requestEvent.request.headers.get(FETCH_TRACE_ID_HEADER) in the trace-id
resolution logic and instead either (a) strip that header at the HTTP handler
boundary so it never reaches this code, or (b) only accept it after a proven
internal validation (e.g., an internal hop marker or request origin check)
before passing it into enterRequestContext; keep the existing traceparent
parsing fallback (the traceparent split/parts logic) as the safe default and
ensure only validated/internal trace IDs are handed to enterRequestContext.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good point. We should sign it using something like HMAC to ensure integrity.

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