Auditing gRPC service authorization: the method the interceptor does not cover
A gRPC service usually centralizes authorization in an interceptor, and the bug is a method the
interceptor does not actually cover. The classic shape is an auth interceptor installed on the unary chain
while the streaming chain runs without it, so a streaming RPC is callable with no credentials. You audit
it by resolving which interceptors are registered and which methods and stream types they actually gate,
then checking each method the check assumes is covered. The other half is the channel: a plaintext or
insecure channel, metadata trusted without verification, missing message and recursion limits, and a
transcoding gateway that fronts the service without the same auth filter. Certificate-validation mechanics
belong to the transport skill; this skill owns whether a reachable method is authorized.
When to use
- You are reviewing a gRPC server's service registration, interceptor chain, and method handlers.
- The service enforces authorization in an interceptor, or is fronted by a transcoding gateway.
- You want to know whether a caller can reach a method without the authorization the service assumes.
Scope check
Audit only services you own or are authorized to assess, and call a method only against an endpoint in
scope, an unauthorized RPC drives real service state. Adjudicate on the registration and the handlers. If
you can't name the authorization, stop.
The loop
Resolve the interceptor coverage and channel credentials first. Enumerate the registered
interceptors and determine, for each, whether it applies to the unary chain, the streaming chain, or
both, and read the channel setup for whether it requires transport credentials or runs insecure. A
method's authorization lives in whatever interceptor actually wraps it, so establish that coverage map
before judging any individual handler.
Check the unary-versus-streaming gap. Look for an authorization interceptor installed on the unary
interceptor while the streaming interceptor chain omits it (or the reverse), so a whole class of RPCs
runs without the check that the unary methods enforce.
Check per-method authorization. Look for a method reachable at the wrong privilege: an interceptor
that authenticates a caller but does not check that this caller may call this method, letting a
low-privilege identity invoke a high-privilege RPC or read another tenant's object.
Check reflection and channel trust. Look for server reflection registered in production, exposing
the full method set to any caller for enumeration, and for a channel using insecure or plaintext
credentials while trusting caller-supplied metadata (an identity header, a tenant id) without verifying
it.
Check decode limits and the transcoding gateway. Look for a missing maximum-receive-message-size or
recursion-depth bound, so a deeply nested or oversized message exhausts the decoder, and for a
transcoding gateway (a REST-to-gRPC front) whose entry does not apply the same authentication and
authorization filter as a native gRPC call, opening a second unguarded path to the method.
Confirm and record. Confirm by calling the method along the suspect path (a streaming RPC with no
metadata, a low-privilege identity against a privileged method, the gateway route) and showing it acts
without the authorization the service assumes. Kill the lead if a server interceptor enforces auth for
both the unary and streaming chains so per-method handlers legitimately omit their own checks, if
reflection is registered only behind an internal-only listener or a development build, if a service mesh
enforces peer identity with mutual authentication so trusting the mesh identity is intended, if the
maximum-message-size and depth limits are set, if metadata is re-validated by an interceptor rather than
blindly trusted, or if the transcoding gateway applies the same auth filter with no REST-versus-gRPC
asymmetry. Record the method, the path that reaches it unguarded, and the authorization it skips.
Where gRPC authorization leaks
- Coverage is per-chain, not per-service. An interceptor on the unary chain does not gate streaming
methods; resolve which chains each interceptor wraps before assuming a method is covered.
- Authentication is not authorization. An interceptor that proves who the caller is, but not that they
may call this method, leaves a per-method gap that reads or writes across a privilege or tenant boundary.
- Reflection in production is free enumeration. Server reflection hands any caller the whole API surface;
behind an internal listener or a dev build it is not exposed, in production it is a map for the attacker.
- An insecure channel makes metadata a lie. Trusting a caller-supplied identity or tenant header on a
plaintext or unauthenticated channel lets the caller assert any identity; the metadata has to be verified.
- The transcoding gateway is a second door. A REST-to-gRPC front that skips the gRPC auth filter reaches
the method by another path; the asymmetry, not the native call, is the bug.
Worked example (a confirm and a kill)
Confirm. A server registers an auth interceptor with the unary interceptor option only; the streaming
interceptor chain has none. A server-streaming RPC that returns account records is callable with no
metadata and streams the records to an unauthenticated caller. Confirmed a streaming method reachable
without authorization because the interceptor covers only the unary chain, high rising to critical
for cross-tenant data, remediation = install the auth interceptor on the streaming chain as well (or a
single interceptor covering both) and re-check per-method privilege.
Kill. The same server installs the auth interceptor on both the unary and streaming chains, verifies
the caller identity and its per-method privilege, sets the maximum-message-size and depth limits, and
registers reflection only on an internal loopback listener. The streaming RPC rejects the metadata-less
call. Killed, kill_reason = "a single auth interceptor covers unary and streaming, per-method
privilege is checked, and reflection is not exposed; no uncovered method or stream."
Rationalizations to reject
- "The interceptor handles auth." -> On which chain? An interceptor on the unary option does not wrap
streaming methods; confirm the coverage includes every chain and stream type.
- "The caller is authenticated." -> Authenticated to do what? Without a per-method privilege check, a
low-privilege identity can call a high-privilege method.
- "Reflection is convenient." -> In production it enumerates the whole API for an attacker; register it
only behind an internal listener or a development build.
- "We read the tenant from metadata." -> On what channel, verified how? On an insecure channel, unverified
metadata is attacker-asserted; verify the identity, do not trust the header.
- "The gateway is just a proxy." -> Does it apply the same auth filter as the native call? A transcoding
front that skips it is a second unguarded path to the method.
Executing this in practice
You need the service and interceptor registration (which interceptors, which chains), each method handler
and whether it relies on the interceptor for authorization, the channel credential setup and any metadata
it trusts, the message-size and recursion-depth limits, the reflection registration and its listener, and
any transcoding gateway and its filter. For each method, decide whether a caller can reach it without the
authorization the service assumes. Reading the registration tells you what is covered; calling the method
tells you what is reachable.
Related
auditing-websocket-connection-trust - the sibling wire-protocol audit where trust is established once at
the handshake; here the analogous gap is trust assumed in an interceptor that misses a method or stream.
auditing-tls-and-certificate-validation - owns the certificate-validation mechanics for the channel;
this skill covers whether the channel requires credentials and whether the method is authorized, not the
chain-and-hostname check.
hunting-broken-object-level-authorization - the object-authorization analogue; a gRPC method that reads
an object without checking the caller owns it has the same missing-scope shape at the method boundary.
- FINDING-SCHEMA.md - source = a caller with forged or absent metadata, sink = a
service method acting without an authorization check, evidence = the interceptor coverage map and the path
that reaches the method unguarded.
1---2name: auditing-grpc-service-authorization3description: Audit a gRPC service for a method a caller can reach without the authorization the service assumes an interceptor enforces, after the interceptor coverage and the channel credentials are resolved. Covers authorization installed on the unary interceptor while the streaming chain omits it, a per-method authorization gap reachable at the wrong privilege, server reflection enabled in production exposing the full API, a plaintext channel with metadata trusted unverified, an absent message-size or recursion-depth limit inviting decode denial of service, and a transcoding gateway that does not apply the same auth filter as native gRPC. Use when reviewing service and interceptor registration, method handlers, and channel setup, not the certificate-validation mechanics the transport skill owns. A caller with forged or absent metadata is the source, a service method acting without an authorization check is the sink, and an interceptor that does not cover the method or stream is the bug.4license: MIT5---67# Auditing gRPC service authorization: the method the interceptor does not cover89A gRPC service usually centralizes authorization in an interceptor, and the bug is a method the10interceptor does not actually cover. The classic shape is an auth interceptor installed on the unary chain11while the streaming chain runs without it, so a streaming RPC is callable with no credentials. You audit12it by resolving which interceptors are registered and which methods and stream types they actually gate,13then checking each method the check assumes is covered. The other half is the channel: a plaintext or14insecure channel, metadata trusted without verification, missing message and recursion limits, and a15transcoding gateway that fronts the service without the same auth filter. Certificate-validation mechanics16belong to the transport skill; this skill owns whether a reachable method is authorized.1718## When to use1920- You are reviewing a gRPC server's service registration, interceptor chain, and method handlers.21- The service enforces authorization in an interceptor, or is fronted by a transcoding gateway.22- You want to know whether a caller can reach a method without the authorization the service assumes.2324## Scope check2526Audit only services you own or are authorized to assess, and call a method only against an endpoint in27scope, an unauthorized RPC drives real service state. Adjudicate on the registration and the handlers. If28you can't name the authorization, stop.2930## The loop31321. **Resolve the interceptor coverage and channel credentials first.** Enumerate the registered33 interceptors and determine, for each, whether it applies to the unary chain, the streaming chain, or34 both, and read the channel setup for whether it requires transport credentials or runs insecure. A35 method's authorization lives in whatever interceptor actually wraps it, so establish that coverage map36 before judging any individual handler.37382. **Check the unary-versus-streaming gap.** Look for an authorization interceptor installed on the unary39 interceptor while the streaming interceptor chain omits it (or the reverse), so a whole class of RPCs40 runs without the check that the unary methods enforce.41423. **Check per-method authorization.** Look for a method reachable at the wrong privilege: an interceptor43 that authenticates a caller but does not check that this caller may call this method, letting a44 low-privilege identity invoke a high-privilege RPC or read another tenant's object.45464. **Check reflection and channel trust.** Look for server reflection registered in production, exposing47 the full method set to any caller for enumeration, and for a channel using insecure or plaintext48 credentials while trusting caller-supplied metadata (an identity header, a tenant id) without verifying49 it.50515. **Check decode limits and the transcoding gateway.** Look for a missing maximum-receive-message-size or52 recursion-depth bound, so a deeply nested or oversized message exhausts the decoder, and for a53 transcoding gateway (a REST-to-gRPC front) whose entry does not apply the same authentication and54 authorization filter as a native gRPC call, opening a second unguarded path to the method.55566. **Confirm and record.** Confirm by calling the method along the suspect path (a streaming RPC with no57 metadata, a low-privilege identity against a privileged method, the gateway route) and showing it acts58 without the authorization the service assumes. Kill the lead if a server interceptor enforces auth for59 both the unary and streaming chains so per-method handlers legitimately omit their own checks, if60 reflection is registered only behind an internal-only listener or a development build, if a service mesh61 enforces peer identity with mutual authentication so trusting the mesh identity is intended, if the62 maximum-message-size and depth limits are set, if metadata is re-validated by an interceptor rather than63 blindly trusted, or if the transcoding gateway applies the same auth filter with no REST-versus-gRPC64 asymmetry. Record the method, the path that reaches it unguarded, and the authorization it skips.6566## Where gRPC authorization leaks6768- **Coverage is per-chain, not per-service.** An interceptor on the unary chain does not gate streaming69 methods; resolve which chains each interceptor wraps before assuming a method is covered.70- **Authentication is not authorization.** An interceptor that proves who the caller is, but not that they71 may call this method, leaves a per-method gap that reads or writes across a privilege or tenant boundary.72- **Reflection in production is free enumeration.** Server reflection hands any caller the whole API surface;73 behind an internal listener or a dev build it is not exposed, in production it is a map for the attacker.74- **An insecure channel makes metadata a lie.** Trusting a caller-supplied identity or tenant header on a75 plaintext or unauthenticated channel lets the caller assert any identity; the metadata has to be verified.76- **The transcoding gateway is a second door.** A REST-to-gRPC front that skips the gRPC auth filter reaches77 the method by another path; the asymmetry, not the native call, is the bug.7879## Worked example (a confirm and a kill)8081> **Confirm.** A server registers an auth interceptor with the unary interceptor option only; the streaming82> interceptor chain has none. A server-streaming RPC that returns account records is callable with no83> metadata and streams the records to an unauthenticated caller. **Confirmed** a streaming method reachable84> without authorization because the interceptor covers only the unary chain, `high` rising to `critical`85> for cross-tenant data, remediation = install the auth interceptor on the streaming chain as well (or a86> single interceptor covering both) and re-check per-method privilege.87>88> **Kill.** The same server installs the auth interceptor on both the unary and streaming chains, verifies89> the caller identity and its per-method privilege, sets the maximum-message-size and depth limits, and90> registers reflection only on an internal loopback listener. The streaming RPC rejects the metadata-less91> call. **Killed**, `kill_reason` = "a single auth interceptor covers unary and streaming, per-method92> privilege is checked, and reflection is not exposed; no uncovered method or stream."9394## Rationalizations to reject9596- *"The interceptor handles auth."* -> On which chain? An interceptor on the unary option does not wrap97 streaming methods; confirm the coverage includes every chain and stream type.98- *"The caller is authenticated."* -> Authenticated to do what? Without a per-method privilege check, a99 low-privilege identity can call a high-privilege method.100- *"Reflection is convenient."* -> In production it enumerates the whole API for an attacker; register it101 only behind an internal listener or a development build.102- *"We read the tenant from metadata."* -> On what channel, verified how? On an insecure channel, unverified103 metadata is attacker-asserted; verify the identity, do not trust the header.104- *"The gateway is just a proxy."* -> Does it apply the same auth filter as the native call? A transcoding105 front that skips it is a second unguarded path to the method.106107## Executing this in practice108109You need the service and interceptor registration (which interceptors, which chains), each method handler110and whether it relies on the interceptor for authorization, the channel credential setup and any metadata111it trusts, the message-size and recursion-depth limits, the reflection registration and its listener, and112any transcoding gateway and its filter. For each method, decide whether a caller can reach it without the113authorization the service assumes. Reading the registration tells you what is covered; calling the method114tells you what is reachable.115116## Related117118- `auditing-websocket-connection-trust` - the sibling wire-protocol audit where trust is established once at119 the handshake; here the analogous gap is trust assumed in an interceptor that misses a method or stream.120- `auditing-tls-and-certificate-validation` - owns the certificate-validation mechanics for the channel;121 this skill covers whether the channel requires credentials and whether the method is authorized, not the122 chain-and-hostname check.123- `hunting-broken-object-level-authorization` - the object-authorization analogue; a gRPC method that reads124 an object without checking the caller owns it has the same missing-scope shape at the method boundary.125- [FINDING-SCHEMA.md](../../FINDING-SCHEMA.md) - source = a caller with forged or absent metadata, sink = a126 service method acting without an authorization check, evidence = the interceptor coverage map and the path127 that reaches the method unguarded.