highJul 21, 2026

gRPC-Go: xDS RBAC Authorization Bypass, HTTP/2 Rapid Reset DoS, and NOT-Rule Panic

Rohit Hatagale
AI Security Researcher, SecureLayer7

Three bugs in gRPC-Go's xDS RBAC engine and HTTP/2 transport let attackers bypass access controls, crash the server with a crafted policy, or exhaust CPU with a stream-reset flood.

Packagegoogle.golang.org/grpc
Ecosystemgo
Affected< 1.82.1
Fixed in1.82.1

The problem

grpc-go versions before 1.82.1 have three related security bugs.

First, the xDS RBAC policy builder silently ignored unsupported `permission` and `principal` rule types (specifically `Metadata` and `RequestedServerName`), treating them as no-ops. Any policy that relied on those matchers for access control was effectively removed, causing fail-open authorization (CVSS 8.2).

Second, the HTTP/2 transport's Rapid Reset mitigation only counted control frames that were actually written to the wire. A flood of `HEADERS` + `RST_STREAM` pairs queued items in the control buffer without hitting that threshold, bypassing backpressure and causing sustained high CPU (CVSS 7.5).

Third, when the RBAC policy translator recursively built matchers for a `NOT` rule wrapping an unsupported field (such as `SourcedMetadata`), the recursive call returned a nil matcher. The engine then dereferenced that nil pointer on the next incoming request, panicking and crashing the server process (CVSS 5.9).

Proof of concept

A working proof-of-concept for this issue in google.golang.org/grpc, with the exact payload below.

bash
# 1. xDS RBAC Authorization Bypass (fail-open)
# Push this LDS/RDS policy via a malicious or misconfigured xDS management server.
# The sole permission rule uses Metadata, which grpc-go < 1.82.1 silently drops.
# Result: the rule evaluates as if no restriction exists, granting access to all callers.
permission:
  metadata:
    filter: "envoy.filters.http.rbac"
    path:
      - key: "role"
    value:
      string_match:
        exact: "admin"

# 2. NOT-rule panic (server crash)
# Push this RBAC policy. The NOT wrapper around the unsupported SourcedMetadata
# field causes the recursive matcher builder to return nil.
# grpc-go dereferences the nil matcher on the first authorized request -> panic.
permission:
  not_rule:
    requested_server_name:
      exact: "ignored-field"

# 3. HTTP/2 Rapid Reset DoS (high CPU)
# Send a rapid stream-creation + immediate reset flood on the same connection.
# Each HEADERS frame opens a new stream; the matching RST_STREAM cancels it
# before a response is written, so grpc-go < 1.82.1 never counts it against
# the control-frame threshold and never blocks the reader goroutine.
# Example using h2load (or any raw HTTP/2 client):
#   h2load -n 100000 -c 1 -m 1000 --header=':method: POST' \
#     --header=':path: /Service/Method' \
#     --header='content-type: application/grpc' \
#     https://target:443 &
# followed immediately by RST_STREAM (error_code=CANCEL) for each stream.

For the RBAC bypass: the policy-translation switch statement in `internal/xds/rbac` had no `case` for `Metadata` or `RequestedServerName` matchers, so they fell through to a no-op return. The patch adds explicit `case` handlers that return an error, causing the entire policy to be rejected rather than silently weakened (CWE-285 / CWE-703).

For the NOT-rule panic: the recursive `buildMatcher` call for the inner rule of a `NOT` node could return `(nil, nil)` for unknown field types. The caller did not check for a nil matcher before wrapping it in a `NotMatcher`, so authorization of any real request immediately dereferenced the nil pointer (CWE-248).

The fix propagates an error upward instead.

For the HTTP/2 Rapid Reset DoS: the pre-patch threshold check only fired for frames that produced outbound control frames (RST_STREAM from the server, SETTINGS ACKs). Client-initiated RST_STREAM resets that abort streams mid-flight never incremented the counter, so a high-frequency HEADERS+RST_STREAM loop bypassed backpressure entirely (CWE-400).

The fix counts client-aborted streams toward the same threshold.

The fix

Upgrade `google.golang.org/grpc` to **v1.82.1** or later. The fix is in commit `4ea465d4ab98013f72a142fe0fc89c19770b2935` (PR #9236). If immediate upgrade is not possible: (a) ensure your xDS management server never pushes RBAC policies containing `Metadata`, `RequestedServerName`, or `NOT` rules around unsupported fields; (b) front the gRPC server with a reverse proxy (e.g., Envoy) configured with strict `max_concurrent_streams` and RST_STREAM rate limits.

Reporter not attributed.

References: [1][2][3][4][5]

Related research