Interception

MITM & interception

There are two methods to send the HTTPS traffic of a CLI to Glider. Glider then adds one mechanism for Cursor only: the Agent RPC plane.

A comparison of the gateway and MITM

GatewayMITM
The client settingSet Override Base URL to :8080/v1Set the proxy, or set nothing for transparent mode
The trafficOnly the traffic that you sent to GliderThe cloud traffic of the CLI
A request that is not localGlider uses your own OpenAI or Anthropic keyGlider sends the request to the origin of the CLI. The credentials do not change.
When to use itUse the gateway for agent work with tools. It is tested and it operates with each CLI.Use MITM for local text replies in Cursor, for transparent interception, and for delegation.

CONNECT and the TLS certificate

CONNECT host:443
  → 200 Connection Established
  → HostMatcher.Match?
       yes → make a leaf certificate from the Glider CA, decrypt, then try
             a local reply, a delegation, or the origin
       no  → tunnel the data with no decryption (bidirectional io.Copy)

Glider decrypts only the hosts in the list. It makes the CA at ~/.glider/mitm/ca.{crt,key} when MITM starts the first time. Install ca.crt in the trust store of your operating system. For a CLI that uses Node or Electron, also set NODE_EXTRA_CA_CERTS. The Node runtime of Claude Code is different: a live test showed that it trusts the operating system store, and it does not need this variable. This is important for transparent mode below.

Host patternVendor
api2.cursor.sh, api3, api4, *.api5.cursor.shCursor
api.anthropic.comClaude Code
daily-cloudcode-pa.googleapis.com, cloudcode-pa.googleapis.com, antigravity-unleash.goog, oauth2.googleapis.com, www.googleapis.com, play.googleapis.comagy (Antigravity)

A host that is not in this list goes to its origin with no decryption, because mitm.passthrough_default is true. Set it to false to refuse a host that you did not list, in place of sending it.

The certificate authority, and how to trust it

Glider makes its own CA the first time that MITM starts (LoadOrCreateAuthority). It signs one leaf certificate for each host that it decrypts.

FileContent
~/.glider/mitm/ca.crtThe certificate. You install this one. The setting is mitm.ca_cert.
~/.glider/mitm/ca.keyThe private key. Give it to no one. The setting is mitm.ca_key.

Decryption fails until the CLI trusts that certificate. There are two steps, and one is not necessary for each CLI:

  1. Install ca.crt in the trust store of the operating system. On Windows this is Trusted Root Certification Authorities.
  2. For a CLI on Node or Electron, such as Cursor, point that runtime at the same file:
    $env:NODE_EXTRA_CA_CERTS = "$env:USERPROFILE\.glider\mitm\ca.crt"
    Use a variable of the user or of the system, so it continues after a restart. Stop Cursor completely after you change this.

Claude Code needs step 1 only. Its Node runtime trusts the store of the operating system directly. Therefore the trust is one action at the level of the system, and it is not a variable for each process. This is why transparent mode is complete for Claude Code, and why Cursor keeps the caveat about a session that is already open.

Routing and trust fail separately. The redirector can move the packets correctly, and the handshake can still fail because the CLI does not trust the CA. Test the two conditions one at a time.

Transparent redirection (Windows: WinDivert — Linux: iptables)

For the usual MITM mode, you must set the proxy in the CLI. Transparent mode does not need that setting. Glider redirects the outbound HTTPS packets in the operating system. It selects the packets by port and by process name. The CLI does not help, and the mode operates on a session that started before Glider. You must still trust the CA: refer to the section above.

mitm:
  transparent: true
  transparent_port: 8083
  transparent_ports: [443]
  windivert_dll_path: ~/.glider/mitm/windivert/WinDivert.dll   # Windows only; WinDivertNN.sys alongside it
PlatformThe mechanismThe permission that it needs
WindowsWinDivert. It changes the destination of a matched packet.Administrator
LinuxAn iptables REDIRECT rule in the nat OUTPUT chain, and SO_ORIGINAL_DST to recover the true destination. Glider calls sudo -n iptables.root, or sudo with no password
macOSNone. The design uses pfctl, and no code exists. Glider gives a clear error.

The iptables rules stay on the machine until Glider removes them. A stop that is not clean — a failure, taskkill /F or SIGKILL — runs no code, therefore it removes nothing. Glider cannot see that event when it occurs. It sees it at the NEXT start, and it gives a warning. The warning tells you to test for a rule that stayed and for a delegate subprocess with no parent: iptables -t nat -L on Linux, or tasklist and netstat on Windows. Glider gives the warning, and it does not repair the condition for you.

WinDivert changes the destination of a matched packet. The new destination is the primary local IP address of this machine, on transparent_port. In the example above this is :8083. It is not the loopback address. WinDivert then calculates the checksums again and sends the packet. The TCP stack of the client does not see this change. Windows has no function that gives the original destination. Linux has SO_ORIGINAL_DST, but Windows does not. Therefore the redirector keeps its own table of the flows. The key is the source ip:port of the client. To select the processes, the redirector reads the vendor registry. The Vendors page of the dashboard fills this registry. Do the discovery on that page first. If you do not, transparent mode has no process filter and uses only the port.

