The gRPC Reflection API as a Reconnaissance Tool

gRPC Server Reflection is an optional service that, when registered, allows any gRPC client to query the server for its full service and message schema. It is the mechanism behind tools like grpcurl, grpc-client-cli, and Postman's gRPC support β€” you point the tool at a server with Reflection enabled and get an interactive client without needing the .proto files.

Reflection is intended for development and debugging environments. In many organisations, it is enabled by default in all environments including production because it was added to the boilerplate and no one removed it. An attacker with network access to an internal service mesh β€” through a compromised service, a lateral movement path, or a misconfigured network boundary β€” can use Reflection to enumerate every gRPC service, every RPC method, every message field type and name across the entire internal architecture.

Reflection is a schema dump: Using grpcurl -plaintext internal-service:50051 describe with Reflection enabled returns a complete description of every service and message type. This includes internal method names, data model field names, and type structures that reveal business logic and internal data organisation β€” high-value intelligence for crafting targeted attacks.

# With Reflection enabled, any caller can enumerate your entire API: grpcurl -plaintext payment-service:50051 list # Returns: payments.PaymentService, grpc.reflection.v1alpha.ServerReflection grpcurl -plaintext payment-service:50051 describe payments.PaymentService # Returns full schema: CreatePayment, RefundPayment, GetTransaction methods # Plus all message types with field names, types, and field numbers # Disable Reflection in production β€” only register it in dev/staging: # Go: remove reflection.Register(s) from production server setup # Java: remove ProtoReflectionService from ServerBuilder

Protocol Buffers Deserialization Risks

Protocol Buffers are generally safer than JSON or XML from a deserialization perspective β€” the binary encoding is type-safe and does not support arbitrary object construction. However, proto3's use of Any type β€” which can contain any message type, encoded as a type URL and serialised bytes β€” reintroduces deserialization risks. Services that accept google.protobuf.Any fields and deserialise them without validating the type URL may be vulnerable to deserialization attacks if the registered type registry contains types with security implications.

Unknown field handling is another risk surface: by default, proto parsers preserve unknown fields (fields present in the message but not in the current schema version). If a service adds a field to its proto schema that has security implications β€” an admin flag, an internal routing field β€” older clients that do not know about the field may pass it through from user input, creating unexpected privilege escalation paths.

The proto3 optional and oneof interaction: Proto3's field presence semantics, combined with the oneof construct, can create edge cases where security-relevant fields have unexpected default values when not present in a message. Carefully validate security-critical field presence explicitly rather than relying on default-value semantics.

Authentication Interceptor Gaps

gRPC authentication is typically implemented as an interceptor (middleware) that validates credentials from gRPC metadata before passing the request to the handler. Unlike HTTP middlewares in frameworks with established security patterns, gRPC interceptors are custom code with no standard implementation. Common implementation errors include: not validating token expiry, not checking the token's audience claim against the current service, applying the interceptor to most RPCs but accidentally excluding sensitive ones, and not propagating the authenticated principal to the RPC handler (so the handler cannot perform authorization checks).

In multi-service environments, services that only communicate with other trusted internal services may disable authentication entirely β€” "this is an internal service, it's not accessible from outside." Once an attacker gains access to any service in the mesh, all unauthenticated internal services become immediately callable with full access.

Schema Evolution and Field Number Reuse

Protocol Buffers field numbers are the canonical identity of a field β€” the field name can change, but the number must remain stable for backwards compatibility. Reusing a field number after removing a field is a well-known protobuf anti-pattern, but it is also a security risk: if field number 5 was previously a boolean is_admin flag and is now reused for a string display_name, old clients that still set field 5 to indicate admin status will now be setting the display name, potentially bypassing admin checks in services that have been updated but are receiving traffic from old clients.

gRPC-Gateway HTTP/JSON Exposure

gRPC-Gateway is a proxy that translates HTTP/JSON requests to gRPC calls, allowing REST clients to interact with gRPC services. It is commonly used to expose internal gRPC services to web frontends or external partners. The HTTP/JSON translation layer introduces its own attack surface: URL parameter injection, JSON parsing differences between the gateway and the gRPC service, and the exposure of internal service schemas through the generated Swagger/OpenAPI documentation that the gateway produces.

gRPC-Gateway authentication: The gateway must enforce the same authentication and authorization as direct gRPC access. A gateway that allows unauthenticated HTTP/JSON access to services that are intended to require mTLS for direct gRPC access defeats the service mesh's security model.

gRPC Security Hardening

  1. Disable gRPC Reflection in production: Remove Reflection service registration from all production deployments. Enable it only in development and staging environments. Use build tags or environment configuration to prevent accidental inclusion in production builds.
  2. Validate token audience and expiry in every interceptor: Ensure every gRPC service's authentication interceptor validates token expiry, the token's audience claim matches the service's expected audience, and the token signature is verified. Use established JWT libraries rather than custom validation code.
  3. Apply authentication interceptors to all RPCs without exception: Register authentication interceptors as server-wide interceptors, not per-handler. Add an integration test that verifies every RPC method in the service rejects unauthenticated calls.
  4. Reserve and never reuse field numbers for removed fields: When removing a proto field, add it to the reserved declaration in the proto file. This prevents accidental reuse and documents the field's former existence for security reviewers.
  5. Restrict google.protobuf.Any type URLs: If your service must accept Any fields, maintain an explicit allowlist of accepted type URLs and reject messages containing unrecognised types before deserialisation.
  6. Audit gRPC-Gateway HTTP routes for unauthenticated access: Review every gRPC-Gateway route annotation to ensure authentication is required for all exposed methods. Treat the HTTP/JSON surface as an external API surface requiring the same security controls as direct external API endpoints.