Skip to content

Inspect traffic

Edit Protobuf and gRPC

Since 0.4.0 Requires Cloak Proxy 0.4.0 or later.

Change gRPC, gRPC-Web, Connect and Protobuf messages as JSON in breakpoints, Compose, Map Local and scripts; Cloak Proxy encodes them back to Protobuf and shows each call's gRPC status.

Protobuf bodies are binary, so you can’t edit them like JSON. Cloak Proxy shows them as JSON wherever you can change a body: a paused breakpoint, Compose, a Map Local rule and a script. When you send it, the JSON is encoded back to Protobuf and framed the way the call expects (gRPC, gRPC-Web or Connect). You can mock an edge case of a gRPC API without writing code.

With your .proto files imported, messages use field names. Without them, fields are keyed by number, in a form that encodes back to exactly the same bytes.

A paused gRPC-Web response, edited as JSON. Execute encodes it again.

  1. How the body is framed and decoded
  2. A field typed in its key
The Breakpoint window with a paused gRPC-Web response from shop.v1.Orders/GetOrder. A purple bar says gRPC-Web, field numbers (no schema), Edit as JSON; it's encoded again on Execute. Below, the body as JSON: 1 is fc68188e, 2 is 4999, 3 is 2, 4 is a list of Trail running shoes and Socks, and 5:float is 1.5.
Content-Type Framing Status
application/grpc, application/grpc+proto gRPC: length-prefixed messages In the grpc-status header or HTTP/2 trailers
application/grpc-web, application/grpc-web+proto gRPC-Web: the same frames, then a trailer frame In the trailer frame, inside the body
application/grpc-web-text, …+proto gRPC-Web as base64 text In the trailer frame
application/connect+proto Connect streaming: frames, then an end-of-stream message In the end-of-stream JSON
application/proto with a Connect-Protocol-Version header Connect unary: one bare message The HTTP status, with a JSON {code, message} body for errors
application/x-protobuf, application/protobuf, application/vnd.google.protobuf One bare message None

Messages compressed with gzip or deflate are decompressed to read and edit them, and sent on uncompressed, which every gRPC peer accepts.

gRPC answers HTTP 200 whatever happened; the call’s own result is its gRPC status. Cloak Proxy finds it in the headers, in a gRPC-Web trailer frame or in a Connect end-of-stream message.

Where What you see
Traffic list A failed call’s Status shows 200 ✕ in red and its dot turns red. Hover for the status name and message.
Inspector header A gRPC NOT_FOUND pill (green for gRPC OK) next to the HTTP status. Hover for the code and message.
Body The decode note starts with the status, for example Status NOT_FOUND · gRPC-Web without a schema: fields by number. A body with only a status shows it as {status, message}.

The HTTP status says 200; the gRPC status says the order wasn't found.

The inspector of a gRPC-Web call: a 200 OK pill next to a red gRPC NOT_FOUND pill, and a body showing status NOT_FOUND and message Order fc68188e not found.
  1. In a breakpoint, a paused gRPC, gRPC-Web, Connect or Protobuf body opens as JSON under a purple bar. The bar says how it’s framed, the message type (or field numbers (no schema)) and how many messages it holds.
  2. Edit the JSON. The editor’s Tree view works too.
  3. Click Execute. Cloak Proxy encodes the messages, frames them as before and keeps the trailers. If the JSON doesn’t fit (an unknown field, an enum value that doesn’t exist, a decimal in a whole-number field), the bar shows why and nothing is sent.

A body you don’t change is sent byte for byte. A stream with several messages is a JSON array, one item per message; add or remove items to change how many are sent.

Without a schema, each key is a field number, with the wire type after a colon when the value alone doesn’t say it:

Key and value Encodes as
"1": 150 A varint. Negative numbers are written as 64-bit two’s complement, as int32 and int64 are.
"2": "Ada Park" Length-delimited text
"3": {"1": 7} A nested message
"4:bytes": "3q2+7w==" Length-delimited bytes, in base64
"5:float": 1.5, "5:fixed32": 7 32-bit
"6:double": 2.5, "6:fixed64": 7 64-bit
"7:sint64": -2 A zigzag varint (sint32, sint64)
"8": [1, 2, 3] A repeated field: one value per occurrence

Packed repeated fields arrive as one length-delimited value, so they show as bytes. Import the .proto file to edit them as lists.

With an imported schema, messages use the Protocol Buffers JSON mapping: field names, enums by name, 64-bit numbers as strings. The type is the one picked for the URL under Decode As, or the gRPC method’s input or output type. Unknown fields and enum values are refused, so a typo can’t silently drop a field.

Set the request’s Content-Type to a Protobuf type (for example application/grpc-web+proto), then write the body as JSON. A purple bar above the body says it will be encoded. When you send, the JSON is encoded with the URL’s message type, or by field number, and framed. Edit and Repeat on a captured Protobuf request opens its body as JSON.

A Map Local rule can answer a gRPC call with a message written as JSON.

A gRPC-Web answer written as JSON, with its status.

A Map Local rule for shop.v1.Orders/GetOrder answering with Content-Type application/grpc-web+proto, the option Write the body as JSON and send it as Protobuf turned on, Message type empty (by field number), gRPC status 0 OK, and the body as JSON with fields 1 to 4.
  1. In the rule’s Respond with › Inline, set the Content-Type header to the type the app expects.
  2. Turn on Write the body as JSON and send it as Protobuf.
  3. Enter a Message type from your imported schemas, or leave it empty to write fields by number (or to use the type the gRPC path gives).
  4. For gRPC-Web, Connect and gRPC, pick the gRPC status and a message, for example 5 NOT_FOUND to test the app’s error handling. gRPC-Web puts it in a trailer frame, Connect in the end-of-stream message, and gRPC in the grpc-status header.
  5. Write the body: an object for one message, an array for several (a stream), or [] for a reply that is only a status.

The editor checks the body as you type. If a request matches while the body doesn’t encode, the app gets a 500 that says why.

A script’s request and response get a protobuf property for Protobuf bodies: the message as JSON (an array for several), with field names or field numbers as above. Change it, and the body is encoded again:

Script
function onResponse(context, url, request, response) {
response.protobuf.status = "CANCELLED"; // with a schema
// response.protobuf["3"] = 4; // without one
return response;
}

body still holds the raw body as text; leave it alone when you change protobuf.

The get_protobuf MCP tool returns a captured body as JSON with its framing, message type, gRPC method, every message of a stream, and the gRPC status and trailers. To change a reply, an assistant can create a Map Local rule with upsert_rule: headers with the Content-Type, body as the JSON, and protobuf: {messageType, grpcStatus, grpcMessage}.

Make the app’s GetOrder call fail with NOT_FOUND, using the response of request #42 as the starting point.

  • gRPC over HTTP/2 carries its status in trailers. A Map Local answer sends it in the headers instead, which gRPC clients accept for a reply with no messages (trailers-only); with messages, most clients read it too, but some expect a trailer.
  • WebSocket messages that carry Protobuf are shown, not edited, as Protobuf.
  • Groups (a deprecated wire type) can’t be edited without a schema.