Files
Mark-VIII/docs/superpowers/plans/2026-08-20-recon-reports-enterprise-stability.md
T

20 KiB

Recon, Reports, Enterprise Certificates, and PineAP Stability Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make Recon status truthful, enrich offline HTML reports with signal maps, move Handshakes under PineAP, add self-signed Evil Enterprise certificate generation, and contain known pineapd crash loops.

Architecture: Preserve the existing single-file Python backend and vanilla-JavaScript SPA conventions. Add small pure helpers around existing Recon, report, enterprise, and health boundaries, with backend-generated state replacing frontend inference. Implement each subsystem test-first and deploy only after the complete local suite passes.

Tech Stack: Python 3.11 standard library and device python3-light, BusyBox/OpenWrt commands, OpenSSL CLI, vanilla JavaScript, inline CSS/SVG, unittest.

Global Constraints

  • Support Pineapple Pager firmware 24.10.1 and daemon SHA-256 e2cf0453d7e7d2e08bf31a242289e480d44649923a5199eed954d73df0cf1da5.
  • Do not add Python packages, frontend packages, a build system, remote assets, or inline report JavaScript.
  • Do not patch or replace /usr/sbin/pineapd.
  • Keep pool broadcast disabled on known affected and unknown builds.
  • Preserve existing authentication, same-origin checks, and API error conventions.
  • Use argv command lists, never shell interpolation, for user-supplied certificate fields.
  • Preserve unrelated worktree changes and do not stage loot/ or untracked existing certificate files unless a task explicitly requires them.

Task 1: Truthful Recon Coverage State

Files:

  • Modify: payload/user/remote_access/pager-webui/server.py:66-79, 1335-1765
  • Modify: payload/user/remote_access/pager-webui/www/js/views.js:1680-1910, 2672-2783
  • Test: tests/test_recon.py:165-512
  • Test: tests/test_env_check.py:202-324

Interfaces:

  • Produces: _recon_coverage(iface, active) -> dict with state, reason, and channel.

  • Produces: scan-state keys baseline_scan_id, scan_id, settling_until, and result_warning.

  • Produces: /api/recon/status fields coverage_24, scan_id, and result_warning.

  • Consumes: existing _recon_hopper_preflight, _iface_associated, _dummy_sta_borrowable, recon_scans_data, and _recon_scan_state lock.

  • Step 1: Add failing backend coverage tests

Add tests that assert an enabled but unassociated dummy STA is not diagnosed as starvation, a successfully borrowed dummy reports full, a failed busy probe reports current_channel_only or pinned with its observed reason, and hopper interfaces are empty after loop cleanup. Mock _iface_associated, _iface_admin_up, _run, and channel state so each test controls runtime evidence rather than UCI alone.

  • Step 2: Run the focused coverage tests and verify failure

Run: python3 -m unittest tests.test_recon.ReconHopperTest tests.test_env_check.EnvCheckTest -v

Expected: new assertions fail because status has no coverage_24 object and completed hopper state retains interfaces.

  • Step 3: Implement backend coverage state

Add a pure status helper that reports idle when no scan is active, full only when wlan0mon is in the active hopper, unavailable when the monitor is absent/down, and otherwise uses the preflight skip reason/current channel to report pinned or current_channel_only. Store preflight per-interface outcomes in _recon_hop_state; do not derive the result solely from _sta_uplink_enabled().

Clear _recon_hop_state['ifaces'] in the hopper loop's finally block while preserving the last coverage reason long enough for final status polling.

  • Step 4: Add failing scan identity tests

Add tests that mock the existing maximum scan ID before start, return a newer row after start, and assert status tracks that exact ID. Add a timeout test using a mocked clock that advances 15 seconds beyond duration and asserts result_warning is set instead of selecting the prior scan.

  • Step 5: Run scan-state tests and verify failure

Run: python3 -m unittest tests.test_recon.ReconScanStateTest -v

Expected: failures because _recon_scan_state has no baseline/result identity or settlement state.

  • Step 6: Implement result association and settlement

Before native start, query the maximum scan ID. After acceptance, store it as baseline_scan_id. During watchdog/status updates, query for the first row with a greater ID and set scan_id. At elapsed duration, allow a 15-second settlement window; after it expires without a row, set result_warning and end the run. Reset all identity fields on explicit reset/start.

  • Step 7: Update Recon frontend state

Render backend coverage_24 copy instead of the wlan0Sta && !hop24 fallback. During auto-follow, select status.scan_id even when its counts are zero. Show completion only after a tracked result exists, or show the backend result warning after settlement failure. Keep existing styling and ASCII copy.

  • Step 8: Run Recon and environment tests

Run: python3 -m unittest tests.test_recon tests.test_env_check -v

Expected: PASS.

  • Step 9: Commit Recon correctness
