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.
5.7 KiB
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 usesdumpcap, whose file capabilities are granted to groupwireshark, which is Debian's standard model. The unit'sCapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMINlimits what any child can ever gain.NoNewPrivilegescannot be set because it would disable dumpcap's file capabilities.ProtectSystem=strictleaves only/var/lib/pilenswritable. - pilens-helper (root): about 400 lines. It can write the PiLens NM
keyfile, its own state directory and
ip_forward; runnft,nmcli,iwandsystemctlwith 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):
mitmdumpas the unprivilegedpilensuser, 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.