- 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.
8.2 KiB
Encryption Landscape Card — Ring + Key Redesign 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: Replace the Encryption Landscape recon card's text headline + clipped canvas legend with a full-size plain-hole ring graph and a wrapping HTML legend (dot + label + count per encryption bucket).
Architecture: Single-file client-side change: the recon view in views.js drops the encValue/encSub text nodes, draws the doughnut with legend:false and a taller height, and populates a new HTML legend container from the existing encCounts bucket map. chart.js's MiniChart.doughnut is unchanged (its canvas legend option is simply no longer used by the enc card). CSS adds flex-wrap legend styles.
Tech Stack: Vanilla JS (no framework), canvas via MiniChart.doughnut in chart.js, plain CSS in app.css. Device deploy via scripts/deploy.sh --password '<device-password>'. Tests: none exist for the frontend; verification is via the deployed device + backend test suite (must stay green).
Global Constraints
- Do not alter data source,
reconEncBucket, per-scan bucketing, re-sync, or the other four recon cards. - Legend entries: colored dot + label + count, format
● WPA2 54; buckets with zero APs hidden. - Ring center hole stays plain (no text).
- Keep
MiniChart.doughnut'slegendoption inchart.js(used by no caller after this change, but harmless). - Empty state text stays "No encryption data yet — run a scan."
- No comments added to code unless already present in the surrounding style.
- Deploy and verify on the device; backend
tests/suite must stay green (291 tests).
Task 1: Ring + HTML legend for Encryption Landscape card
Files:
- Modify:
payload/user/remote_access/pager-webui/www/js/views.js:1497-1507(card markup) - Modify:
payload/user/remote_access/pager-webui/www/js/views.js:2256-2322(drawCharts enc block) - Modify:
payload/user/remote_access/pager-webui/www/css/app.css(after line 328) - Test: none (frontend); verify via device sweep
Interfaces:
-
Consumes:
RECON_ENC_BUCKETS(6 names,views.js:852),RECON_ENC_COLORS(6 colors,views.js:851),encCountsobject{bucketName: count},reconEncBucket(a.encryption). -
Produces: DOM
div#recon-enc-legendunder the enc card body, populated bydrawCharts;canvas#recon-encryptionredrawn with{ legend: false, height: 120 }.encValue/encSubvariables are removed. -
Step 1: Edit the enc card markup in views.js
Replace lines 1497-1507:
const encBody = titleCard('Encryption Landscape', null);
const encValue = h('div', { class: 'recon-card-value', text: '—' });
const encSub = h('div', { class: 'recon-card-sub', text: '' });
encBody.appendChild(encValue);
encBody.appendChild(encSub);
const encBox = h('div', { class: 'recon-chart-box' });
encBody.appendChild(encBox);
const encCanvas = h('canvas', { id: 'recon-encryption' });
encBox.appendChild(encCanvas);
const encEmpty = h('div', { class: 'recon-no-data', text: 'No encryption data yet — run a scan.' });
encBox.appendChild(encEmpty);
with:
const encBody = titleCard('Encryption Landscape', null);
const encBox = h('div', { class: 'recon-chart-box' });
encBody.appendChild(encBox);
const encCanvas = h('canvas', { id: 'recon-encryption' });
encBox.appendChild(encCanvas);
const encEmpty = h('div', { class: 'recon-no-data', text: 'No encryption data yet — run a scan.' });
encBox.appendChild(encEmpty);
const encLegend = h('div', { class: 'recon-enc-legend' });
encBody.appendChild(encLegend);
- Step 2: Remove the enc text-headline computation in drawCharts
Replace lines 2256-2266 (the encCounts loop stays — only the topEnc block goes):
let topEnc = null, topEncN = 0;
Object.keys(encCounts).forEach((k) => {
if (encCounts[k] > topEncN) { topEnc = k; topEncN = encCounts[k]; }
});
encValue.textContent = topEnc || '—';
encSub.textContent = topEnc ? topEncN + ' of ' + n + ' APs' : '';
with nothing (delete those lines). The encCounts computation immediately above must remain.
- Step 3: Rewrite the enc chart block in drawCharts
Replace lines 2308-2322:
const enc = document.getElementById('recon-encryption');
if (enc && typeof MiniChart !== 'undefined' && MiniChart.doughnut) {
try {
if (aps.length) {
MiniChart.doughnut(enc, RECON_ENC_BUCKETS.map((k, i) => ({
label: k, value: encCounts[k] || 0, color: RECON_ENC_COLORS[i]
})), { legend: true, height: 66 });
enc.classList.remove('hidden');
encEmpty.classList.add('hidden');
} else {
enc.classList.add('hidden');
encEmpty.classList.remove('hidden');
}
} catch (e) {}
}
with:
const enc = document.getElementById('recon-encryption');
if (enc && typeof MiniChart !== 'undefined' && MiniChart.doughnut) {
try {
if (aps.length) {
MiniChart.doughnut(enc, RECON_ENC_BUCKETS.map((k, i) => ({
label: k, value: encCounts[k] || 0, color: RECON_ENC_COLORS[i]
})), { legend: false, height: 120 });
enc.classList.remove('hidden');
encEmpty.classList.add('hidden');
} else {
enc.classList.add('hidden');
encEmpty.classList.remove('hidden');
}
} catch (e) {}
}
const encLegend = document.getElementById('recon-enc-legend');
if (encLegend) {
encLegend.innerHTML = '';
RECON_ENC_BUCKETS.forEach((k, i) => {
const c = encCounts[k] || 0;
if (!c) return;
const entry = h('div', { class: 'recon-enc-entry' });
const dot = h('span', { class: 'recon-enc-dot' });
dot.style.background = RECON_ENC_COLORS[i];
entry.appendChild(dot);
entry.appendChild(h('span', { class: 'recon-enc-label', text: k }));
entry.appendChild(h('span', { class: 'recon-enc-count', text: String(c) }));
encLegend.appendChild(entry);
});
}
- Step 4: Add the legend CSS to app.css
Insert after line 328 (the .recon-no-data rule):
.recon-enc-legend { display: flex; flex-wrap: wrap; gap: 2px 10px; margin-top: 4px; align-items: baseline; }
.recon-enc-entry { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text); }
.recon-enc-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.recon-enc-label { color: var(--muted); }
.recon-enc-count { font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
- Step 5: Syntax check both JS files
Run: node --check payload/user/remote_access/pager-webui/www/js/views.js
Run: node --check payload/user/remote_access/pager-webui/www/js/chart.js
Expected: exit 0, no output.
- Step 6: Run the backend test suite
Run: cd <repo> && python3 -m pytest tests/ -q 2>&1 | tail -3
Expected: 291 passed (or the current passing count) — no regressions from unrelated files.
- Step 7: Deploy to the device
Run: cd <repo> && ./scripts/deploy.sh --password '<device-password>'
Expected: deploy completes with EXTRACT_OK / success output.
- Step 8: Verify the enc card on the device
Recreate the CDP venv if absent (python3 -m venv /tmp/cdpenv2 && /tmp/cdpenv2/bin/pip install -q websocket-client), then drive headless Chrome against http://:8080 (login <device-password>, go to #/recon, wait ~20s) and assert:
document.getElementById('recon-encryption')canvas has non-zerowidthattribute and the card is visible (not.hidden).document.getElementById('recon-enc-legend')contains entries whose text matches/WPA2/and/\d+/, and norecon-card-valueelement exists inside the enc card.- Zero
Runtime.exceptionThrownevents. Expected: all three pass; screenshots unavailable, text-state assertions only.
- Step 9: Commit
cd <repo> && git add payload/user/remote_access/pager-webui/www/js/views.js payload/user/remote_access/pager-webui/www/css/app.css && git commit -m "ui: encryption landscape card — ring + HTML legend with counts"