# Pager WebUI — Design Spec (v1) - **Date:** 2026-08-10 - **Status:** Approved (pending written-spec review) - **Owner:** Hak5 WiFi Pineapple Pager expansion project ## 1. Goal Build a Mark VII-style web management UI that runs **on the WiFi Pineapple Pager** itself, with core-parity featureset (Dashboard, PineAP, Recon, Handshakes/Loot, Payloads, Logs, Settings, and a bottom-docked terminal), packaged as a Payload-Portal-installable payload and deployed over SSH/SCP. The UI is reached at `http://172.16.52.1:8080/` (verified free on the device). ## 2. Verified Research Basis (live devices) ### Pager (172.16.52.1) - MediaTek MT76x8 SoC, OpenWRT-based `Pineapple Pager 24.10.1` (`ramips/mt76x8`, `mipsel_24kc`, Linux 6.6.86). - Full root SSH (`root` + device password). `opkg` available. - Persistent 4GB MMC: `/root` -> `/mmc/root`; payloads live under `/root/payloads/`, loot under `/root/loot/`. - Tools present: `python3` (3.11.14), `sqlite3`, `iwinfo`, `hostapd_cli`, `ubus`, `uci`, `curl`, `nc`, `wget`. - Firmware is U-Boot **signed** — firmware modification is out of scope; no stock files are touched. ### Hak5 daemon (`/pineapple/pineapple`, Go) - HTTP API on `:1471` and an unauthenticated Unix socket `/tmp/api.sock` serving the same router. - Auth: `POST /api/login` (`{"username":"root","password":...}`) returns `{"token": ...}`. Requests authorize with `Authorization: Bearer ` or the `AUTH_` cookie (serverid from `/api/api_ping`, e.g. `001337AEE050`). - Existing API used by the stock Virtual Pager: `api_ping`, `login`, `payloads/portal/{index,refresh,updates,/install,/remove}`, `loot/archive`, `loot/zip`, `files/zip/root/loot/handshakes`, `terminal/openWs` (WS), `pager/input/keys.ws` (WS), `pager/display/screen.ws` (WS). - Terminal WS `/api/terminal/openWs`: plain-text WebSocket (keystrokes in, output out). Requires the `AUTH_*` cookie (verified 401 without, 101 with). Accepts cross-origin handshakes. Cookies are host-scoped and **port-agnostic**, and the `:8080` page and `:1471` host are the same site, so a cookie set for host `172.16.52.1` is sent on the WS handshake from the `:8080` page. The docked terminal therefore connects **directly** to the daemon WS; a server-side relay is the documented fallback. ### Control channel (payload interface) - `/usr/bin/hak5cmd` (C++, protobuf client to the daemon). 49 symlinks provide every PineAP operation, e.g.: `PINEAPPLE_SSID_POOL_{ADD,ADD_FILE,START,STOP,LIST,DELETE,CLEAR,COLLECT_START,COLLECT_STOP}`, `PINEAPPLE_NETWORK_FILTER_{MODE,ADD,ADD_FILE,DELETE,LIST,CLEAR}`, `PINEAPPLE_DEVICE_FILTER_{MODE,ADD,ADD_FILE,DELETE,LIST,CLEAR}`, `PINEAPPLE_MIMIC_{ENABLE,DISABLE}`, `PINEAPPLE_DEAUTH_CLIENT`, `PINEAPPLE_EXAMINE_{BSSID,CHANNEL,RESET}`, `PINEAPPLE_HOPPING_{START,STOP}`, `PINEAPPLE_SET_BANDS`, `PINEAPPLE_RECON_NEW`, `PINEAPPLE_LOOT_ARCHIVE`. - Verified: commands run from CLI over SSH (read-only ones confirmed). ### Recon data - SQLite `/root/recon/recon.db`: tables `scan`, `wifi_device`, `ssid`, `handshake`, `hostap_chalresp`, `hostap_basic`, `hostap_client`, `hostap_handshake`. ### PineAP config - UCI file `/etc/config/pineapd` (sections: `pineapd`, `hostapd`, `ssidpool`, `ssid_filter`, `mac_filter`, `interface wlan0mon/wlan1mon/wlan2mon`). - Reload trigger exists (`/etc/init.d/pineapd reload`, `pineap_reload`). ### Precedents (payloads already on device) - `nautilus` (`user/remote_access/nautilus`): web UI payload with init script (`nautilus.init`, START=99, procd), python3 proxy, foreground/background run modes, `PAYLOAD_GET_CONFIG` persistence. - `virtual_pager_enhancer`: uhttpd+CGI on port 4040 (init script pattern). ### Mark VII blueprint - 113 `/api/*` endpoints extracted from its Angular bundle: dashboard, `pineap/*` (clients, kick, deauth, filters, handshakes, settings, ssids, summary), `recon/*` (scans, start/stop/status, tags), logging, settings/ networking, modules, device, terminal (xterm.js bottom-docked panel). ## 3. Repo Layout Dev workspace: `C:\Users\root\Documents\Pineapple\pager-webui\` ``` pager-webui\ ├── payload\user\general\pager-webui\ │ ├── _hak5_manifest.json │ ├── payload.sh # run-mode installer (nautilus pattern) │ ├── pagerwebui.init # OpenWRT init template (START=99, procd, respawn) │ ├── server.py # Python3 stdlib HTTP+WS+JSON API backend (:8080) │ └── www\ # vanilla JS SPA + xterm.js bundle ├── scripts\ │ ├── deploy.ps1 # build portal zip + SCP/install to Pager │ └── dev.ps1 # local dev server w/ proxy to Pager ├── docs\specs\ # design specs └── README.md ``` ## 4. Packaging & Deployment - **Payload format** mirrors `nautilus` exactly: self-contained directory `user/general/pager-webui/` with `payload.sh`, `pagerwebui.init`, `server.py`, `www/`. - **Sideload (v1 delivery):** `scripts/deploy.ps1` builds a portal-format zip (`payload-.zip`) and installs it to `/root/payloads/user/general/pager-webui/` on the Pager over SCP. The payload then appears in the on-device Payloads menu and the Virtual Pager's portal view — installable like any Payload Portal payload. - **Future portal distribution:** packaging is drop-in ready for a PR to `hak5/wifipineapplepager-payloads`; the build script generates the manifest `time` / `last_hash` / `zip` fields. ### `payload.sh` run flow (nautilus pattern) 1. If `/etc/init.d/pagerwebui` exists and `running`: show URL `http://172.16.52.1:8080/`, offer `CONFIRMATION_DIALOG "Stop service?"`; on confirm -> `stop`, `disable`, remove init script; exit. 2. Read `PAYLOAD_GET_CONFIG pager_webui auto_mode` / `run_mode`. If `auto_mode=true`, skip prompt (background if `run_mode=background`, else foreground). 3. Else `CONFIRMATION_DIALOG "Run as background service?"`: - **Yes -> background:** `cp pagerwebui.init /etc/init.d/pagerwebui`, `chmod +x`, `enable`, `start` (procd runs `python3 server.py`, respawns on crash). Boot-persistent via rc.d symlinks. - **No -> foreground:** spawn `python3 server.py` with `/tmp/pagerwebui.pid`, show URL, loop `WAIT_FOR_INPUT` until B/Escape, `trap cleanup` kills the server and removes the pid file. 4. Guard: python3 is present on current firmware; init script fails gracefully if missing. - **Uninstall:** the stop path above, plus deleting the payload directory (portal semantics). - **Boot persistence:** rc.d symlinks (overlay). Survives reboots and firmware upgrades; after a firmware upgrade (which wipes overlay) re-run the payload to re-enable — same caveat as nautilus. Foreground mode needs no persistence. ## 5. Backend (`server.py`) Python 3.11 stdlib only (`http.server`, `sqlite3`, `subprocess`, `json`, `socketserver`, hand-rolled minimal RFC6455 for the live-update WS). Binds `0.0.0.0:8080`. Single origin serves the SPA and the API. | Endpoint | Method | Implementation | |---|---|---| | `/api/login` | POST | validate via daemon `:1471/api/login`; on success return token and `Set-Cookie: AUTH_` (host `172.16.52.1`, no `Domain`, `Path=/`, `HttpOnly`, `SameSite=Lax`) so docked terminal + loot downloads on `:1471` are authorized cross-port | | `/api/api_ping` | GET | serverid + version (mirror daemon) | | `/api/status` | GET | battery/power via sysfs, WiFi/interfaces via `iwinfo` + `ip`, firmware/daemon versions, disk via `df`, uptime | | `/api/pineap/settings` | GET/POST | read/write UCI `/etc/config/pineapd` (mimic, collect probes, advertise, collect handshakes, random MAC, wigle, bands) + `pineap_reload` | | `/api/pineap/ssids` | GET/POST | SSID pool read/write via `hak5cmd` | | `/api/pineap/ssidpool/{start,stop,collect_start,collect_stop}` | POST | via `hak5cmd` | | `/api/pineap/filters/client` | GET/POST | device filter mode + list via `hak5cmd` | | `/api/pineap/filters/ssid` | GET/POST | network filter mode + list via `hak5cmd` | | `/api/pineap/clients` | GET | associated clients via `iwinfo assoclist` / `hostapd_cli` on `wlan0open`/`wlan0wpa`/`wlan0mgmt` | | `/api/pineap/clients/kick` | POST | deauth via `hak5cmd` + auto-add to deny filter (Mark VII behavior) | | `/api/pineap/deauth/client` | POST | `PINEAPPLE_DEAUTH_CLIENT` | | `/api/recon/{start,stop}` | POST | `hak5cmd` `RECON_NEW` / stop | | `/api/recon/scans` | GET | scans list from `recon.db` | | `/api/recon/scans/` | GET | APs/clients/handshakes for a scan from `recon.db` | | `/api/pineap/handshakes` | GET/DELETE | list/delete `/root/loot/handshakes` | | `/api/loot/zip` | GET | zip download (wraps daemon `:1471` with cookie) | | `/api/loot/archive` | POST | wraps daemon `:1471` | | `/api/payloads/index`, `/api/payloads/install`, `/api/payloads/remove`, `/api/payloads/refresh` | GET/POST | wrap daemon portal endpoints | | `/api/logging/system` | GET | `logread` (filtered, tail) | | `/api/logging/pineap` | GET | pineapd/daemon log sources | | `/api/device` | GET | hostname, MACs, model | | `/api/settings/hostname` | GET/POST | `uci` network hostname | | `/api/settings/password` | POST | change the device root password via BusyBox `passwd` stdin | | `/api/settings/ntp` | GET/POST | UCI `system` `timeserver` (enabled + servers) + restart `sysntpd` | | `/api/ws` | WS | live push: status/clients/recon deltas every ~2s | | `/api/terminal/openWs` | WS | same path on our origin; client connects direct to daemon `ws://172.16.52.1:1471/api/terminal/openWs` (cookie-authed). Fallback: server relays | ### Auth model - All endpoints (except `/api/login`) require a valid session cookie set by `/api/login`. - Token is never logged or stored server-side beyond the session check; the backend validates each request by checking the session cookie against the daemon's token (a lightweight session store in `/tmp/pagerwebui.session`). - Commands are executed with argument lists (no shell string interpolation) to prevent injection. ## 6. Frontend (vanilla JS SPA) - Static files in `www/`: `index.html`, `css/app.css`, `js/app.js` (or split modules), `js/xterm.js` + `js/xterm-fit.js` (bundled copies), `assets/`. - Mark VII-style chrome: dark theme, top bar (logo, live status, **Terminal button**), nav rail, hash-based routing (`#/dashboard`, `#/pineap`, `#/recon`, `#/handshakes`, `#/payloads`, `#/logs`, `#/settings`). - **Dashboard:** status cards + live counters (battery, clients, APs, handshakes) fed by `/api/ws`. - **PineAP:** settings toggles (mimic/advertise/collect/handshakes/random MAC/ wigle/bands), SSID pool CRUD + collect/start/stop, client & SSID filters (mode + list CRUD), client list with kick. - **Recon:** scan list, AP/client tables from `recon.db`, start/stop buttons. - **Handshakes/Loot:** list, download zip, delete, archive. - **Payloads:** portal list + install/remove/refresh. - **Logs:** system + pineap logs with tail/poll. - **Settings:** hostname, NTP, password, webUI prefs (poll interval, accent), service status (background/foreground). - **Terminal:** bottom-docked xterm panel toggled by the top-bar button (Mark VII parity). Connects `ws://172.16.52.1:1471/api/terminal/openWs`. Default 80x24; resize behavior validated during implementation (daemon may ignore resize; fallback fixed size with xterm `fit` disabled). ## 7. Security & Resilience - Auth: device password validated through the daemon; HttpOnly session cookie; no plaintext secret storage; state-changing endpoints all behind login. - No stock files modified; no `opkg` changes; reversible via `payload.sh` stop/uninstall; factory reset / firmware recovery remain available. - `server.py` failure modes: procd respawn in background mode; foreground mode cleans up on exit; `recon.db` opened read-only; subprocesses use arg lists. - Binds `0.0.0.0:8080` (same exposure class as stock `:1471` / `:7681`). ## 8. Testing & Verification - Windows dev loop: `scripts/dev.ps1` runs the SPA + API locally and proxies to the Pager. - PowerShell-driven API tests against `:8080`: login, status, each read/write endpoint before frontend wiring. - On-device smoke tests per page: status, pool CRUD, filter toggles, recon start/scan read, handshake listing, portal install/remove, terminal I/O, background vs foreground modes, reboot persistence. - Recovery drill: uninstall, re-install, foreground-stop, firmware-upgrade caveat documented in README. ## 9. Out of Scope (v1) - `:1471` takeover/redirect. - Mark VII features without a Pager equivalent: Campaigns, Modules, Cloud C², Enterprise/EAP pages. - Physical-display screen mirror. - Publishing a PR to the official payloads repo (packaging ready; submission later). ## 10. Open Risks (mitigated during implementation) - Client-list source validation: `iwinfo assoclist` / `hostapd_cli` against the Pager's hostapd interfaces (`wlan0open`, `wlan0wpa`, `wlan0mgmt`). - Minimal RFC6455 WebSocket server correctness. - Daemon terminal resize support (fallback to fixed 80x24).