git add payload/user/remote_access/pager-webui/server.py payload/user/remote_access/pager-webui/www/js/views.js tests/test_recon.py tests/test_env_check.py
git commit -m "fix: report observed recon coverage and scan results"

Task 2: Firmware-Gated pineapd Containment

Files:

  • Modify: payload/user/remote_access/pager-webui/server.py:4432-4616, 4667-4814, 6982-6992
  • Modify: payload/user/remote_access/pager-webui/www/js/app.js:516-546
  • Modify: payload/user/remote_access/pager-webui/README.md:112-130
  • Modify: README.md:54-61, 112-130
  • Test: tests/test_health.py
  • Test: tests/test_env_check.py
  • Test: tests/test_robustness.py

Interfaces:

  • Produces: _pineapd_build() -> dict with firmware, sha256, and policy (affected or unknown).

  • Produces: _reconcile_pineapd_safety() -> dict listing changed keys and errors.

  • Produces: health-state keys events, quarantined, stable_since, build, and last_reconcile.

  • Produces: GET /api/health/diagnostics as a JSON download with no secrets.

  • Step 1: Add failing build-policy and reconciliation tests

Test the exact affected firmware/hash pair, an unknown hash, the five required safe UCI values, no unrelated UCI writes, one commit only when values changed, and no command-socket ping. Assert unknown builds are reported but not automatically rewritten.

  • Step 2: Run focused tests and verify failure

Run: python3 -m unittest tests.test_health tests.test_env_check -v

Expected: failures because build policy and production reconciliation do not exist.

  • Step 3: Implement build detection and safe reconciliation

Read firmware through the existing release parser and hash /usr/sbin/pineapd with sha256sum. Match the exact affected tuple. On affected builds, compare and set only:

pineapd.@ssidpool[0].disable=1
pineapd.wlan2mon.disable=1
pineapd.wlan2mon.hop=0
pineapd.wlan1mon.bands=5
pineapd.wlan0mon.bands=2

Commit pineapd once if needed. Invoke reconciliation before Mark VIII explicitly starts/restarts the daemon, not continuously while a healthy daemon is running.

  • Step 4: Add failing circuit-breaker tests

Use a mocked clock and process/SIGSEGV samples to assert three crashes in two minutes enter quarantine, Mark VIII issues no further restart, five stable minutes clear quarantine, and explicit recovery reruns reconciliation. Add pool tests for preserving 64 entries/4096 bytes and quarantining then clearing data above either limit only during a correlated crash loop.

  • Step 5: Implement bounded health recovery

Track timestamped PID/SIGSEGV events in a bounded in-memory list. Enter quarantine at three crashes in 120 seconds. Stop Mark VIII restart calls while quarantined. Clear after 300 stable seconds or explicit recovery. For an oversized pool during the threshold-crossing event, write a timestamped diagnostic copy under /root/loot/pineapd-diagnostics/, clear through the existing safe pool command, and make one reconciled restart attempt.

  • Step 6: Add diagnostics endpoint tests

Assert the download contains firmware/hash, policy, relevant UCI, monitor state, pool metrics, bounded event history, and health actions. Assert it excludes certificate private-key contents, enterprise secrets, cookies, and daemon binary data.

  • Step 7: Implement diagnostics and UI health detail

Register GET /api/health/diagnostics. Reuse Download with JSON content and attachment naming. Expand /api/health with build/quarantine/reconcile data and display concise affected/unknown/quarantined state in the existing top-bar health treatment.

  • Step 8: Correct stability documentation

Document automatic affected-build reconciliation, unknown-build behavior, passive checks, thresholds, and diagnostic preservation. Remove claims that normal health checks always clear the pool.

  • Step 9: Run health, environment, and robustness tests

Run: python3 -m unittest tests.test_health tests.test_env_check tests.test_robustness -v

Expected: PASS.

  • Step 10: Commit containment changes
git add payload/user/remote_access/pager-webui/server.py payload/user/remote_access/pager-webui/www/js/app.js payload/user/remote_access/pager-webui/README.md README.md tests/test_health.py tests/test_env_check.py tests/test_robustness.py
git commit -m "fix: contain affected pineapd crash loops"

Task 3: Self-Contained Report Signal Maps

Files:

  • Modify: payload/user/remote_access/pager-webui/server.py:1921-2174
  • Test: tests/test_recon.py:991-1191

Interfaces:

  • Produces: _report_channel_maps(aps) -> str containing zero or more escaped inline SVG sections.

  • Produces: _report_channel_map(aps, band) -> str for one of 2.4, 5, or 6.

  • Consumes: AP dictionaries from recon_scan_data and existing _esc_html, band_of, and report CSS.

  • Step 1: Add failing report-map tests

