- portals: replace zipfile with struct+zlib ZIP reader (python3-light has
no urllib; import endpoint was dead on device)
- dns hijack: uci add_list/del_list for dhcp.@dnsmasq[0].address (list
option; plain set was silently dropped from generated dnsmasq config)
- radio1: bridge attack APs into br-lan via network.brlan.ports so
victims get DHCP/portal reach; wlan1ent runtime-bridged after hostapd
verify (retry loop may recreate the iface)
- capture: auto-start pinned wlan1mon pcap on 5GHz WPA deploy, teardown
on stop; loot flows via hc22000 export (crack-verified end-to-end)
- enterprise: pineapd restart after ctrl link + re-assert PineAPE toggles
Documented residual: pineapd refuses forwarding from foreign hostapd
instances (broken pipe), and daemon set_ap rejects radio1 names - so
hostap_handshake rows for radio1 twins and enterprise cred tables cannot
populate without a Hak5 firmware change.
New tests/test_validation_fixes.py covers each fix (TDD); full suite
(29 modules) green.
- Recon AP focus sidebar: 'Deauth All Clients' with engagement-scope confirm
- Deauth Targeting card: 'Deauth All' behind the same scope confirmation
- New POST /api/attacks/deauth/bulk (max 32 targets, per-target results)
- Evil Portal tab: import EvilPortalNano-format portal zips into
/mmc/mk8/portals, serve active portal on port 80 to unauthenticated
clients via a minimal PHP shim, capture all form POSTs (.logs in stock
MyPortal.php format + captures.jsonl), dnsmasq address=/#/ DNS hijack
- OpenAP: Evil Portal template dropdown (greyed when none), activated with
the attack and stopped with it
- Monitor Capture fix: iface-less status now reports whichever monitor is
actually capturing; pcap dir mkdir'd; tcpdump stderr surfaced instead of
discarded
- deauth/kick: use full PINEAPPLE_DEAUTH_CLIENT hak5cmd app name (BUG 1)
- _allow_all_ssids: PINEAPPLE_NETWORK_FILTER_MODE deny so karma lets
clients associate after deploy (BUG 2)
- enterprise inner EAP (BUG 3), two root causes found by live experiment:
- hostapd never wildcard-matches a bare `*` identity for phase-2
lookups; eap_users now uses quoted empty prefix `""* ... [2]`,
which prefix-matches any inner identity
- pineape_auth_pass=1 forwards inner EAP to pineapd, which has no
standalone responder; deploy forces 0 and stop restores 1
Residual: plaintext MSCHAPv2 capture is firmware-blocked on 24.10.1
(MSG_DEBUG compiled out of the karma wpad; `-f` silently ignored);
documented in code comments and the validation report.
- ISSUE 4: point pineapd.@hostapd[0].mgmtiface at wlan1wpa during 5 GHz
WPA deploys so handshakes/loot populate; cleared on stop
- ISSUE 5: radio0 set_ap path polls 90 s across the wifi-reload window
and retries set_ap once if the iface still has not appeared
- ISSUE 6: skills docs use `llc && eth.type == 0x888e` (firmware tcpdump
matches 0 frames on `eapol`)
- ISSUE 7: capture state helper detects dead-pid / iface-down, cleans up,
status reports {running:false, stale:true} instead of zombie running
- tests: fix global os.path monkeypatch leaks between test modules that
broke test_mk8_events/test_reliability_api under discovery; add
regression coverage for all fixes above (463 tests green)
Live validation evidence and newly discovered firmware quirks
(DEVICE_FILTER_DELETE no-op, dropbear rate limiting) recorded in
docs/validation/2026-08-23-pineap-validation-report.md.
- Remove the Passive/Active/Advanced mode card from the PineAP dashboard
and move the RF Role (radio1) card into its slot, along with the now
dead mode state machinery and mode-card-only CSS.
- Sort recon HTML report access points by signal strength (strongest first).
Retry and serialize pineapd/hak5 calls, queue virtual-pager keys, and grey out buttons until the pager finishes. Deploy now installs python3-light after factory firmware. Bump version to 1.3.2.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Env check is read-only when state is sane: no pineapd command-socket
writes, no live pool-list commits, no wifi reload; pineapd restarts only
when a runtime-sensitive UCI value changed or the daemon was down
- Failed monitor repairs now fail the startup contract instead of being
reported as fixed; runtime pool state is read from active config
- Enterprise AP recovery runs only on device boot (PAGER_WEBUI_BOOT), not
on every web-service restart
- serve() gates the HTTP port on startup checks with bounded retries and
shuts down cleanly on SIGTERM/SIGINT; the recon watchdog waits
interruptibly
- Recon uses a bounded userspace channel scheduler that drives both
monitor radios over non-DFS channels, with preflight verification,
serialized starts, and per-cycle error reporting
- payload.sh waits for real readiness on start, fully removes the boot
service (stop + disable + delete) on stop, and surfaces a
stopped-but-enabled boot service; deploy.sh refreshes the installed
init script even when the service is stopped
- Bump version to 1.3.1
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- STA uplink disable now uses uci commit + ip link set wlan0 down instead of
wifi reload, so the env check never bounces radios/APs/drops monitors
- Removed the wlan0mon 'hop is off' warning: field-verified hop=0 is normal
(wlan1mon also reports 0 while scanning both bands); the STA/AP pinning
checks are the real signals
- recon/status drops wlan0_hopping; UI keeps wlan0_sta + wlan0_pinned pills
The stock STA client interface (wlan0) holds phy0's channel, pinning wlan0mon
so 2.4GHz recon captures nothing (verified: iw set channel -> Resource busy
until wlan0 is down). env_check now disables it (uci wireless.dummy_radio0
disabled=1 + wifi reload) at startup and /api/recon/status exposes wlan0_sta
so the recon page can warn if it regresses.
The band UCI config looked correct but pineapd was not hopping wlan0mon at
runtime, so 2.4GHz recon results were entirely absent. env_check now parses
_pineap INTERFACE LIST and warns when wlan0mon hop is off; /api/recon/status
exposes wlan0_hopping and the recon scan bar surfaces it.
- env_check(): verifies daemon/pineapd/UCI/monitors/recon DB, auto-fixes
fixable issues and re-verifies; forces runtime SSID-pool broadcast off to
match the UI (kills the 'pool on but UI shows off' gap)
- server.py --env-check CLI; payload.sh runs it verbosely before starting,
aborts on core failure
- serve() runs the check at every startup (boot + procd respawn)
- /api/health exposes env report + pool_runtime; /api/recon/status exposes
wlan0_pinned (2.4GHz under-sampling warning); recon page warns when a
radio0 AP pins wlan0mon
- recon start failures now include the daemon reason in the UI error
- shared stabilization refactor (_stabilize_uci/PINEAPD_SAFE_UCI/_raise_monitors)
- tests: test_env_check.py (18) + health/recon updates
- Version 1.1 -> 1.2 in the hak5 manifest and MCP serverInfo.
- Removed leaked personal details from process docs: device root password,
the user's personal iPhone SSID (authorized-test target), the local
/Users/... checkout path, and the device IP where it appeared alongside
the password. Replaced with <device-password>, <authorized-test-ssid>,
<repo>, and <device-ip> placeholders.
- Landscape card mirrors Encryption: headline text removed; square 112px
ring (drawn height 112, CSS fixed square, no more ellipse) + HTML legend
(dot + label + count) for Access Points / Clients / Unassociated. The
canvas previously had no legend and the '90' headline never matched the
dominant arc; the legend makes every count visible and labeled.
- Legend markup generalized: .recon-enc-* -> .recon-chart-* shared by both
cards; shared chartLegendEntry() builder; landscape skips zero slices.
- .recon-toggle now flex-wraps (was overflowing 'Auto-collect handshakes'
past the card edge at narrow widths; card min-width inner is ~142px vs a
233px label).
Verified on device via CDP at 1200px and 860px viewports: landscape ring
112x112 square with Access Points 92 / Clients 291 / Unassociated 760;
encryption card intact; toggle label stays inside the 184px card (wraps to
2 lines, no card overflow); zero JS exceptions.
The enc canvas was drawn at 233x120 internally but .recon-chart-box canvas
forced width:100%/height:100% onto a ~233x115 box, scaling the bitmap and
flattening the ring. #recon-encryption is now a static 112px square with the
doughnut drawn at height 112, so internal and CSS pixels match (1:1) and the
circle stays circular. Verified on device: box and bitmap both 112x112,
card fits without overflow, legend intact, no JS exceptions.
- Ring and legend now derive from reconEncBucket's actual family keys
(WPA2-PSK, WPA3-Personal, WPA2-Enterprise, ...) instead of the six
nominal buckets that never matched encCounts, so the ring drew an
empty ring and the legend was always blank on WPA2-dominated data.
Ordering via RECON_ENC_ORDER; colors cycle RECON_ENC_COLORS.
- Legend div had class but no id while drawCharts looked it up with
getElementById — the population block silently never ran. Added the id.
Verified on device via CDP: legend lists Open 9, WEP 1, WPA2-PSK 44,
WPA3-Personal 20, WPA3-Enterprise 5 with per-family dot colors; no JS
exceptions.