Docs + guide for Mode B HTTPS interception; record Pi 5 verification

Updated README/architecture/security/limitations/testing/api/CHANGELOG and the
LaTeX user guide (now 14pp) with the Certificates and HTTP Explorer screens.
0 called Go vulns, npm audit clean.
This commit is contained in:
Andy Dixon
2026-10-10 08:14:02 +01:00
parent 5dbf85f84e
commit f3cfa9ccf1
12 changed files with 129 additions and 20 deletions
+16
View File
@@ -56,3 +56,19 @@ All are GET, authenticated, and derived from already-captured data; none change
state or touch the network. Conversations and statistics are computed from the
SQLite index (fast, cross-segment); follow/dns/tls/findings call tshark over the
segment files on demand.
## Interception (Phase 4, Mode B)
| Method & path | Purpose |
|---|---|
| `GET /interception` | `{config, ca, running, port, note}` |
| `PUT /interception {enabled, capture_bodies, max_body_kb, exclude_hosts[]}` | Toggle/configure; applies firewall redirect + proxy via helper |
| `GET /interception/ca.crt` | Download the CA **public** certificate (never the key) |
| `POST /interception/ca/rotate` | Regenerate the CA (only while disabled) |
| `POST /interception/clear` | Delete all intercepted HTTP flows + retained bodies |
| `GET /http?q=&client=&limit=` | Decrypted HTTP transactions, newest first |
| `GET /http/{id}` | One transaction (headers, timing, TLS, flags) |
| `GET /http/{id}/body/{req\|resp}` | Stored body as an opaque download (nosniff, sandbox CSP); only if body capture was on |
Interception is off by default. Live transactions also arrive on `/events` as
`http` events.
+16 -4
View File
@@ -167,7 +167,19 @@ dumpcap -i IF [-f BPF] -w - ──pcapng──▶ splitter ──▶ seg-000001
## Not yet implemented (phase 3+)
Implemented since Phase 2: conversations, follow TCP stream, DNS explorer, TLS
explorer (metadata only), statistics. Not in the navigation until they work:
HTTP explorer and TLS interception (modes B and C, deferred together), findings,
and capture scheduling.
Implemented: conversations, follow TCP stream, DNS/TLS explorers, statistics,
findings, and Mode B HTTPS interception (CA + mitmdump + HTTP explorer). Not yet
built: Mode C strict fail-closed policy, and capture scheduling.
## Interception (Mode B)
`pilens-proxy.service` runs `mitmdump` (in `/opt/pilens/proxy-venv`) as the
unprivileged `pilens` user, in transparent mode. An in-app RSA CA
(`internal/proxy`) is written to the mitmproxy confdir; the public cert is
downloadable, the key never is. When the operator toggles interception on, the
core writes `proxy.env`, then the root helper re-renders the nft table with a
`prerouting` redirect (`iifname <ap> tcp dport {80,443} redirect`) plus an
`input` rule accepting only `ct status dnat` connections, and starts the service.
A mitmdump addon writes each transaction as NDJSON; the core tails it into SQLite
and the HTTP Explorer. Toggling off removes the redirect and stops the service.
Interception is off by default and the appliance is pure capture-only until enabled.
Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.
+42 -3
View File
@@ -189,8 +189,7 @@ ports, DNS, TLS handshakes, timing, the lot.
scrambled. PiLens shows you \emph{who} talked to \emph{whom}, \emph{when}, over
\emph{what} --- the server name, the TLS version, the certificate when it is sent
in the clear --- but \textbf{not the contents}. That is TLS doing its job, not
PiLens failing. Decryption needs a separate, explicit step on each device (a
future feature), and \textbf{a trusted certificate can never be pushed down the
PiLens failing. Decryption needs a separate, explicit step on each device (Mode B, \S3.9), and \textbf{a trusted certificate can never be pushed down the
network} --- the device's owner must install it by hand. Anything that claimed
otherwise would be lying to you.
\end{truth}
@@ -201,7 +200,8 @@ otherwise would be lying to you.
self-signed, so your browser warns once. Check the fingerprint the installer
printed, trust it, move on.
\item \textbf{The interception padlock} (decrypting a device's HTTPS) is a
\emph{separate}, opt-in, per-device thing that is not part of this version.
\emph{separate}, opt-in, per-device thing: a toggle on the Certificates page.
See \S3.9.
\end{itemize}
% ======================= 3. THE SCREENS =======================
@@ -327,6 +327,43 @@ spells out the innocent explanations. PiLens will never tell you ``you've been
hacked'' from a retransmission.
\end{truth}
\subsection{HTTPS inspection (Mode B) --- the opt-in one}
\begin{tldr}
A toggle on \textbf{Certificates}. Download the appliance's CA, install and trust
it on a device you own, flip the switch, and that device's HTTPS shows up decrypted
in the \textbf{HTTP Explorer}. Off by default.
\end{tldr}
\screen{certificates}{Certificates \& HTTPS Inspection --- the toggle, the CA to download, per-device install steps, and the privacy options.}
\begin{truth}
This is the only part that can see inside HTTPS, and it only works \emph{with
consent}: you must install the CA on each device by hand and turn trust on. It
\textbf{cannot} be pushed over the network or via DHCP. Pinned apps, QUIC and
private trust stores still can't be read --- they show up as honest errors, never
silently bypassed. The CA's private key never leaves the Pi and is not
downloadable.
\end{truth}
\textbf{To use it:} Certificates \textgreater\ \emph{Download CA certificate}
\textgreater\ install it on your test device (the page has per-OS steps; iOS needs
the extra ``Certificate Trust Settings'' toggle) \textgreater\ verify the
fingerprint matches \textgreater\ flip \emph{Interception} on.
\begin{heads}
Turning interception on while a device has \emph{not} installed the CA will break
that device's HTTPS (it correctly refuses the unknown certificate). Install first,
toggle second --- and toggle it back off when you're done.
\end{heads}
\begin{tip}
\textbf{Bodies are off by default.} The Explorer shows headers, status and timing
for everything; request/response bodies are only stored if you opt in on the
Certificates page, for a named investigation. Clear them when done.
\end{tip}
\screen{http}{HTTP Explorer --- decrypted transactions from trusting devices, with headers and (opt-in) body viewers.}
\subsection{The housekeeping screens}
\begin{description}[leftmargin=0pt, style=nextline, itemsep=3pt]
\item[\textcolor{amber}{Capture Library}] Every saved session. Rename, tag,
@@ -334,6 +371,8 @@ hacked'' from a retransmission.
integrity (SHA-256 per file + a manifest).
\item[\textcolor{amber}{Devices}] Who has been on the Wi-Fi. Label them, add
notes, jump to their packets. \mn{Randomised MACs show a badge; PiLens never pretends they identify hardware.}
\item[\textcolor{amber}{Certificates}] The HTTPS-inspection toggle, CA
download and device install guide (\S3.9).
\item[\textcolor{amber}{Network Settings}] Change the access point, DHCP, DNS,
forwarding and management access. Applied as one transaction with automatic
rollback.
+7 -5
View File
@@ -3,12 +3,14 @@
**Encryption.** PiLens captures packets; it does not decrypt them. HTTPS, QUIC/HTTP-3, TLS 1.3 with
Encrypted ClientHello, DNS-over-HTTPS/TLS, and VPN traffic are visible only as encrypted
packets plus whatever metadata the protocol exposes in clear (for TLS ≤1.3 without ECH: SNI,
versions, ALPN; for TLS 1.2: certificates). The optional HTTPS inspection mode (client-installed
CA via mitmproxy) is **not implemented** in this version, and even when it is, it will not work
against certificate pinning, private trust stores, mutual TLS, QUIC, or apps that bypass proxies.
versions, ALPN; for TLS 1.2: certificates). The optional HTTPS inspection mode (Mode B) **is** available: enable it on the Certificates page,
install the appliance CA on each authorised test device, and decrypted transactions appear in the
HTTP Explorer. It does **not** work against certificate pinning, private trust stores, mutual TLS,
QUIC/HTTP-3, or apps that bypass the proxy — these are reported honestly as not intercepted, never
bypassed. It is off by default; while off, PiLens is pure capture-only.
**Not yet implemented:** HTTP explorer, findings/diagnostics rules, capture scheduling,
the strict inspection mode, WireGuard, packet comparison. They are not shown in the UI.
**Not yet implemented:** the strict inspection mode (Mode C fail-closed policy), capture scheduling,
WireGuard, packet comparison. They are not shown in the UI.
**TLS explorer shows metadata only.** SNI, ALPN, negotiated version and cipher, plus any
certificate sent in the clear (TLS ≤1.2). TLS 1.3 encrypts the certificate, so the certificate
+8 -1
View File
@@ -28,12 +28,17 @@ sensitive system.
and `systemctl` with fixed argv; and nothing else. There is no generic
command execution.
- **pilens-dnsmasq**: Debian's dnsmasq, which drops privileges itself.
- **pilens-proxy** (Mode B, optional): `mitmdump` as the unprivileged `pilens`
user, sandboxed (`ProtectSystem=strict`, `NoNewPrivileges`). Started/stopped by
the helper on toggle. The interception CA key is 0600 and never downloadable;
only the root helper adds the firewall redirect.
## Secrets
| Secret | Where | Exposure |
|---|---|---|
| Admin password | Argon2id hash in SQLite (0640 pilens) | Never returned |
| Interception CA key | `proxy/ca/mitmproxy-ca.pem` (0600 pilens) | Never downloadable; only the public cert is served. Separate from the management CA. |
| Session tokens | Only the SHA-256 is stored | Cookie only |
| Wi-Fi passphrase | SQLite + helper state (0600 root) + NM keyfile (0600 root) | Write-only in the API. Never in a process argv. Redacted in diagnostics. |
| Setup token | `/var/lib/pilens/setup-token` (0600) | Deleted when the first admin is created |
@@ -41,7 +46,9 @@ sensitive system.
## Data protection
- PiLens currently stores no decrypted payloads, because it decrypts nothing.
- With interception **off** (the default) PiLens decrypts nothing and stores no
payloads. With it on, HTTP metadata is stored; bodies are retained only when the
operator explicitly opts in, size-limited, and clearable, with views audited.
- Captures are deleted by retention (oldest unpreserved first) or explicitly
by an admin, who must confirm. Deleting a session removes its files and all
index rows.
+1
View File
@@ -67,6 +67,7 @@ Only tests that were actually run are listed.
| H15 | **pass** | `setcap -r dumpcap` → `repair.sh` restored `cap_net_admin,cap_net_raw=eip`, all services ✓ |
| H16 | **partial** | Loopback worst-case: 108 MB/s / 4,000 pkt/s with 14.7% drops honestly reported. Real Wi-Fi iperf3 not run. See [benchmarks.md](benchmarks.md). |
| H17 | **pass** | 263 MB import indexed in 3.2 s (~3,200 pkt/s); filters ~1.2 s; UI responsive. |
| Interception (Mode B) | **pass** | On the Pi 5: CA generated + downloaded (public only, 0 key lines). Toggle on → proxy active on 8081, nft redirect + `ct status dnat` accept present. A CA-trusting client (curl `--cacert`) decrypted https://example.com and api.github.com (TLS 1.3); an untrusting client was refused and logged as a TLS failure. HTTP Explorer showed all three. Toggle off → redirect removed, proxy stopped, masquerade intact. Full device trust (phone) is the operator's manual step. |
| Findings | **pass** | On the iPhone capture: 18 retransmissions/7 flows, 1 SYN retry, 7 resets, 17 dup-ACKs, 3 NXDOMAIN, 1 ICMP unreachable — each linking to evidence packets. |
| H9, H10, H14 | not run | Need physical cable/client actions. |
+26 -5
View File
@@ -12,15 +12,36 @@ single most common misunderstanding about a tool like this.
unrelated to anything captured. The installer prints its SHA-256 fingerprint;
verify it the first time your browser warns about it.
2. **Interception CA** — a separate authority that *would* let PiLens decrypt a
test device's HTTPS, by having that device explicitly trust it. **This is not
implemented in this version** (Phase 4). When it is, it will be opt-in,
per-device, and require you to install and trust the CA on the client
yourself. There is no way to do it silently.
2. **Interception CA** — a separate authority that lets PiLens decrypt a test
device's HTTPS, *after* that device's owner explicitly installs and trusts it.
Generated in-app (RSA-2048, `PiLens Interception CA`), downloadable from the
Certificates page (public cert only — the key never leaves the appliance).
Interception is a toggle, **off by default**. There is no way to make a device
trust it silently or over the network.
The two are kept independent on purpose: compromising or distributing an
interception CA must never affect the management channel.
## Mode B: authorised interception (optional, off by default)
Turn it on from **Certificates & HTTPS Inspection**. PiLens then transparently
redirects AP clients' TCP 80/443 to a local mitmdump, which presents certificates
signed by the appliance CA. A device that has installed and trusts that CA sees a
valid chain, so its HTTPS is decrypted and the transactions appear in the **HTTP
Explorer**. A device that has *not* installed it sees an untrusted certificate and
its connections fail — which is why interception is useless (and harmless) until
you do the manual install step on each device you own.
- Redirect is `iifname <ap>` only, TCP 80/443. The proxy accepts only
connections the redirect created (`ct status dnat`), so it is never an open
proxy for clients.
- **Excluded hosts** (pinned apps, privacy-sensitive sites) are passed straight
through, still encrypted.
- **Bodies are not retained by default.** Enable it per-investigation; the data
is on the appliance disk until cleared, and views/exports are audited.
- Pinned apps, private trust stores, mutual TLS and QUIC cannot be intercepted
and are reported as such — never bypassed, never downgraded.
## Why HTTPS content is unavailable without client trust
TLS exists precisely to stop a device in the middle from reading traffic. To