Skip to content

security: harden gRPC server — disable by default, bind to localhost#2078

Merged
manav2401 merged 3 commits into0xPolygon:developfrom
harshinsecurity:fix/grpc-security-hardening
Apr 7, 2026
Merged

security: harden gRPC server — disable by default, bind to localhost#2078
manav2401 merged 3 commits into0xPolygon:developfrom
harshinsecurity:fix/grpc-security-hardening

Conversation

@harshinsecurity
Copy link
Copy Markdown
Contributor

Summary

The gRPC server in Bor currently starts unconditionally on all network interfaces (0.0.0.0:3131) with:

  • No authentication or authorization
  • No TLS encryption
  • No CLI flag to disable it
  • Server reflection enabled (allows service/method discovery)

This is inconsistent with HTTP-RPC and WS-RPC, which are disabled by default and bind to localhost. Any node with port 3131 reachable from the network exposes sensitive RPCs without credentials.

Impact

An unauthenticated attacker with network access to port 3131 can:

gRPC Method Impact
ChainSetHead(number) Force chain reorg to arbitrary block — causes the node to re-sync and lose recent state
PeersAdd(enode) / PeersRemove(enode) Manipulate the peer table — prerequisite for eclipse attacks
PeersList / PeersStatus Enumerate all connected peers, their enodes, and network topology
StatusBorStatus Leak sync state, current/highest block, network ID — reconnaissance
DebugPprof Extract CPU/memory/goroutine profiles — leak internal state

The most significant risk is Denial of Service: repeatedly calling ChainSetHead(0) forces the node to re-sync from genesis, effectively taking it offline. While this cannot steal funds or halt the Polygon network (requires multiple validators), it can degrade individual node availability.

CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H → 8.6 High

Changes

File Change
internal/cli/server/config.go Add Enabled bool field to GRPCConfig; default to false; change bind address from :3131 to 127.0.0.1:3131
internal/cli/server/server.go Guard gRPC startup with config.GRPC.Enabled check; fix GetGrpcAddr() to use net.SplitHostPort instead of brittle [1:] string slice
internal/cli/server/flags.go Add --grpc.enabled CLI boolean flag
internal/cli/server/helper.go Set config.GRPC.Enabled = true in test mock so existing tests pass
docs/cli/server.md Document new --grpc.enabled flag and updated default address

Behavior Change

Before After
gRPC server Always starts Only starts with --grpc.enabled
Default bind 0.0.0.0:3131 (all interfaces) 127.0.0.1:3131 (localhost only)
Opt-in flag None --grpc.enabled

This is a breaking change for anyone relying on the gRPC server being on by default. Operators who need gRPC must now explicitly pass --grpc.enabled. This is the same pattern used by --http and --ws and is the secure default.

How to Test

# Default: gRPC should NOT start
./build/bin/bor server --datadir /tmp/bor-test
# Verify port 3131 is NOT listening

# Explicit enable: gRPC starts on localhost
./build/bin/bor server --datadir /tmp/bor-test --grpc.enabled
# Verify port 3131 IS listening on 127.0.0.1 only

# Existing tests pass
go test ./internal/cli/server/...

For questions or discussion, feel free to reach out: hi@harshinsecurity.in

- Add 'Enabled' field to GRPCConfig (default: false), requiring
  explicit --grpc.enabled flag to start the gRPC server
- Change default bind address from 0.0.0.0:3131 to 127.0.0.1:3131
- Guard gRPC server startup with Enabled check in NewServer()
- Add --grpc.enabled CLI flag in flags.go
- Fix GetGrpcAddr() to use net.SplitHostPort instead of string slice
- Update docs and test helper for compatibility

The gRPC server currently starts unconditionally on all interfaces
(0.0.0.0:3131) with no authentication, no TLS, and no way to disable
it. This is inconsistent with HTTP-RPC and WS-RPC which are disabled
by default. An attacker with network access can invoke sensitive RPCs
including ChainSetHead (reorg), PeersAdd/Remove (eclipse attacks),
and StatusBorStatus (reconnaissance) without credentials.
@sonarqubecloud
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown

This PR is stale because it has been open 21 days with no activity. Remove stale label or comment or this will be closed in 14 days.

Copy link
Copy Markdown
Member

@manav2401 manav2401 left a comment

Choose a reason for hiding this comment

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

Hi, thanks for the PR. Overall it makes sense but it changes the default behavior for node operators.

What I'd suggest is that

  • Have a flag called grpc.disabled to disable it instead and keep it enabled by default.
  • Default addr is changed to "127.0.0.1:3131" which anyways prevents public exposure to the cli.
  • As this is only used for cli, maybe worth adding a log suggesting that grpc is disabled.
  • Updating tests according to the new changes.
  • Ensure GetGrpcAddr has nil server checks and returns full "host:port" instead of just port. It's only used in tests which is easy to change.

Let me know if you have any concerns or suggestions. Thanks!

@manav2401
Copy link
Copy Markdown
Member

manav2401 commented Apr 3, 2026

I've made some simplifications to get this merged. IMO, binding the grpc server to "127.0.0.1" should pretty much suffice and solve the core issue. I think changing the flag can be friction point for node operators relying on cli commands (served by grpc).

@pratikspatil024
Copy link
Copy Markdown
Member

A config related unit test is failing

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 6, 2026

@manav2401 manav2401 merged commit 1aeb36e into 0xPolygon:develop Apr 7, 2026
1 check passed
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.

4 participants