Add representative 2.4, 5, and 6 GHz APs and assert one SVG per populated band, expected labels, all four signal colors, raised-cosine path data, and escaped SSID/vendor metadata inside <title>. Assert no <script>, remote URL, or malformed SVG for missing channel/frequency.

  • Step 2: Run report tests and verify failure

Run: python3 -m unittest tests.test_recon.ReconReportTest tests.test_recon.ReconArchivesTest -v

Expected: failures because reports contain occupancy tables but no signal-map SVG.

  • Step 3: Implement pure SVG helpers

Port the existing channel-to-frequency, band-range, -100/-30 dBm scaling, +/-10 MHz width, and raised-cosine sampling math to Python. Clamp signal values to the graph range. Generate deterministic viewBox SVG with axes, labels, paths, and escaped titles. Omit bands with no plottable APs.

  • Step 4: Integrate maps and print styling

Add responsive .signal-map, .map-grid, axis, and print CSS to REPORT_CSS. Insert maps after stat cards and before breakdown tables in _recon_html_download. Label live GPS as a report-generation-time fix and preserve archive omission.

  • Step 5: Run report tests

Run: python3 -m unittest tests.test_recon.ReconReportTest tests.test_recon.ReconArchivesTest -v

Expected: PASS.

  • Step 6: Commit report maps
git add payload/user/remote_access/pager-webui/server.py tests/test_recon.py
git commit -m "feat: add signal maps to HTML recon reports"

Task 4: Move Handshakes Under PineAP

Files:

  • Modify: payload/user/remote_access/pager-webui/www/js/app.js:94-99, 411-436
  • Modify: payload/user/remote_access/pager-webui/www/js/views.js:330-363, 944-948, 1747-1750, 2905-2908
  • Test: tests/test_recon.py

Interfaces:

  • Produces: canonical route #/pineap/handshakes rendering views.recon_handshakes within pineapShell.

  • Preserves: redirect #/recon/handshakes to the canonical route.

  • Step 1: Add failing static route tests

Assert PINEAP_TABS contains #/pineap/handshakes, RECON_TABS does not contain Handshakes, the router registers the canonical route, the legacy redirect exists, and the Recon card links to the canonical route.

  • Step 2: Run route tests and verify failure

Run: python3 -m unittest tests.test_recon -v

Expected: new navigation assertions fail.

  • Step 3: Move the route and shell

Add Handshakes to PINEAP_TABS, remove it from RECON_TABS, register #/pineap/handshakes, redirect the old hash, update the summary-card link, and render the existing Handshakes body inside pineapShell(root, '#/pineap/handshakes') without changing data APIs.

  • Step 4: Run Recon tests

Run: python3 -m unittest tests.test_recon -v

Expected: PASS.

  • Step 5: Commit navigation
git add payload/user/remote_access/pager-webui/www/js/app.js payload/user/remote_access/pager-webui/www/js/views.js tests/test_recon.py
git commit -m "ui: move handshakes into PineAP"

Task 5: Evil Enterprise Self-Signed Certificates

Files:

  • Modify: payload/user/remote_access/pager-webui/server.py:3462-4151, 6673-6683
  • Modify: payload/user/remote_access/pager-webui/www/js/views.js:1386-1628
  • Modify: payload/user/remote_access/pager-webui/www/css/app.css:501-620
  • Modify: payload/user/remote_access/pager-webui/pagerwebui.init:15-25
  • Test: tests/test_attacks.py:204-319, 434-455
  • Test: tests/test_pineap_enterprise.py

Interfaces:

  • Produces: _validate_cert_request(fields) -> dict normalized subject/SAN/validity data or raises ValueError.

  • Produces: _ent_cert_status() -> dict with validity, subject, SANs, fingerprint, dates, key type/size, and key-match state.

  • Produces: _generate_ent_certificate(fields) -> dict with install/restart/rollback result.

  • Produces: GET /api/pineap/enterprise/certificate and POST /api/pineap/enterprise/certificate/generate.

  • Step 1: Add failing validation and status tests

Test country length, control-character rejection, 128-byte subject limits, required common name, 1-3650 validity, no more than 20 SANs, ASCII DNS syntax/253-character limit, and normalization of comma/newline-separated SAN input. Mock OpenSSL status commands and assert private-key content is never returned.

  • Step 2: Run enterprise tests and verify failure

Run: python3 -m unittest tests.test_attacks tests.test_pineap_enterprise -v

Expected: failures because certificate request/status helpers and routes do not exist.

  • Step 3: Implement validation and certificate inspection

Use strict subject-field validation and DNS-label parsing. Inspect certificate metadata with bounded OpenSSL argv calls. Compare certificate/key public-key digests without exposing them through the API. Return clear invalid/missing state rather than treating nonempty files as valid.

  • Step 4: Add failing atomic generation tests

