/rpc-surface-audit
L1 trigger - audits JSON-RPC and Engine API surfaces for authentication bypass, rate limiting, subscription buffer overflows, and method-specific DoS.
$ npx -y skills add PlamenTSV/plamen --skill rpc-surface-audit --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/rpc-surface-audit
Context preview
The summary Claude sees to decide when to auto-load this skill.
L1 trigger - audits JSON-RPC and Engine API surfaces for authentication bypass, rate limiting, subscription buffer overflows, and method-specific DoS.
SKILL.md
rpc-surface-audit.SKILL.mdname: "rpc-surface-audit"
description: "L1 trigger - audits JSON-RPC and Engine API surfaces for authentication bypass, rate limiting, subscription buffer overflows, and method-specific DoS."
Injectable Skill: RPC Surface Audit
> **L1 trigger**: `L1_PATTERN=true` AND (`rpc/` OR `jsonrpc` OR `engine_api` OR `eth/api` OR `websocket` OR `ipc` detected in recon subsystem map) > **Inject Into**: `depth-network-surface` > **Language**: Go and Rust > **Finding prefix**: `[RPC-N]` > **Status**: v0.1 draft, Round 4 exemplars pending
Orchestrator Decomposition Guide
- Section 1: depth-network-surface (attack surface)
- Section 2: depth-network-surface (auth + rate limit)
- Section 3: depth-state-trace (subscription state)
- Section 4: depth-edge-case (method boundaries)
When This Skill Activates
Recon identifies an RPC subsystem. RPC is the most publicly exposed attack surface on any L1 node — typically unauthenticated (HTTP JSON-RPC, WebSocket) or semi-authenticated (Engine API with JWT). Even a Medium-severity bug here often upgrades to High because of permissionless reachability (see severity-matrix.md modifier).
1. Attack Surface Enumeration
Enumerate all RPC entry points via LSP `workspace/symbol` filtered by known method-registration patterns:
| Transport | Registration pattern | Example | |---|---|---| | **HTTP JSON-RPC** | `rpc.Register`, `httpServer.Handle`, `#[method(name=...)]` | `eth_*`, `debug_*`, `admin_*` | | **WebSocket** | Same as HTTP + subscription handlers | `eth_subscribe` | | **IPC** (unix socket) | Often same as HTTP | Geth `admin` namespace | | **Engine API** | JWT-authenticated | `engine_newPayloadV*`, `engine_forkchoiceUpdatedV*` | | **Prometheus metrics** | `/metrics` endpoint | often bound to all interfaces | | **PPROF profiling** | `/debug/pprof/` (Go) | should NEVER be public |
Write the enumeration to `scratchpad/rpc_surface.md`.
2. Authentication and Namespace Gating
2a. Default exposure
- What namespaces are enabled by default on HTTP? (Geth: `eth`, `net`, `web3`. Not `admin`, `debug`, `personal`.)
- Is the default bind address localhost or 0.0.0.0?
- Is authentication required for any namespace?
2b. Dangerous namespaces
- `admin_*` — should never be exposed on HTTP; check the config validation
- `debug_*` — can expose internal state; check default
- `personal_*` — key management; deprecated in Geth but still present in forks
- `engine_*` — JWT-authenticated; check the JWT secret handling
- `miner_*` / `txpool_*` — varies by client
2c. CORS and origin checks
- Is the WebSocket origin-checked? (Yes in Geth; verify in forks)
- Are CORS headers tight? Default should be same-origin
2d. JWT handling (Engine API specifically)
- JWT secret rotation: what happens on secret change mid-operation?
- JWT `iat` claim: is clock drift tolerated? Excessive tolerance is a replay window
- JWT algorithm whitelisting: must be HS256 only; reject `alg=none` and key-confusion attacks
Tag: `[RPC-AUTH:{issue}]`
3. Rate Limiting and Resource Quotas
3a. Per-request cost tracking
Some RPC methods are cheap (`eth_blockNumber`), others are expensive (`eth_getLogs` with wide range, `debug_traceTransaction`). Is there a cost model that bounds work per request?
**Check**:
- For each expensive method, is there a **result size limit**? E.g., `eth_getLogs` with `max_logs_per_request`
- For each historical query, is there a **block range limit**? E.g., `eth_getLogs` with `fromBlock` → `toBlock` span
- For each trace method, is there a **trace depth** / **trace time** limit?
3b. Per-client rate limit
- Is there rate limiting by IP, by API key, by connection?
- What's the default burst + sustained rate?
3c. Total concurrent request cap
- Maximum simultaneous HTTP requests
- Maximum simultaneous WebSocket connections
- Maximum subscriptions per WebSocket connection
Tag: `[RPC-RATE:{scope}:{limit-or-unbounded}]`
3d. Outbound HTTP client timeout audit
The skill historically focused on incoming RPC. Audit OUTBOUND HTTP clients used by the node for peer fetches and inter-node API calls — they are a symmetric DoS vector.
**Check**:
- For each `reqwest::Client::builder()`, `awc::Client::builder()`, `hyper::Client::builder()`, and `http::Client::new()` site, verify `.timeout(...)` AND `.connect_timeout(...)` are set explicitly. The library defaults are NO timeout.
- For each `tokio::time::timeout` wrapper around an outbound call, verify the timeout bound is a constant (not derived from peer-controlled input).
- For Go: `http.Client{}` literal (zero value) has NO timeout — flag every site that constructs `http.Client` without setting `Timeout`. Also check `Transport.DialContext` for connect-timeout.
- **Fail mode**: an unresponsive remote peer can hang the calling task forever, blocking critical bootstrap, sync, or peer-handshake routines. Combined with peer-list flooding, an attacker can stall every honest node's startup.
Tag: `[RPC-CLIENT-NO-TIMEOUT:{file}:{line}]`
3e. JSON number precision for u64 fields
JavaScript clients (and many other dynamic languages) cannot represent integers above `2^53 - 1` precisely. L1 nodes that serialize `u64` fields as JSON numbers (not strings) will silently corrupt block heights, balances, gas values, and timestamps when consumed by JS clients.
**Check**:
- For each `serde::Serialize` impl on a struct exposed via RPC, find every `u64` / `u128` / `i64` / `i128` field
- Verify the serializer either uses `#[serde(with = "as_string")]` or a similar string-coercion attribute, OR that the field's max value provably fits in 53 bits
- For Go: check `json.Marshal` of `uint64` fields and `*hexutil.Uint64` wrappers (the latter is correct, the former is not)
Tag: `[RPC-JSON-PRECISION:{type}.{field}]`
3f. Success Return vs Side-Effect Completion
For each RPC handler, peer HTTP endpoint, and outbound API client wrapper that returns `Ok(())`, HTTP 2xx, a success JSON body, or increments peer score,
Read more
name: "rpc-surface-audit" description: "L1 trigger - audits JSON-RPC and Engine API surfaces for authentication bypass, rate limiting, subscription buffer overflows, and method-specific DoS."
Injectable Skill: RPC Surface Audit
> **L1 trigger**: `L1_PATTERN=true` AND (`rpc/` OR `jsonrpc` OR `engine_api` OR `eth/api` OR `websocket` OR `ipc` detected in recon subsystem map) > **Inject Into**: `depth-network-surface` > **Language**: Go and Rust > **Finding prefix**: `[RPC-N]` > **Status**: v0.1 draft, Round 4 exemplars pending
Orchestrator Decomposition Guide
- Section 1: depth-network-surface (attack surface)
- Section 2: depth-network-surface (auth + rate limit)
- Section 3: depth-state-trace (subscription state)
- Section 4: depth-edge-case (method boundaries)
When This Skill Activates
Recon identifies an RPC subsystem. RPC is the most publicly exposed attack surface on any L1 node — typically unauthenticated (HTTP JSON-RPC, WebSocket) or semi-authenticated (Engine API with JWT). Even a Medium-severity bug here often upgrades to High because of permissionless reachability (see severity-matrix.md modifier).
1. Attack Surface Enumeration
Enumerate all RPC entry points via LSP `workspace/symbol` filtered by known method-registration patterns:
| Transport | Registration pattern | Example | |---|---|---| | **HTTP JSON-RPC** | `rpc.Register`, `httpServer.Handle`, `#[method(name=...)]` | `eth_*`, `debug_*`, `admin_*` | | **WebSocket** | Same as HTTP + subscription handlers | `eth_subscribe` | | **IPC** (unix socket) | Often same as HTTP | Geth `admin` namespace | | **Engine API** | JWT-authenticated | `engine_newPayloadV*`, `engine_forkchoiceUpdatedV*` | | **Prometheus metrics** | `/metrics` endpoint | often bound to all interfaces | | **PPROF profiling** | `/debug/pprof/` (Go) | should NEVER be public |
Write the enumeration to `scratchpad/rpc_surface.md`.
2. Authentication and Namespace Gating
2a. Default exposure
- What namespaces are enabled by default on HTTP? (Geth: `eth`, `net`, `web3`. Not `admin`, `debug`, `personal`.)
- Is the default bind address localhost or 0.0.0.0?
- Is authentication required for any namespace?
2b. Dangerous namespaces
- `admin_*` — should never be exposed on HTTP; check the config validation
- `debug_*` — can expose internal state; check default
- `personal_*` — key management; deprecated in Geth but still present in forks
- `engine_*` — JWT-authenticated; check the JWT secret handling
- `miner_*` / `txpool_*` — varies by client
2c. CORS and origin checks
- Is the WebSocket origin-checked? (Yes in Geth; verify in forks)
- Are CORS headers tight? Default should be same-origin
2d. JWT handling (Engine API specifically)
- JWT secret rotation: what happens on secret change mid-operation?
- JWT `iat` claim: is clock drift tolerated? Excessive tolerance is a replay window
- JWT algorithm whitelisting: must be HS256 only; reject `alg=none` and key-confusion attacks
Tag: `[RPC-AUTH:{issue}]`
3. Rate Limiting and Resource Quotas
3a. Per-request cost tracking
Some RPC methods are cheap (`eth_blockNumber`), others are expensive (`eth_getLogs` with wide range, `debug_traceTransaction`). Is there a cost model that bounds work per request?
**Check**:
- For each expensive method, is there a **result size limit**? E.g., `eth_getLogs` with `max_logs_per_request`
- For each historical query, is there a **block range limit**? E.g., `eth_getLogs` with `fromBlock` → `toBlock` span
- For each trace method, is there a **trace depth** / **trace time** limit?
3b. Per-client rate limit
- Is there rate limiting by IP, by API key, by connection?
- What's the default burst + sustained rate?
3c. Total concurrent request cap
- Maximum simultaneous HTTP requests
- Maximum simultaneous WebSocket connections
- Maximum subscriptions per WebSocket connection
Tag: `[RPC-RATE:{scope}:{limit-or-unbounded}]`
3d. Outbound HTTP client timeout audit
The skill historically focused on incoming RPC. Audit OUTBOUND HTTP clients used by the node for peer fetches and inter-node API calls — they are a symmetric DoS vector.
**Check**:
- For each `reqwest::Client::builder()`, `awc::Client::builder()`, `hyper::Client::builder()`, and `http::Client::new()` site, verify `.timeout(...)` AND `.connect_timeout(...)` are set explicitly. The library defaults are NO timeout.
- For each `tokio::time::timeout` wrapper around an outbound call, verify the timeout bound is a constant (not derived from peer-controlled input).
- For Go: `http.Client{}` literal (zero value) has NO timeout — flag every site that constructs `http.Client` without setting `Timeout`. Also check `Transport.DialContext` for connect-timeout.
- **Fail mode**: an unresponsive remote peer can hang the calling task forever, blocking critical bootstrap, sync, or peer-handshake routines. Combined with peer-list flooding, an attacker can stall every honest node's startup.
Tag: `[RPC-CLIENT-NO-TIMEOUT:{file}:{line}]`
3e. JSON number precision for u64 fields
JavaScript clients (and many other dynamic languages) cannot represent integers above `2^53 - 1` precisely. L1 nodes that serialize `u64` fields as JSON numbers (not strings) will silently corrupt block heights, balances, gas values, and timestamps when consumed by JS clients.
**Check**:
- For each `serde::Serialize` impl on a struct exposed via RPC, find every `u64` / `u128` / `i64` / `i128` field
- Verify the serializer either uses `#[serde(with = "as_string")]` or a similar string-coercion attribute, OR that the field's max value provably fits in 53 bits
- For Go: check `json.Marshal` of `uint64` fields and `*hexutil.Uint64` wrappers (the latter is correct, the former is not)
Tag: `[RPC-JSON-PRECISION:{type}.{field}]`
3f. Success Return vs Side-Effect Completion
For each RPC handler, peer HTTP endpoint, and outbound API client wrapper that returns `Ok(())`, HTTP 2xx, a success JSON body, or increments peer score,
Autonomous Web3 security auditor for Claude Code and OpenAI Codex CLI. Orchestrates 18-100 AI agents across 40+ phases to produce audit reports with verified PoC exploits — for smart contracts and L1 node-client infrastructure.
Repo: PlamenTSV/plamen
Other skills on plamen.
- /ability-analysis
Trigger Pattern Always (Aptos Move) - foundational security check - Inject Into Breadth agents, depth agents
Open skill - /bit-shift-safety
Trigger Pattern Always (Aptos Move) - Move VM aborts on shift = bit width - Inject Into Breadth agents, depth-edge-case
Open skill - /centralization-risk
Trigger Protocol has privileged roles (admin, operator, governance, resource account owner) - Covers Single points of failure, privilege escalation, external governance dependen...
Open skill - /cross-chain-timing
Trigger Pattern wormhole|layerzero|ccip|bridge|cross_chain|vaa|guardian|emitter|relay|remote_chain|payload|nonce.sequence - Inject Into Breadth agents, depth-external
Open skill - /dependency-audit
Trigger EXTERNAL_LIB flag detected (protocol uses third-party Move dependencies) - Used by Breadth agents, depth-external
Open skill - /economic-design-audit
Trigger Pattern MONETARY_PARAMETER flag (required) - Inject Into Breadth agents (merged via M4 hierarchy)
Open skill

