gRPC: Server reflection exposure

gRPC relies on Protobuf (Protocol Buffers) schemas to define endpoints. In production, developers should disable server reflection to keep these schemas private. If reflection is enabled, we use tools like grpcurl or ghz to dump the server schema, revealing the exact names of your internal methods and database keys:

# Query gRPC reflection to list all available services
grpcurl -plaintext your-api.simpalabs.com:50051 list
# Dump details of a specific service schema
grpcurl -plaintext your-api.simpalabs.com:50051 describe PaymentService

The Fix: Disable the reflection service in your production build. In Java/Go/Node.js, only register the reflection service if the build environment is set to development.

tRPC: Input validation bypasses

tRPC provides automatic type safety between TypeScript frontends and backends. However, TypeScript types only exist at compile time. At runtime, input parameters must be validated using Zod, Superstruct, or custom validators. If a developer defines a tRPC query without a validation schema, the input accepts arbitrary JSON payloads:

// VULNERABLE tRPC query: no validation schema defined
export const paymentRouter = router({
  transferFunds: publicProcedure
    .query(async ({ input }) => {
      // Flaw: input is treated as "any" and can be tampered with
      return await db.transfer(input.source, input.dest, input.amount);
    })
});

The Fix: Always declare an explicit validator schema on all tRPC procedures to enforce strict runtime type checking:

// SECURE tRPC query pattern
import { z } from 'zod';

export const paymentRouter = router({
  transferFunds: protectedProcedure
    .input(z.object({
      source: z.string().uuid(),
      dest: z.string().uuid(),
      amount: z.number().positive()
    }))
    .mutation(async ({ input, ctx }) => {
      // Safe: input is validated, ctx.user is authenticated
      return await db.transfer(ctx.user.id, input.dest, input.amount);
    })
});

gRPC metadata and interceptor auditing

gRPC handles authentication using middleware interceptors that check request metadata headers. If the interceptors do not handle errors correctly, or if they fail to validate JWT claims (like signature validation or token expiry), attackers can craft fake authorization headers to bypass authentication entirely. We audit your interceptor code configurations to ensure all metadata is validated before processing the request.

Example finding

gRPC reflection exposed in transaction ledger

During an audit of a microservices ledger backend running gRPC, we queried the reflection endpoint and dumped the protobuf schema. The schema revealed an unlisted administrative service call: ForceManualReconciliation(amount, walletId). Because the interceptor for this method was not configured to validate roles, we invoked the method and successfully credited arbitrary funds to a test account. Fix priority: critical. Remediated by disabling reflection and adding role checks to the interceptor.

Building scalable systems with gRPC or tRPC? Schedule a protocol security audit.

Book a gRPC / tRPC Pentest

Frequently asked questions

Why is gRPC reflection a security risk?

gRPC server reflection allows clients to query the server for its service definitions and schemas. Leaving reflection active in production exposes your entire API structure, method names, and message payloads to attackers.

How does tRPC enforce runtime type safety?

tRPC validates inputs using schemas (like Zod or Superstruct) before invoking resolver functions. This prevents parameter manipulation and type coercion bypasses.

What is a metadata injection attack in gRPC?

Metadata injection occurs when an attacker manipulates standard gRPC request headers (like authorization metadata) to spoof identity fields or bypass interceptor checks.

Related reading

Blog: API data leaks · Rate limiting payment APIs · Next.js Server Action audits

Services: API security testing · Secure architecture review