Mock temporary-directory creation and OpenSSL to assert req -x509 -newkey rsa:2048 -sha256 -nodes, SAN/serverAuth extensions, mode 0600 for key, 0644 for certificate, validation before replacement, backup restoration on failure, and cleanup of temporary files.

  • Step 5: Implement atomic generation

Generate under a mode-0700 directory inside /root/loot/enterprise. Validate parseability, current validity, key match, subject, and SANs. Back up active files, atomically replace them, and restore backups on install failure. Replace _ensure_ent_certs shared-bundle copying with generation through this path when valid active files are absent.

  • Step 6: Add failing active-restart and rollback tests

When _ent_running() is true, assert the existing deployment state is copied, the new certificate is installed, _deploy_enterprise is invoked with unchanged settings, and success requires enabled hostapd. Force deployment failure and assert old certificate restoration plus one previous-state redeploy attempt; include rollback outcome in HTTP 502 data.

  • Step 7: Implement serialized restart and rollback

Use the attack/deploy lock for certificate generation. Keep old certificate backups until redeploy succeeds. On failure, stop partial enterprise state, restore files, and attempt previous deployment exactly once. Do not recursively call certificate generation from deployment fallback.

  • Step 8: Add certificate APIs and matching UI card

Register authenticated GET/status and POST/generate routes. Add an Evil Enterprise card using existing .settings-form-grid, .pineap-infobox, runAction, and button patterns. Include the requested fields, installed metadata, generation busy state, and a warning that active Enterprise will restart automatically. Refresh attack and certificate status after completion.

  • Step 9: Fix secret permissions

Replace chmod -R 755 "$PAGER_WEBUI_DIR" with directory/file-specific safe modes that preserve executable scripts while keeping bundled/runtime keys 0600. Explicitly chmod runtime server.key, eap_users, and enterprise state to 0600 after writes.

  • Step 10: Run enterprise tests

Run: python3 -m unittest tests.test_attacks tests.test_pineap_enterprise -v

Expected: PASS.

  • Step 11: Commit certificate flow
git add payload/user/remote_access/pager-webui/server.py payload/user/remote_access/pager-webui/www/js/views.js payload/user/remote_access/pager-webui/www/css/app.css payload/user/remote_access/pager-webui/pagerwebui.init tests/test_attacks.py tests/test_pineap_enterprise.py
git commit -m "feat: generate Evil Enterprise certificates"

Task 6: Integration Verification and Pager Deployment

Files:

  • Modify if required by verified defects: files from Tasks 1-5 only
  • Build artifact: build/pager-webui/payload-cGFnZXItd2VidWk.zip

Interfaces:

  • Consumes: all completed task interfaces.

  • Produces: verified local suite, payload build, live Pager deployment, and browser/device evidence.

  • Step 1: Run every unit-test module in isolated processes

Run:

for f in tests/test_*.py; do python3 -m unittest "tests.$(basename "$f" .py)" -v || exit 1; done

Expected: every module reports OK.

  • Step 2: Build the payload

Run the repository's existing payload build/deploy script in its build-only or pre-upload stage. Verify the staged payload contains updated source and no loot/, temporary private keys, test caches, or unrelated untracked files.

  • Step 3: Deploy to the authorized Pager

Use scripts/deploy.sh --password 'Bryce9205' against root@172.16.52.1. Record whether safe UCI reconciliation changes the five affected keys. Do not enable pool broadcast.

  • Step 4: Verify daemon and APIs

Confirm pidof pineapd, /api/health, affected build/hash, non-quarantined status, safe UCI values, both monitor interfaces expected by hardware, and diagnostic download. Watch logs long enough to cover multiple historical 15-second crash intervals.

  • Step 5: Verify Recon behavior

Run a controlled timed scan with enabled but unassociated dummy_radio0; verify no false starvation message, tracked scan ID, and 2.4 GHz results. Then use an existing legitimate AP/STA pin scenario and confirm channel-limited copy is accurate.

  • Step 6: Verify reports and navigation in browser

At desktop and mobile viewports, verify Recon has Scanning/Reports only, PineAP has Handshakes, the old hash redirects, and generated live/archive HTML reports contain printable maps for all available bands.

  • Step 7: Verify certificate generation and restart

Generate a certificate with representative attributes, inspect it with openssl x509 -text, verify file modes, deploy Enterprise, generate a second certificate while active, and confirm automatic restart uses the new fingerprint. Exercise a controlled mocked/local failure for rollback if forcing hostapd failure on the live device would risk connectivity.

  • Step 8: Review final diff and status

Run git status --short, git diff --check, and inspect the cumulative diff from 00cbc52. Confirm loot/ and pre-existing untracked certs/ remain unstaged.

  • Step 9: Commit only verified integration fixes if any

If verification required code changes, stage only those files and commit with a focused message. Do not create an empty commit.