gRPC Reflection and Authorization Bugs Worth Reporting
gRPC feels opaque until reflection is enabled. With server reflection, a client can list services and descriptors the same way Swagger lists HTTP routes. Production reflection is a smell. Missing authorization on a reflected Admin method is the actual payout.
I reach for grpcurl early, then switch to a small client when I need custom metadata.
Map the surface
If reflection answers, list services and describe the interesting ones. Look for names containing Admin, Internal, Debug, Impersonate, or Export. Note which methods are unary versus streaming; streaming endpoints sometimes enforce auth only on the initial message.
No reflection? You still have protobuf crumbs in mobile apps, web bundles talking gRPC-Web, leaked .proto files in public repos, and error strings that echo full method paths. Capture traffic from a legitimate client and replay with altered metadata. Call health once anyway—a plaintext reflection port beside a locked TLS frontend shows up more than people expect.
Authorization is per method, not per port
TLS on the load balancer does not mean every RPC checks the caller. Send the same method with:
- No authorization metadata
- A low-privilege bearer token
- A second tenant's identity
- Metadata the gateway injects (
x-user-id,x-org-id) forged from the client
The server must ignore client-supplied identity headers unless they are cryptographically bound. I've seen gateways verify JWT for HTTP/JSON transcoding while the native gRPC listener trusts user-id metadata alone.
Prove with two owned accounts. Call GetWidget with B's ID while authenticated as A. Call an admin-only method with a member token. Prefer RPCs that return a canary string you set earlier over anything destructive.
Streaming needs an extra check: does authorization run only on stream open, or on every message? Send a permitted first message, then a privileged follow-up on the same stream when the API shape allows it.
Reflection as finding versus amplifier
Some programs score open reflection on an internal-only service differently from reflection on a customer API. Be honest. If anonymous reflection only reveals public Health/Check, say so. If it reveals Billing.IssueRefund and that method accepts a member token, lead with the authz failure and treat reflection as how you discovered the method name.
gRPC-Web and Envoy transcoding add another angle: a method blocked on one listener may be reachable on another path with different auth middleware. Compare both. Browser-reachable gRPC-Web often inherits cookie auth while native clients use bearer metadata—test each style.
What a usable report contains
Show the reflection listing (trimmed), the full method path, request metadata with secrets redacted, and the unauthorized response body containing your canary. Mention client tools and whether plaintext or TLS was used in scope.
Recommend disabling reflection in production, requiring auth before descriptor access if reflection must stay, enforcing authorization in every service implementation, rejecting spoofable identity metadata, and aligning gRPC and HTTP transcoding policies. Short docs on reflection explain the feature; your writeup should explain the trust miss. If a method requires mutual TLS in production docs but your scoped lab accepts plain tokens, say so. Prerequisites matter as much as the missing check.