REST and GraphQL are no longer the only targets in web application security. High-performance microservices, especially in the fintech space, rely heavily on Remote Procedure Call (RPC) frameworks. gRPC provides blazing-fast, binary-encoded communication between backend microservices. tRPC provides end-to-end type safety between React frontends and Node.js backends. Both protocols obscure the underlying network traffic, leading developers to a false sense of security. Attackers simply adapt their tooling to intercept, decode, and manipulate the binary payloads.
gRPC: Server reflection exposure
gRPC relies on Protobuf (Protocol Buffers) schemas to define endpoints. The client and server must share this `.proto` definition to communicate. In development environments, engineers often enable Server Reflection. This feature acts as an internal API documentation endpoint, allowing debugging tools to dynamically discover all available services, methods, and expected payload structures.
In production, developers routinely forget to disable server reflection. If reflection remains active, we use tools like grpcurl or ghz to connect to the exposed port and dump the entire server schema. This hands us a complete map of your internal infrastructure, revealing hidden administrative methods and internal 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 pipelines. In Java, Go, or Node.js gRPC servers, wrap the reflection registration block in an environment variable check (`if (ENV === 'development')`). Attackers cannot attack methods they cannot easily map, forcing them into blind enumeration which triggers rate limiters and intrusion detection alerts.
Bypassing gRPC Authentication Interceptors
gRPC handles authentication via Interceptors. These are middleware functions that execute before the actual procedure call. Interceptors parse incoming metadata (the gRPC equivalent of HTTP headers), extract the JWT or session token, validate it, and attach the user identity to the context.
We regularly find vulnerabilities where the interceptor fails open. If the JWT signature is invalid, or if the token has expired, poorly written interceptors log the error but still pass the request through to the service layer. We test this by crafting gRPC payloads with expired tokens, modified signatures, and completely absent authorization metadata. If the service method assumes the interceptor already validated the token, the unauthenticated request succeeds.
gRPC reflection exposed in transaction ledger
During a microservices audit, production reflection exposed an administrative method named ForceManualReconciliation. The gRPC interceptor checked if an authorization token was present, but it did not verify if the user held the "Admin" role. Because the method was exposed via reflection, we invoked it using a standard user token, forcing the ledger to prematurely reconcile millions in pending transactions.
tRPC: Input validation bypasses
tRPC provides automatic type safety between TypeScript frontends and backends. This is brilliant for developer experience, but TypeScript types only exist at compile time. They are stripped out entirely during the build process. At runtime, the tRPC server receives raw JSON over HTTP. If a developer defines a tRPC query without explicitly attaching a runtime validation schema, the input accepts arbitrary, untyped JSON payloads.
// VULNERABLE tRPC query: no runtime validation schema defined
export const paymentRouter = router({
transferFunds: publicProcedure
.query(async ({ input }) => {
// Flaw: input is treated as "any" at runtime.
// Attackers can pass objects instead of strings to bypass logic.
return await db.transfer(input.source, input.dest, input.amount);
})
}); When we find procedures like this, we intercept the tRPC batch request in Burp Suite. We replace standard string inputs with NoSQL injection payloads (like { "$ne": null }) or massive arrays designed to trigger Denial of Service. The server crashes or leaks data because the expected types were not enforced at runtime.
The Fix: Always declare an explicit validator schema (Zod, Yup, or Superstruct) on all tRPC procedures to enforce strict runtime type checking. If the payload does not match the schema perfectly, tRPC will automatically reject the request with a `BAD_REQUEST` error before it hits your resolver function.
// 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().max(1000000)
}))
.mutation(async ({ input, ctx }) => {
// Safe: input is strictly validated by Zod at runtime.
// ctx.user is securely authenticated via protectedProcedure.
return await db.transfer(ctx.user.id, input.dest, input.amount);
})
}); tRPC Batch Request Exploitation
tRPC optimizes network performance by batching multiple queries into a single HTTP request. A React client might need a user profile, recent transactions, and notification settings simultaneously. tRPC sends these as one URL-encoded request to the backend, and the backend resolves them concurrently.
We exploit this batching feature to execute Application-Layer DDoS attacks. By crafting a single tRPC HTTP request containing 5,000 parallel calls to a computationally expensive query (like `generatePDFReport`), we can completely exhaust the Node.js event loop and crash the backend server. To defend against batch abuse, you must enforce strict rate limiting at the infrastructure layer (WAF/API Gateway) and limit the maximum number of batch queries permitted per single HTTP request inside your tRPC router configuration.
Exploiting gRPC Server-Side Streaming
Unlike traditional REST APIs that return a single JSON payload, gRPC supports server-side streaming. The client sends one request, and the server opens a persistent connection, streaming multiple responses back over time. This is commonly used in fintech for real-time market data, order book updates, or live transaction status streams.
This persistent connection is a massive target for Denial of Service (DoS) attacks. If an attacker initiates thousands of concurrent server-side streams without properly consuming the data on the client side, the server's TCP buffers fill up. The server must hold the connection state, allocating memory for each stream. If the server lacks strict connection limits (MaxConcurrentStreams) and idle timeouts (Keepalive), the attacker can easily exhaust the server's memory, crashing the entire microservice and bringing down the trading platform.
Bidirectional Deadlocks in Financial Workflows
gRPC also supports bidirectional streaming, where both the client and server send continuous streams of data over a single connection. This is often used for high-frequency trading execution algorithms or live fraud detection feeds. The complexity of bidirectional state management introduces severe logical vulnerabilities.
During an audit, we test for deadlock conditions. We open a bidirectional stream, send an initial execution request, and then intentionally halt our client-side stream. We refuse to read the server's responses and refuse to close the connection. If the backend code attempts to push data to our blocked stream synchronously, the entire backend worker thread halts. By orchestrating this attack across a few dozen connections, we can completely freeze the execution engine. Robust gRPC services must implement asynchronous, non-blocking writes and aggressive timeout drops for uncooperative clients.
Building scalable microservices with gRPC or tRPC? Schedule a protocol-specific security audit.
Book a gRPC / tRPC PentestFrequently 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