# Network capture, interception, and replay

Source: https://olef.dev/docs/concepts/network

Public guide snapshot. [View source](https://github.com/olef-dev/olef/blob/4a2fd8128ec9f6be0f6e5254e1c6b81211d2170c/docs/network.md). For exact flags in your installation, use `olef commands <path> --json`.

`net` is a host-side MITM proxy built into the single binary — no Python, no
external mitmproxy. `net start` spawns the proxy, wires the device through
`adb reverse` and proxy settings, and decrypted HTTP(S) transactions then stream
as `http` events on the same timeline as `screen` when `watch` is running.

The pre-existing device proxy setting is persisted before wiring; a repeated
`net start` repairs wiring to an already-running daemon, while `net stop`
attempts to restore that exact setting without overwriting unowned wiring.
`proxy_restoration` and `adb_reverse_restoration` are `restored`, `not_needed`,
or `unresolved`; `cleanup_complete` requires both to be resolved. Recovery
snapshots remain until readback verifies restoration. An unowned proxy with no
daemon/snapshot is preserved with an actionable warning.

`network_reachable` reports only raw-IP ping plus DNS (`--canary-host` selects
the DNS probe). `application_connectivity` is `not_checked`; verify an app HTTP
request separately. The legacy `connectivity_restored` field is now null and
must not be used as a boolean success check. Teardown errors preserve the
underlying typed cause plus `phase`, `completed_phases`, and total `elapsed_ms`
in `detail`. Progress is on stderr; stdout remains one terminal JSON object.

`net status` now exits nonzero with `net_status_incomplete` when a required
observation fails. Its `detail` retains partial results, `complete:false`,
`http_proxy_state:unknown`, and typed per-observation errors. Unknown match
results are null. A successfully read absent proxy has `http_proxy:null` and
`http_proxy_state:known`; only a complete observation returns action success.

## Capture host scope

`net start --host example.com` and `net start --host '*.example.com'` both
capture the domain and its subdomains, ignoring case and matching at domain
label boundaries. They do not match `otherexample.com` or
`example.com.other.example`. Bare fragments such as `example` retain substring
matching. Repeat `--host` for multiple scopes, or omit both `--host` and
configured `proxy.hosts` to capture all hosts.

Only the leading `*.` wildcard form is supported. Patterns such as
`*example.com`, `api.*.com`, and `example.*` fail before proxy startup. These
rules also apply to configured `proxy.hosts`. The `net log` and intercept/rule
host filters use substring matching on captured flows and do not change which
hosts the proxy captures.

## Sessions, checkpoints, and querying

Each new proxy run returns a stable `capture_session_id`, and every captured
flow records both that session and any matching rule ids. Isolate a phase with
`net log --session`, `--since 2m`, `--after-id`, `--rule-id`, or create a
durable boundary with `net checkpoint` and query it using
`net log --after-checkpoint`. `net log clear` clears only queryable history
while preserving an active proxy and its rules; restarting the proxy creates a
new session.

Completed flows enter a bounded in-memory queue; `net status` exposes
`dropped_flows` if sustained traffic outruns storage. The session JSONL keeps
one 64 MiB current generation plus one rotated generation, bounding disk use to
roughly 128 MiB per device session.

## Intercepting and mutating flows

Beyond observing, the agent can **intercept** a flow — `net intercept` pauses
matching requests/responses and emits them as `http_intercept` events on
`watch`; each held event includes device-scoped `net show`, `resume`, `drop`,
and `respond` actions so the agent can decide before the hold deadline. The
same flow ID remains actionable across `net status`, `net show`, and the release
verbs while held. Status includes its phase, held/expiry timestamps, and client
connection state; a raced action reports `already_released`, `deadline_expired`,
`client_canceled`, or `unknown_id` with the available lifecycle timestamps. The
agent inspects with `net show`, then releases with
`net resume --set-status/--body/…`, `net drop`, or `net respond` (a canned
reply).

HTTP `net drop <id>` returns 502 by default; `--set-status` returns an explicit
HTTP status. These drops, including fail-closed hold timeouts, persist one final
flow with the synthetic response sent to the app, `matched:intercept:drop`, and
an error. `upstream_bypassed` identifies request-phase drops; response-phase drops
replace the captured upstream response with the synthetic result.
Use `net drop <id> --transport` to abort the downstream connection
or HTTP/2 stream without sending a response status/body. For a lost successful
refresh response, first intercept the response with `--at response --status 200`
and inspect it before the transport abort. Capture retains the upstream 200 and
body, marked `intercept:transport_abort` and a downstream error. The app never
received that 200. This mode requires a current daemon, is HTTP-only, and cannot
be combined with `--set-status`.

`net intercept --clear` disarms HTTP interception. It first checks daemon support;
an older daemon returns `net_http_intercept_clear_unsupported` without changing
its matcher. With `--dir c2s` or
`--dir s2c`, it disarms the single WebSocket interception matcher instead.
The other protocol's matcher is unchanged. Clearing does not release flows
or frames already held: they keep their deadlines and remain actionable with
the usual release commands.

## Declarative rules

Repeated edits can be promoted to declarative `net rule`s (map-local /
map-remote / set-status / set-request-header / set-response-header / replace /
block / delay). A `respond` rule atomically returns a synthetic status, headers,
and literal or file-backed body without contacting upstream; it can match a
GraphQL `operationName` from either the URL query or JSON POST body. Captured
flows name the rule and report `upstream_bypassed:true`.

Header rules deliberately name their phase: use `set-request-header` before
upstream or `set-response-header` before returning to the app. The ambiguous
`set-header` kind is rejected instead of guessing.

## Fixtures and replay

`net export fixtures` writes the versioned, content-addressed bundle consumed
directly by `net replay --from`: the loader validates its schema, paths, sizes,
hashes, statuses, and headers before atomically replacing the active set.
Replay keys include method, scheme, canonical host, effective port,
case-sensitive path, normalized query pairs, GraphQL operation, and canonical
JSON (or raw body) hash, so same-route requests cannot silently collide.
Redacted, truncated, streamed, errored, or otherwise incomplete captures are
refused rather than producing misleading fixtures. A corrupt or incompatible
replay candidate leaves the prior active generation untouched; an older running
daemon is detected by capability preflight and must be restarted before loading
the bundle.

`net export har|curl|fixtures` hands flows to other tools by writing a durable
artifact and returning an actionable summary; HAR defaults to
`olef-network.har`, curl to `olef-network.curl.sh`, and fixtures
to `olef-fixtures` unless `--out` selects another path.

## Verifying an app is interceptable

`net check <app>` labels its debuggable/targetSdk result as a static heuristic
and leaves the app-specific verdict unverified. With the proxy running,
`net check --probe <app>` launches a package-scoped HTTPS canary; it reports
verified/interceptable only when the app handles that intent, requests its
unique URL, and the exact decrypted flow is captured.

## Protocol coverage

The decrypted leg negotiates **HTTP/2 or HTTP/1.1** (h2 apps aren't
downgraded), streams **SSE / large bodies** through instead of buffering them —
both response and request (a big upload streams chunked; marked
`streamed`/`req_streamed` in the flow) — decodes `gzip`/`deflate`/`br`/`zstd`
for capture, rules and the app (`upstream_content_encoding` and
`upstream_resp_len` in the flow keep what the server actually sent),
and **captures WebSocket (WS/WSS) frames**. The proxy validates the real server
certificate for both HTTPS and WSS, so a tampered network cannot hide behind the
Olef CA; `net start --insecure-upstream` (or `proxy.verify_upstream: false`
in config) accepts a self-signed dev backend you trust.

## WebSocket capture and drive

Once an in-scope connection upgrades, the proxy forwards every byte unchanged
and decodes a copy of the frame stream. Inspect it hierarchically so an agent
spends tokens only on the frames it needs: `net ws` lists sessions (id, url,
per-direction message/byte counts), `net ws <id>` lists that session's messages
(compact `dir`/`opcode`/`preview`, filterable by `--dir`/`--opcode`/`--grep`/
`--since`), and `net show <message-id> --body` reveals a full reassembled
payload (`--body-file` writes it binary-safe; bare `net show` returns metadata +
`preview`). Fragmented messages are reassembled and `permessage-deflate`
payloads inflated (marked `compressed`/`decompressed`, with `wire_len` vs
`payload_len`). `net log` shows `ws_open`/`ws_close` lifecycle inline with HTTP
by default; `--protocol websocket|all` adds per-message `ws_msg` events, which
also stream live on `watch`. `net ws <id> --stats` summarizes a chatty socket
(opcode histogram, per-direction bytes, compression ratio, rate) in one call;
`net show <msg> --format hex|json|protobuf` decodes a payload and `--frames`
shows a fragmented message's per-frame breakdown; `net export jsonl` and
`net export har` (with devtools `_webSocketMessages`) write durable dumps.
Payload retention is bounded (`truncated`), `--redact` scrubs text frames,
handshake headers, and close reasons, and an engine that bypasses the proxy or
pins its certificate is reported (`tls_error`) rather than silently dropped.

Beyond observing, an agent can **drive** a live session in the same
agent-in-the-loop model as HTTP: `net inject <id> --dir s2c --text …` splices a
frame in (simulate a server push, or send to the server as the app; always safe,
even under compression); `net rule add ws-drop`/`ws-set-text` declaratively drop
or rewrite matching frames; and `net intercept --dir …` pauses matching frames
(surfaced on `watch` and in `net status`) for `net resume [--text …]`/`net drop`.
Drop/modify re-encode a frame, which is unsafe under `permessage-deflate`
context takeover — those are forwarded unchanged and marked `refused_deflate`;
`net start --anticomp` negotiates an uncompressed session where they fully
apply.

## The proxy CA

By default the proxy signs with a CA it generates on first use. To reuse a CA
the device already trusts — an existing mitmproxy/Charles/corporate CA — run
`net ca import --cert <pem>` (the key can be a separate `--key`, or bundled in a
combined PEM like mitmproxy's `mitmproxy-ca.pem`); every downstream step then
signs and installs *your* CA. `net ca info` shows the active CA and
`net ca reset` returns to a generated one. Per-project CAs and the
`ca_trusted` assertion are covered in [configuration.md](https://olef.dev/docs/concepts/configuration).

`net trust` (`--auto`, the default) installs the CA into the device's
**system** trust store, falling back to the user store. On a debuggable build,
such as an emulator's google_apis image, it first restarts adbd as root
(`adb root`), which briefly drops adb forwards that Olef re-creates on
the next command; `--no-adb-root` skips that. Physical devices and production
images (including the android-tv emulator image) cannot become root: use
`net trust --push` and finish the install in Settings, which needs a screen-lock
credential. Apps that opt out of user CAs via network security config, pin their
certificates, or use stacks that bypass the system trust store will reject the
proxy — that failure is surfaced as a `tls_error` event rather than silently
missing traffic.

## Redaction at the network boundary

`net start --redact` applies the built-in/configured policy to
authorization/cookie headers, nested JSON/GraphQL body fields, JWTs, email/IP
values, URL path/query values (including percent-encoded names and values),
hosts, errors, and configured patterns before completed captures are persisted
(the session log is written `0600` either way). Records flag redacted routing
or error fields and carry policy version 2. Forwarded traffic is unchanged.

A live daemon reports its capture-policy fingerprint; requesting `--redact`
refuses to reuse an unredacted or differently configured daemon and asks for an
explicit stop/start. See
[security-and-redaction.md](https://olef.dev/docs/concepts/security-and-redaction).

## Traffic the proxy cannot see

`net` only sees traffic that goes through the device proxy and trusts its CA.
A `tls_error` usually means the app pins certificates or does not trust user
CAs; clients with custom socket factories, Cronet, or QUIC bypass the proxy
entirely. For a debug build, trust the Olef CA in the network security
config (`net ca info`) or disable pinning in debug. In-process capture for such
traffic is planned for the Olef runtime; the former OkHttp AAR companion
has been removed.

### Guarded JSON response edits

Use an RFC 6901 JSON pointer to change one existing field, with an expected old
JSON value and a limit on successful applications (default: one):

```sh
olef --target tv net rule add set-json --host api.example.com --path /graphql --method POST --operation-name SwitchProfile /extensions/expiresIn 600 30 1
olef --target tv net rule list
```

GraphQL response rules match the **request's** operation name. Missing pointers,
invalid JSON, or unexpected old values leave the response unchanged and record
`json_pointer_missing`, `json_body_invalid`, or `json_expected_value_mismatch`
in the rule's `runtime.last_error`. `runtime.rejections` and `applications`
show what actually happened; installing a rule is not proof that it applied.
Limits count successful edits, including concurrent flows, and reset when the
rule is reinstalled or the daemon restarts. Configured values are omitted from
rule summaries. Streamed/undecodable responses are not edited. Standard flow
`modified` and `rule_ids` fields identify successful mutations.

Canonical rule files use a `transform` action with `type: "set_json"`,
`pointer`, `expected`, `value`, and `max_applications` fields. JSON null is a
value; an absent field never satisfies an expected null.

## Prepared edits and client deadlines

For deterministic response changes, prepare a narrowly matched rule before
triggering the request, save its returned id, verify the app result, then remove
that id in `finally`. Avoid clearing unrelated rules. Use a response-phase
replacement when upstream must still execute; request-phase `respond` bypasses
upstream. `commands --guide net` includes a concrete operation-scoped recipe.
A configured hold deadline does not extend the app's timeout. Client cancellation
is not repaired by increasing the proxy hold; observe the outcome before replay.
