← Back to Research
Research · July 22, 2026

How an exposed debug_traceCall lets one unauthenticated request burn seconds of go-ethereum CPU

Simon Morley·5 min read

debug_traceCall executes an eth_call-style message with a full EVM tracer attached, re-running the call opcode-by-opcode against the target block's state and recording per-step instrumentation — so a single small request converts into up to seconds of single-goroutine CPU on an exposed go-ethereum node, a compute-amplification denial-of-service payable by an unauthenticated caller.

Why this matters

The debug namespace is off by default, and go-ethereum's own guidance points away from exposing it publicly: its documentation notes that enabling non-default namespaces increases the attack surface, that no RPC endpoint is designed to withstand hostile clients, and that full execution traces are very resource-intensive. But operators who need tracing — block explorers, tracing/indexing backends, MEV tooling — do enable it, and where it is reachable on a routable interface it is served without per-method authentication. Tracing is materially more expensive than a plain eth_call, so an attacker who can reach the endpoint drives heavy re-execution for the cost of one request. The per-request ceilings that exist (--rpc.gascap, --rpc.evmtimeout) bound a single call but not the rate or concurrency of calls, so a naïve per-call limit does not close it: the aggregate CPU pressure is the ceiling multiplied by an attacker-chosen number of parallel traces.

How the attack works

  • Entry point: the JSON-RPC debug_traceCall method, reachable when the debug namespace is enabled (--http.api debug / --ws.api debug) on a non-loopback bind. The debug namespace is not enabled by default, and go-ethereum's guidance discourages exposing it; there is no per-method auth on the transport.
  • Preconditions: the debug namespace exposed on a routable interface, with no per-source rate limit and no per-connection concurrency cap at the RPC transport.
  • Processing path: debug_traceCall is served by API.TraceCall (eth/tracers/api.go): it reconstructs the parent block's state, builds the requested message, and executes it through the EVM with a tracer hooked into every step — the default struct/opcode logger (or a JS / callTracer / prestateTracer) records instrumentation per opcode and per memory/stack change.
  • Boundary crossed: availability. The attacker controls the cost by choosing a call whose execution path is long or memory-heavy, so it runs right up to the per-request ceilings.
  • Resulting behaviour: each request burns up to the configured wall-clock/gas ceiling of single-goroutine CPU; because the transport applies no rate or concurrency cap, N attacker-chosen concurrent debug_traceCalls each burn up to that ceiling in parallel, saturating node CPU.

Affected systems

  • Severity: MEDIUM — availability-only (no funds, no consensus break, no authentication bypass).
  • Chain: Ethereum.
  • Implementation: go-ethereum (ethereum/go-ethereum), run with the debug namespace exposed on a routable interface.
  • Component / method: JSON-RPC debug_traceCall (the debug namespace over --http/--ws).
  • Not affected: a default node (the debug namespace is off by default), or a node that keeps debug on loopback or behind auth. This advisory does not establish a specific affected version range beyond "go-ethereum with the debug namespace exposed"; the behaviour is the documented method semantics, not a version-specific regression.

Evidence

  • Publicly documented: go-ethereum's documentation supports treating the debug namespace as unsafe to expose — it flags non-default namespaces as increasing the attack surface, states that no RPC endpoint is designed to withstand hostile clients, and describes full execution traces as very resource-intensive; the mechanism is cited to eth/tracers/api.go (API.TraceCall) in the advisory, and the per-request ceilings are the documented --rpc.gascap (default 50,000,000) and --rpc.evmtimeout (default 5s).
  • Independently reproduced by NullRabbit (lab): the published corpus reproducer (primitive eth_debug_tracecall_compute, family compute_amp, source_class: original) is shipped in the public dataset NullRabbit/nr-bundles-public. It captures the attack traffic — repeated debug_traceCall requests that drive heavy traced re-execution — contrasted with a light benign eth_call/trace to the same surface against a local self-owned node.
  • Measured: not established as a single constant. The per-request cost is bounded by --rpc.evmtimeout / --rpc.gascap and maximised by a call whose traced execution is long or memory-heavy; the aggregate impact scales with attacker concurrency, which the RPC server does not cap — so the realised CPU pressure is a function of those ceilings times connection count, not a fixed multiplier. The advisory stands on the source trace and go-ethereum's own documentation of the debug namespace, not on a single measured CPU figure.

Operator actions

  • Do not expose the debug namespace on a routable interface — this is go-ethereum's own guidance. Omit debug from --http.api / --ws.api, or keep it on loopback or behind an authenticating gateway.
  • If tracing must be exposed, auth-gate it, rate-limit per source, and cap concurrency, and tighten --rpc.evmtimeout and --rpc.gascap below the defaults for the traced surface.
  • Serve tracing from a dedicated, isolated, capacity-planned backend rather than a validating/RPC node that also serves consensus-critical traffic.
  • Exposure check: confirm whether debug appears in your --http.api/--ws.api set and whether that transport is bound to a routable address. Per-source rate and concurrency monitoring at the gateway is a suggested control, not a confirmed vendor fix.

Canonical references

Related attacks

eth_debug_tracecall_compute is a compute_amp RPC technique — the same family as other unauthenticated "one small request drives heavy server-side execution" levers on exposed RPC surfaces. Related links here are limited to the shared NRDAX technique id and the compute_amp family recorded in the registry; no further relationship is asserted beyond that.

Track attacks affecting go-ethereum and Ethereum RPC infrastructure.

Analysis plus reproducer. No weaponised proof-of-concept code.

researchethereumgo-ethereumdebug_tracecallcompute_amprpcinfrastructure-security
Publication policy:Why we publish these findings →

Related Posts

One prior-knowledge h2c connection multiplexes N eth_getLogs past an L4 per-connection cap on go-ethereum's JSON-RPC port

·7 min read

go-ethereum's JSON-RPC port terminates cleartext HTTP/2 (h2c) by prior knowledge, so one TCP connection can multiplex N eth_getLogs streams past an L4 edge that only caps connections per IP (nginx stream limit_conn) — measured 20/20 streams and ~39 MB pulled through a single connection the edge counts as one, an amplification-multiplexing bypass of a connection cap, not of an L7 request rate limit.

researchethereumgo-ethereum+5 more

Bypassing go-ethereum's --http.vhosts host allowlist with an empty Host, forged by an HAProxy HTTP/2→1.1 downgrade

·7 min read

An HTTP/2 request with an empty :authority, downgraded to HTTP/1.1 by an HAProxy front, reaches go-ethereum with an empty Host header that its --http.vhosts allowlist accepts (200) though it rejects any concrete disallowed host (403) — a bypass of geth's anti-DNS-rebinding Host filter by an attacker with raw-HTTP/2 reach to the edge.

researchethereumgo-ethereum+5 more