Files
Mark-VIII/docs/superpowers/specs/2026-08-11-pineap-page-overhaul-design.md
T
2026-08-11 20:24:24 -07:00

163 lines
9.6 KiB
Markdown

# 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/<subpath>` 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.