# Design: Mark VII PineAP Page Port for the Pager WebUI Date: 2026-08-11 ## Goal Replace the current pager-webui PineAP page with a faithful replica of the WiFi Pineapple Mark VII PineAP view, fully functional on the WiFi Pineapple Pager, independent of campaign needs. Also fix the side-rail icon (location pin -> wifi). ## Background / findings - The current PineAP page (tabs: Open, Clients, Filtering, APs, Impersonation) looks nothing like the Mark VII page and its backend is broken on the Pager: - `server.py` reads Mark VII uci keys (`pineapd.pineapd.mimic`, `collect_handshakes`, ...) that do not exist in the Pager's `pineapd` config, so settings never persist (live `GET /api/pineap/settings` returns only `{"bands":"2.4"}` plus raw). - The filter-mode endpoints call `hak5cmd` commands without the required argument, returning usage text instead of the mode. - The Pager's daemon (`/pineapple/pineapple`, listens on `:1471`) natively exposes a Mark VII-style PineAP REST API over a root-only unix socket at `/tmp/api.sock` (raw HTTP/1.1, no auth - socket permission is the boundary). pager-webui already uses this socket (`daemon_sock_call`, e.g. recon start/stop). Confirmed live routes: | Route | Method | Purpose | |---|---|---| | `/api/pineap/get_config`, `/set_config` | GET/POST | log flags, handshake path, ssidpool autocollect | | `/api/pineap/hostapd/get_config`, `/set_config` | GET/POST | master PineAP on/off, Evil WPA (`wpa_ifaces`), Evil Enterprise (`pineape_*`) | | `/api/pineap/hostapd/enable_pineap` | POST | master enable | | `/api/pineap/hostapd/enable_pineape` | POST | evil enterprise enable | | `/api/pineap/hostapd/enable_pineape_auth` | POST | enterprise auth-pass capture | | `/api/pineap/mimic/enable`, `/disable` | POST | karma | | `/api/pineap/examine/bssid`, `/reset` | POST | targeted handshake capture | | `/api/pineap/ssidpool/list`, `/add`, `/clear`, `/disable`, `/disable_collect` | GET/POST | SSID pool + advertise/collect | | `/api/pineap/ssidfilter/get_config`, `/set_config`, `/allow/*`, `/deny/*` | GET/POST | SSID filter mode + lists | | `/api/pineap/macfilter/get_config`, `/set_config`, `/set_mode`, `/allow/*`, `/deny/*` | GET/POST | MAC/client filter mode + lists | | `/api/pineap/interfaces/get`, `/set_interface`, `/set_interface_bands` | GET/POST | monitor interface hop/inject/bands | | `/api/pineap/log/recon|pcap|wigle/start|stop` | POST | logging control | | `/api/pineap/recon/new` | POST | new recon scan | - The Pager natively supports Evil WPA (rogue AP on `wlan0wpa`: SSID, PSK, encryption WPA2-PSK / WPA3-SAE / WPA3-OAE, hidden, enabled) and Evil Enterprise (EAP, auth-pass capture), confirmed via the daemon binary strings and the native (virtual) pager menu. - The daemon lacks routes for: connected clients (use `iwinfo`), kick (`hak5cmd PINEAPPLE_DEAUTH_CLIENT`), nearby-AP scan (`iwinfo scan`), and handshake file listing (`/root/loot/handshakes/`). pager-webui already implements these. ## Architecture ### Backend (`payload/user/general/pager-webui/server.py`) 1. Add a generic proxy handler `h_pineap_proxy` that forwards the browser request (method + JSON body) verbatim to the daemon socket path `/api/pineap/` using the existing `daemon_sock_call`, and returns the daemon's JSON response. Register it for the whole native tree (`get_config`, `set_config`, `hostapd/*`, `mimic/*`, `examine/*`, `ssidpool/*`, `ssidfilter/*`, `macfilter/*`, `interfaces/*`, `log/*`, `recon/new`) so every native capability is reachable 1:1 through pager-webui's authenticated `/api/pineap/*` namespace. 2. Keep the custom endpoints that have no daemon route: `clients` (iwinfo), `clients/kick` (deauth), `aps` (iwinfo scan), `handshakes` (loot listing + download/delete). 3. Add Enterprise data endpoints: list + clear of `hostap_basic` / `hostap_challenge` rows from `recon.db` (via the sqlite3 CLI, the same mechanism the recon page uses). 4. Delete the broken Mark VII-uci settings handlers (`SETTING_MAP`, `_uci_map`, `h_pineap_settings_get/post`) and the filter handlers that returned hak5 usage text (`h_filter_get/post`); replace with the proxy. 5. On daemon socket failure return 502 with a JSON error; never crash. ### Frontend (`www/js/app.js`, `www/js/views.js`, `www/css/app.css`) 1. Rail icon: change the PineAP rail item from `pineap` (map-marker) to the existing `wifi` icon in `icons.js`/`app.js` rail definition. 2. Rebuild the PineAP page as 8 tabs matching the Mark VII structure (using the existing vanilla-JS SPA design language - material-style cards, toggles, tables, tab bar): | Tab | Route | Content | |---|---|---| | PineAP | `#/pineap` | Mode badge (Passive/Active/Advanced from `pineap_disabled`+mimic+evilwpa/enterprise), description, quick toggles (Capture SSIDs to Pool, Advertise AP Impersonation Pool, Randomize Source MAC), alert-payload info note, 4 status cards (Karma / Open Network / Evil WPA / Evil Enterprise) with Configure links | | Open AP | `#/pineap/open` | Enable PineAP (master), Karma, Logging group (handshakes/partial/pcap/wigle/recon), Capture SSIDs, Advertise Pool, Randomize MAC, AP Channel select, PineAP MAC + Target MAC (read-only) | | Evil WPA | `#/pineap/evilwpa` | SSID, passphrase, encryption select (WPA2-PSK / WPA3-SAE / WPA3-OAE), Hidden, Enabled; handshake capture card (Examine BSSID + start/stop/reset); captured handshakes table | | Enterprise | `#/pineap/enterprise` | Enabled, Auth Pass Capture; Basic Data + Challenge Data tables with Clear | | Impersonation | `#/pineap/impersonation` | SSID pool textarea editor + Add + Clear; Advertise + Randomize toggles; pool start/stop/collect | | Clients | `#/pineap/clients` | Connected clients table (MAC/interface/RSSI) + Kick, 5s auto-refresh | | Filtering | `#/pineap/filtering` | Client Filter + SSID Filter cards: Allow/Deny mode + line-based list textareas with add/delete/clear | | APs | `#/pineap/aps` | Kept as-is (iwinfo scan table, 10s refresh) | 3. Mark VII-only controls with no Pager equivalent (Autostart, Beacon Responses, Beacon Intervals, enterprise cert generation) are omitted rather than greyed out; Client Connect/Disconnect Notifications are shown as an info note (the Pager handles these via alert payloads natively). 4. Register the new routes in the `routes` map and add the tab bar entries + CSS. ## Data flow Browser -> pager-webui `/api/pineap/*` (session-authenticated) -> `daemon_sock_call` -> `/tmp/api.sock` -> Pager daemon -> `pineapd`/`hostapd`. Mutating calls show a toast; failures surface a friendly error. ## Error handling - Daemon socket unavailable -> HTTP 502 `{error}`; frontend shows a toast and never renders a dead page. - Unknown/unsupported subpath -> 404 through pager-webui. - Enterprise tables empty -> "no data" empty states (same pattern as Recon). ## Testing - Rewrite `tests/test_pineap_{settings,pool,clients,aps}.py` and add `test_pineap_evilwpa.py` / `test_pineap_enterprise.py` / `test_pineap_filtering.py` against the proxied shapes, mocking the daemon socket call (module-level monkeypatch per the existing test conventions). - Run the README's per-file unittest loop on Windows. - On-device smoke test after `deploy.ps1`: walk every tab, verify toggles persist across a reboot. ## Out of scope - Firmware changes; the daemon API internals are used as-is. - A PR to `hak5/wifipineapplepager-payloads` (packaging is drop-in ready). ## Open items (resolved during implementation) - Exact `hostapd/set_config` Evil WPA field names (SSID/PSK/encryption/ hidden/enabled) - probe on the live device. **Resolved:** Evil WPA lives in UCI `wireless.wlan0wpa`. The daemon route `PUT /api/settings/wifi/set_ap` takes `{"configs":[{interface, ssid, enctype, enabled, hidden, key, channel(int)}]}` (full replace; `channel` must be int). `PUT /api/settings/wifi/get_ap` is a stub (always `{"interfaces":[]}`), so the webui reads UCI `wireless.wlan0wpa`/`wlan0open` directly. `enctype` values: `psk2`, `psk`, `sae`, `none` (not `wpa2`). Applying reconfigures the radio and briefly drops the management connection. - `ssidpool` list response shape (fallback: `hak5cmd PINEAPPLE_SSID_POOL_LIST`). **Resolved:** `GET /api/pineap/ssidpool/list` is a stub (`{"success":true}`); the pool is stored base64 in UCI `pineapd.@ssidpool[0].ssid`. `hak5cmd` `PINEAPPLE_SSID_POOL_LIST/ADD/DELETE/CLEAR` all work and are used. - Randomize-Source-MAC route (`GetPineAPRandomizeMAC` internal var) - discover the backing route/field. **Resolved (partial):** no readable daemon route for the pool advertise state (`ssidpool/enable|disable` are POST; `enable` returns 500 natively because no `wlan0open` interface exists on the Pager). The webui surfaces the daemon error. - sqlite3 CLI availability for the Enterprise tables. **Resolved:** sqlite3 CLI present; `hostap_basic`/`hostap_challenge` do not exist yet in `recon.db`, so the Enterprise tab renders empty tables (webui returns `rows: []`). - Pineapd UCI required-field cycle: the daemon's `set_config`/`hostapd/set_config` are full-replaces. If a write omits required fields (`pineapd.@pineapd[0]. reconpath` etc.), `get_config` then fails and later writes stay broken. The webui merges each write over the current daemon config (or defaults when `get_config` fails) so required fields are always preserved. - Verified daemon methods: reads GET; `set_config`/`hostapd/set_config`/ `hostapd/enable_pineap`/`interfaces/set_interface`/`ssidpool/add`/`wifi/*` PUT; toggles (`mimic/*`, `examine/*`, `ssidpool/enable|disable|enable_collect| disable_collect`) POST. `ssidfilter`/`macfilter` `set_config` are PUT.