A live test showed that this mode operates correctly with Claude Code. The test included the redirection, the CA trust, no help from the CLI, and a terminal session that was already open. There is no equivalent test for cursor-agent or agy. Linux has its own implementation (internal/mitm/redirector_linux.go). It uses iptables REDIRECT in the nat OUTPUT chain, and SO_ORIGINAL_DST to get the true destination. On macOS the design is complete, but no code exists: that platform gets a stub which gives an error at start. The macOS design uses pfctl.

Cursor's Agent RPC plane (BidiAppend → RunSSE)

The Cursor protocol sends the prompt and the answer in two different RPCs. This section and the two sections below apply to the Cursor MITM plane only. They are not a general mechanism.

  1. BidiAppend. The prompt and the context arrive. Glider extracts the text of the most recent turn. Glider then makes a decision and calls ArmLocal or ArmOrigin on the hub. The key is the request UUID.
  2. RunSSE. The reply stream opens. The hub calls Wait(reqID, 800ms). For a local decision, Glider encodes the text and sends the reply. If there is no local decision, Glider connects to the origin.

Glider can give a local text reply only for a root Agent RunSSE. A child RunSSE and a tool-loop RunSSE go to the origin. You can turn on mitm.agent_rpc_tool_codec, which gives a small map of the usual ToolCall frames. The full list of frames does not exist yet. For agent work with tools, use the gateway.

How the fulfill hub operates

AgentFulfillHub connects the BidiAppend prompt to the RunSSE answer for the same request UUID. One mutex protects three maps. waiting holds the requests where RunSSE arrived first. pending holds the requests where BidiAppend arrived first. family holds the sticky condition of a turn family. ArmLocal and ArmOrigin tell a waiter if a waiter exists. If no waiter exists, they write to pending and start a 30-second timer to remove it. Wait looks in pending first. If it finds nothing, it adds a waiter and then waits for an offer or for an 800-millisecond timer. After the timer, Wait looks in pending one more time. This is necessary when BidiAppend arrives at the same moment as the timer. If the local encode operation fails after a partial write, Glider must not connect to the origin, because the client already received part of a reply.

The sticky-cloud states

The turn-family sticky condition does not apply to the full conversation. An explicit flag or a cloud DecideLocal decision opens a family for a short time. The key is the root request UUID. The immediate follow-on messages, such as the reply summary and the title, get the same condition. Without this mechanism, those messages look like small local prompts. The router then sends them to the local model, but they must stay with the cloud parent that just operated.

ConstantValueFunction
defaultRunSSEFulfillWait800 msRunSSE waits this long for a BidiAppend decision. An idle reconnection goes to the origin.
DefaultTurnFamilyTTL90 sThe length of the family window. The setting is routing.turn_family_ttl.
DefaultCloudPostRunGrace120 sThe additional sticky time after the parent RunSSE ends

Rule: while a StickyCloud family is live, a concurrent StickyLocal must not replace it. The full policy is in planning/routing_and_context.md.

The config settings

KeyFunction
enabledIn the default profile this is true
hostsThe hosts that Glider decrypts. Refer to the table above.
agent_rpc_fulfillLets Glider give a Cursor text reply from BidiAppend through RunSSE
agent_rpc_canned_on_errorKeep this false in normal operation
agent_rpc_tool_codecTurns on the Cursor tool-call frames. For agent work with tools, use the gateway.
origin_on_local_errorSends the request to the origin after a local failure. This is the default. Pure local mode sets it to false.
transparent, transparent_port, transparent_ports, windivert_dll_pathRefer to transparent redirection above
passthrough_defaulttrue sends a host that you did not list to its origin. false refuses it.
require_local_healthyGlider selects the local model only after a health check succeeds. This stops a silent change to the origin.
agent_rpc_canned_textThe fixed text of agent_rpc_canned_on_error.
debug_agent_rpc, debug_dump_dirRefer to the debug output below.

You must restart Glider after you change the port, the CA, the hosts or the transparent settings. Glider accepts all other changes while it runs.

The configuration reference gives the type and the default of each key.

How to make transparent mode more narrow

Transparent mode selects the traffic by port and by the image name of the process. Therefore it catches EACH process on the machine with a name that agrees. To limit it to the processes that you name, send their PIDs:

curl -X POST -H 'content-type: application/json' \
     -d '{"pids":[1234,5678]}' \
     http://127.0.0.1:8081/api/mitm/enrollment

Glider then uses these PIDs in addition to the test on the name. An empty array, or no array, stops the narrow selection. That is the behaviour when you start.

The call succeeds and does nothing when transparent mode does not operate, or when the redirector of your platform does not support this. Therefore it is not dangerous to call it against a setup with no transparent mode.

The debug output

mitm.debug_agent_rpc: true adds three things. The environment variable GLIDER_MITM_DEBUG_RPC does the same.

What you getWhere
Structured logs of the Agent RPC planeThe output of Glider
The bodies of the requests and the replies~/.glider/mitm-debug/, or mitm.debug_dump_dir
The most recent intercepted requestsGET /api/mitm/debug/recent

A dump holds the full body of a request. That body can contain your source code and your prompts. Turn this off when you finish, and remove the directory.