gRPC add-on fails to handle native application/grpc bodies because VariantGrpc assumes Base64
Describe the bug:
The gRPC add-on 0.3.0 fails to decode and actively scan native gRPC traffic using Content-Type: application/grpc.
ZAP successfully proxies and captures native gRPC over HTTP/2. The captured requests are valid POST requests with:
Content-Type: application/grpc
and a non-empty binary gRPC body.
However, during Active Scan, VariantGrpc repeatedly fails with:
ERROR VariantGrpc - Parsing message body failed: Illegal base64 character 0
The captured request body is native binary gRPC rather than Base64. For example, the beginning of one captured body is:
00 00 00 00 c2 a8 ...
This corresponds to a normal uncompressed gRPC frame:
00 compression flag 00 00 00 c2 protobuf payload length (194 bytes) a8 ... beginning of protobuf payload
Looking at addOns/grpc/src/main/java/org/zaproxy/addon/grpc/internal/VariantGrpc.java, setMessage() currently does:
byte[] body = Base64.getDecoder().decode(msg.getRequestBody().getBytes());
byte[] payload = DecoderUtils.extractPayload(body);
This appears appropriate for application/grpc-web-text, but native application/grpc carries the framed gRPC message directly as binary bytes.
The existing VariantGrpcUnitTest tests also appear to create messages using:
Content-Type: application/grpc-web-text
with Base64-encoded request bodies.
As a result, native gRPC reaches VariantGrpc, but protobuf decoding fails before any fields can be exposed to Active Scan rules.
Steps to reproduce the behavior:
- Run ZAP with gRPC Support add-on 0.3.0.
Tested with: zaproxy/zap-weekly:20260826 gRPC Support: 0.3.0
- Proxy a native .NET gRPC client through ZAP.
- Send a normal unary, uncompressed gRPC request.
- Confirm in ZAP history/API that the captured request is similar to:
POST https://example-grpc-host/Some.Service/SomeMethod HTTP/2 Content-Type: application/grpc te: trailers
and has a non-empty request body.
- Inspect the raw captured request body. For example:
curl -sG "http://localhost:8090/JSON/core/view/message/"
--data-urlencode "id=<MESSAGE_ID>"
| jq -r '.message.requestBody'
| xxd -g1 -l 20
- The body begins with a normal native gRPC frame, for example:
00000000: 00 00 00 00 c2 a8 ...
- Run an Active Scan against a context/site containing this request.
- Inspect zap.log.
The following error is generated repeatedly:
ERROR VariantGrpc - Parsing message body failed: Illegal base64 character 0
Parameter-oriented Active Scan rules then generally report zero generated messages because no protobuf fields were successfully extracted.
For example:
SqlInjectionScanRule ... 0 message(s) sent CodeInjectionScanRule ... 0 message(s) sent CommandInjectionScanRule ... 0 message(s) sent ParameterTamperScanRule ... 0 message(s) sen
Expected behavior:
VariantGrpc should distinguish between gRPC transport representations.
For example:
application/grpc-web-text -> Base64 decode/encode application/grpc -> direct binary decode/encode application/grpc-web -> direct binary decode/encode
For native application/grpc, the add-on should:
- Read HttpBody.getBytes() directly.
- Remove/parse the 5-byte gRPC frame header.
- Decode the protobuf payload.
- Expose protobuf fields as Active Scan input parameters.
- Mutate the selected protobuf field.
- Re-encode the protobuf message.
- Rebuild the native binary gRPC frame.
- Send the mutated request while preserving Content-Type: application/grpc.
The existing ProtoBufMessageEncoder already appears to generate the normal 5-byte gRPC frame:
compression flag + 4-byte message length + protobuf payload
so the primary issue appears to be the unconditional Base64 handling in VariantGrpc.
Software Versions:
ZAP Docker image: zaproxy/zap-weekly:20260826 ZAP gRPC Support add-on: 0.3.0
Client: .NET gRPC client Transport: HTTP/2 Content-Type: application/grpc Request type tested: unary, uncompressed gRPC OS hosting Docker: Windows
The gRPC add-on reports:
installationStatus: INSTALLED version: 0.3.0 id: grpc
Screenshots:
No response
Errors from the zap.log file:
RROR VariantGrpc - Parsing message body failed: Illegal base64 character 0
INFO HostProcess - start host https://example-grpc-host | SqlInjectionScanRule strength MEDIUM threshold MEDIUM ERROR VariantGrpc - Parsing message body failed: Illegal base64 character 0 INFO HostProcess - completed host/plugin https://example-grpc-host | SqlInjectionScanRule ... with 0 message(s) sent and 0 alert(s) raised.
INFO HostProcess - start host https://example-grpc-host | CommandInjectionScanRule strength MEDIUM threshold MEDIUM ERROR VariantGrpc - Parsing message body failed: Illegal base64 character 0 INFO HostProcess - completed host/plugin https://example-grpc-host | CommandInjectionScanRule ... with 0 message(s) sent and 0 alert(s) raised.
Additional context:
The gRPC add-on itself does appear to be loaded and invoked correctly:
the add-on reports INSTALLED;
native HTTP/2 gRPC requests are successfully proxied by ZAP;
POST application/grpc requests and HTTP/2 200 responses are visible in ZAP
VariantGrpc is clearly being invoked during Active Scan, as shown by its log messages.
The failure therefore appears specifically related to decoding the body representation.
Would you like to help fix this issue?
- Yes
A patch for the problem can be seen below: grpc-native-http2.patch
Source: zaproxy/zaproxy