What data is captured
WiFi Monitor measures network performance. Captures contain connectivity measurements, not personal data.
| Data | Purpose | Sensitivity |
|---|---|---|
| Latency samples (ICMP, UDP, TCP, HTTP, QUIC) | Round-trip time and loss measurement | Low |
| Throughput results | Download/upload speed over time | Low |
| DNS resolution timing | Name resolution performance | Low |
| Traceroute hops | Network path and per-hop latency | Medium |
| WiFi signal (RSSI, noise, channel) | Correlate signal quality with performance | Low |
| Gateway IP | Local network identification | Medium |
| SSID / BSSID | WiFi network identification | Medium |
| Location label | User-provided tag (e.g. "SFO→JFK") | Medium |
| Network label | User-provided tag (e.g. "United WiFi") | Medium |
| VPN status | Whether VPN tunnel is active | Low |
No data ties to a specific user identity. No browsing history, credentials, device identifiers, or personal information is collected.
Privacy controls
You control what network-identifying information is included when uploading captures. Local capture files are never modified.
Upload privacy modes
| Mode | SSID / BSSID | Network / Location | Gateway IP | Traceroute Hops |
|---|---|---|---|---|
include |
Sent as-is | Sent as-is | Sent as-is | Sent as-is |
hash |
SHA-256 truncated | SHA-256 truncated | SHA-256 truncated | Per-hop IP hashed |
omit |
Removed entirely | Removed entirely | Removed entirely | IPs replaced with *.*.*.* |
Set via --privacy {include,hash,omit} on the CLI, or in Settings (⌘,) under "Network Information" in the macOS app. Your choice persists across sessions.
Privacy transforms are applied in memory before upload. Your local CSV files always contain the original data so your own analysis isn't affected.
Upload security
Captures are uploaded through a Cloudflare Worker proxy that enforces multiple controls.
Server-side controls
- Rate limiting — 20 uploads per hour per IP address
- Size cap — 100 MB maximum per file
- Filename validation — must match the expected capture filename pattern
- Content-Length required — streaming uploads without declared size are rejected
- Audit logging — each upload is logged (IP addresses are masked)
Client-side protections
- Captures are gzip-compressed before upload (~5–6x compression)
- Upload state is tracked locally to avoid duplicates
- Pending uploads (e.g. laptop closed mid-capture) are caught up automatically on the next run
Download validation
When pulling shared captures (via --pull), files are validated before writing to disk:
- Filename must match the expected pattern
- Content must be valid UTF-8 with the expected CSV header
- Decompressed size is capped at 200 MB
- Files that fail validation are rejected with a warning
App security
macOS app
The macOS app is signed with a Developer ID certificate and notarized by Apple. macOS Gatekeeper verifies the signature before allowing the app to run.
Updates are delivered via Sparkle with EdDSA signature verification. Automatic update checks are disabled by default — updates are only checked when you choose "Check for Updates" from the menu.
The app runs with macOS hardened runtime protections. Two entitlements are required for the embedded Python interpreter:
- Disable library validation — required because Python extension modules aren't signed with our Developer ID
- Allow unsigned executable memory — required for Python's ctypes and JIT compilation
Both are standard for any macOS app that embeds or spawns a Python interpreter.
The Python capture process is launched with explicit argument arrays (no shell interpolation). User-provided strings (location, network labels) are passed as direct process arguments, preventing shell injection.
iOS app
The iOS app is a native Swift reimplementation — no Python subprocess, no embedded interpreter. It runs in iOS's standard app sandbox.
- TestFlight distribution — all builds are signed by Apple and distributed through TestFlight. No sideloading required.
- Location permission — requested once for WiFi SSID access (iOS requires location authorization to read the current network name). Used only for capture metadata, never tracked or transmitted beyond the capture file.
- No background execution — captures only run while the app is in the foreground. No background location, no background network access.
- ATS exception — a single App Transport Security exception allows HTTP (not HTTPS) to
example.com, used as a canary target for transparent proxy detection. - Non-exempt encryption — the app declares
ITSAppUsesNonExemptEncryption: false(standard TLS only, no custom cryptography).
Windows app
The Windows app is built with Tauri 2.0 (Rust backend + HTML/JS frontend). The Python capture process runs as a subprocess, same as macOS.
- Minimal permissions — Tauri capabilities are restricted to
core:default,opener:default, andupdater:default. No filesystem, shell, or network permissions beyond what the subprocess needs. - Auto-updates — delivered via Tauri's built-in updater with signature verification.
- Subprocess isolation — same explicit argument array approach as macOS, preventing shell injection.
Data storage
Local
- Captures are saved to
captures/in the project directory - Upload configuration is stored in
~/.wifi-monitor/r2.jsonwith 0600 permissions (owner-only read/write) - Upload tracking is stored in
~/.wifi-monitor/uploaded.json
Remote
- Uploaded captures are stored in a Cloudflare R2 bucket, served through a Cloudflare Worker
- The capture listing and index are restricted to VPN — only users on the corporate network can browse all captures
- Individual results pages are accessible by direct link only — capture IDs include a random component to prevent guessing
- Crawlers are blocked from indexing capture pages (via
robots.txtandnoindexmeta tags) - Brute-force scanning is rate-limited — excessive 404s on capture paths trigger a temporary IP block
- Each uploader gets a unique 128-bit identifier, but this is not linked to any personal identity
Dependencies
The macOS and Windows apps bundle Python with pinned dependencies to prevent supply-chain risks from unpinned PyPI downloads:
- Core (always bundled):
requests,boto3,aioquic,netifaces2— pinned to exact versions - Optional (analysis only):
matplotlib,numpy— not required for capture or upload, installed separately if needed - Dependencies are managed via uv
The iOS app has zero third-party dependencies — all probes are implemented using only Apple system frameworks (Foundation, Network, CoreWLAN).
Questions
For security questions or to report a concern, visit the About & Contact page. For general questions or feedback, reach out via the same page.