Files
pilens/docs/security.md
T
Andy Dixon f3cfa9ccf1 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.
2026-10-10 08:14:02 +01:00

5.7 KiB

Security and privacy model

Captured traffic can contain credentials, tokens, private messages, and medical or financial data. PiLens treats the appliance as a privileged, sensitive system.

Trust boundaries

Boundary Untrusted side Controls
Browser ↔ pilens Management network TLS, source allow-list (nftables and middleware), Argon2id passwords, session cookies Secure; HttpOnly; SameSite=Strict, CSRF token header, login throttling, optional TOTP, audit log
AP clients ↔ Pi Wi-Fi network nftables drops everything except DHCP, DNS and ICMP. Management is unreachable even through the uplink address.
pilens ↔ pilens-helper pilens process Unix socket 0660 root:pilens. SO_PEERCRED uid check. Fixed operations only. The helper re-validates the full config. Requests are capped at 1 MiB.
Packet data ↔ parsers Anyone on the wire / uploaded files Our pcap/pcapng readers bounds-check every length (16 MiB block cap). tshark runs via rlimit-exec (RLIMIT_AS, RLIMIT_CPU, no core dumps, no_new_privs) with a timeout and empty environment.
Packet data ↔ browser Packet content Rendered as text nodes by React. No dangerouslySetInnerHTML and no HTML preview. CSP default-src 'self', frame-ancestors 'none'.
Uploads Uploaded file Size limit enforced while streaming. Magic-number check (pcap/pcapng only, no archives, so no decompression bombs). Server-generated file names, so no path traversal. Session IDs are strictly whitelisted (^[0-9TZ]{16}-[0-9a-f]{6}$).

Privilege separation

  • pilens (user pilens): no root and no ambient capabilities. Capture uses dumpcap, whose file capabilities are granted to group wireshark, which is Debian's standard model. The unit's CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN limits what any child can ever gain. NoNewPrivileges cannot be set because it would disable dumpcap's file capabilities. ProtectSystem=strict leaves only /var/lib/pilens writable.
  • pilens-helper (root): about 400 lines. It can write the PiLens NM keyfile, its own state directory and ip_forward; run nft, nmcli, iw 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
Management TLS key /var/lib/pilens/tls/mgmt.key (0600) Never exported. Separate from any future interception CA.

Data protection

  • 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.
  • Exports, deletions, configuration changes, logins (including failures), password/TOTP changes and setup are recorded in the audit log.
  • No telemetry. The only outbound traffic the appliance generates is what the OS already does (apt, NTP) and the DNS forwarding it performs for AP clients.

Reviewed attack classes

Class Mitigation
Command injection All execs use argv lists, never a shell. Filter strings are passed as single arguments and checked for control characters. Interface names are matched against ^[A-Za-z0-9_.-]{1,15}$.
Config injection (dnsmasq/NM keyfile) Every rendered value is strictly validated (SSID charset, hostnames as RFC 1123 labels, IPs parsed), so newlines cannot reach a config file.
SQL injection Parameterised queries only
Path traversal IDs are server-generated and whitelisted. Segment file names come from the database, not the client.
XSS via packet content React text rendering, CSP, no HTML rendering of captured data
SSRF The server makes no outbound requests based on user input
Auth bypass Every /api/* route except health, setup status, first-admin creation and login goes through requireAuth. The setup endpoint needs the setup token, and the INSERT is conditional on no user existing (race-safe).
Resource exhaustion Bounded SSE queues (slow clients drop updates, never capture). Bounded tshark feed. Per-process rlimits. Upload and request size limits. Storage quota and reserve. Filter cache capped at 16 entries.
Malicious pcap Bounds-checked block reader, fuzz-style malformed-input tests, tshark resource limits
Lock-out The API refuses a management configuration that would exclude the caller's own address

Dependency inventory: Go modules modernc.org/sqlite, golang.org/x/crypto (Argon2id) and gopkg.in/yaml.v3; npm packages react and react-dom, plus vite and typescript at build time. Run govulncheck ./... and npm audit before each release.