feat: local MCP harness (tools/resources/prompts) + Harness UI page
Streamable-HTTP MCP server on POST /mcp: device.state, attack.deploy/stop/ status/deauth/capture/export_hc22000, loot.handshakes/enterprise_creds, recon.aps/isearch/devices, pineap.kick_client/set_filter tools; recon DB + bundled opencode skills resources; attack playbook prompts. Cookie or Bearer auth. Harness page shows endpoint, token, curl snippet, capability explorer and a copy-paste pi.dev prompt. scripts/harness_stdio.py for stdio-only agents.
This commit is contained in:
@@ -1388,6 +1388,90 @@ function attackLauncher(kind, opts) {
|
||||
};
|
||||
}
|
||||
|
||||
views.harness = (root) => {
|
||||
root.appendChild(h('h1', { class: 'page-title', text: 'Harness' }));
|
||||
const box = h('div', {});
|
||||
root.appendChild(box);
|
||||
|
||||
const info = h('div', { class: 'pineap-title-card' });
|
||||
info.appendChild(h('div', { class: 'pineap-card-title' }, 'Local Harness (MCP)'));
|
||||
const infoBody = h('div', { style: 'font-size:13px;line-height:1.9' });
|
||||
info.appendChild(infoBody);
|
||||
box.appendChild(info);
|
||||
|
||||
const tok = h('code', { style: 'font-size:12px', text: '…' });
|
||||
const endpoint = h('code', { style: 'font-size:12px', text: location.origin + '/mcp' });
|
||||
infoBody.appendChild(h('div', { class: 'row' }, h('div', { style: 'min-width:130px', text: 'Endpoint' }), endpoint));
|
||||
infoBody.appendChild(h('div', { class: 'row' }, h('div', { style: 'min-width:130px', text: 'Bearer token' }), tok));
|
||||
infoBody.appendChild(h('div', { class: 'muted', style: 'font-size:12px', text: 'Agents call POST /mcp with JSON-RPC 2.0 (MCP Streamable HTTP). The token is the current session token.' }));
|
||||
|
||||
const snippet = h('pre', { style: 'font-size:12px;overflow:auto;background:rgba(127,127,127,.12);padding:10px;border-radius:4px;white-space:pre-wrap' });
|
||||
const capBox = h('div', { class: 'pineap-title-card' });
|
||||
capBox.appendChild(h('div', { class: 'pineap-card-title' }, 'Capabilities'));
|
||||
const capBody = h('div', { style: 'font-size:13px' });
|
||||
capBox.appendChild(capBody);
|
||||
box.appendChild(capBox);
|
||||
|
||||
const promptBox = h('div', { class: 'pineap-title-card' });
|
||||
promptBox.appendChild(h('div', { class: 'pineap-card-title' }, 'Prompt for pi.dev'));
|
||||
const promptArea = h('textarea', { rows: 14, style: 'width:100%;font-family:monospace;font-size:12px;box-sizing:border-box' });
|
||||
promptBox.appendChild(promptArea);
|
||||
promptBox.appendChild(h('div', { class: 'row', style: 'margin-top:8px' },
|
||||
h('div', {}, btn('Copy Prompt', () => {
|
||||
promptArea.select();
|
||||
document.execCommand('copy');
|
||||
App.toast('Copied');
|
||||
})),
|
||||
h('div', {}, btn('Copy Token', () => {
|
||||
navigator.clipboard.writeText(tok.textContent).then(() => App.toast('Token copied'))
|
||||
.catch(() => App.toast('Copy failed', 'error'));
|
||||
}))));
|
||||
box.appendChild(promptBox);
|
||||
|
||||
function buildPrompt(token) {
|
||||
return 'You are driving a WiFi Pineapple Pager (FENRIS firmware) through its local MCP harness.\n' +
|
||||
'Endpoint: ' + location.origin + '/mcp (Streamable HTTP, POST JSON-RPC 2.0).\n' +
|
||||
'Authorization: Bearer ' + token + '\n\n' +
|
||||
'Before acting, read these resources (MCP resources/read) — they are the field-verified operating manual:\n' +
|
||||
' skills://pineapple-control (device access, radios, UCI truth, pineapd crash-loop fix)\n' +
|
||||
' skills://wifi-deauth (deauth + handshake methodology, PMKSA failure modes)\n' +
|
||||
' skills://aircrack-suite (hashcat handoff)\n\n' +
|
||||
'Rules:\n' +
|
||||
'1. The DEVICE is the source of truth: read device.state / UCI before and after every change; never assume.\n' +
|
||||
'2. Only attack the network the operator explicitly authorized (currently <authorized-test-ssid>). No deauth blasts — short targeted bursts.\n' +
|
||||
'3. After attack.deploy, verify with attack.status (live flag) before proceeding.\n' +
|
||||
'4. Use the playbook prompts (prompts/get): evil-wpa-attack, evil-enterprise-attack, recon-survey.\n' +
|
||||
'5. Report verified outcomes only; say what you changed on the device.';
|
||||
}
|
||||
|
||||
function load() {
|
||||
PagerAPI.get('/api/harness/capabilities').then((r) => {
|
||||
const d = r.data || {};
|
||||
capBody.innerHTML = '';
|
||||
const tools = d.tools || [];
|
||||
const prompts = d.prompts || [];
|
||||
capBody.appendChild(h('div', { class: 'muted', style: 'font-size:12px',
|
||||
text: tools.length + ' tools, ' + prompts.length + ' playbooks, ' +
|
||||
((d.resources || []).length) + ' resources' }));
|
||||
const list = h('ul', { style: 'font-size:12px;padding-left:18px' });
|
||||
tools.forEach((t) => list.appendChild(h('li', { text: t.name + ' — ' + t.description })));
|
||||
capBody.appendChild(list);
|
||||
}).catch(() => {});
|
||||
PagerAPI.get('/api/harness/token').then((r) => {
|
||||
const t = (r.data || {}).token || '';
|
||||
tok.textContent = t ? t.slice(0, 12) + '…' : '(none)';
|
||||
snippet.textContent = 'curl -s -X POST ' + location.origin + '/mcp \\\n' +
|
||||
' -H "Content-Type: application/json" \\\n' +
|
||||
' -H "Authorization: Bearer ' + t + '" \\\n' +
|
||||
' -d \'{"jsonrpc":"2.0","id":1,"method":"tools/list"}\'';
|
||||
promptArea.value = buildPrompt(t);
|
||||
infoBody.appendChild(snippet);
|
||||
}).catch(() => {});
|
||||
}
|
||||
load();
|
||||
return { destroy: () => {} };
|
||||
};
|
||||
|
||||
function deauthPanel() {
|
||||
const wrap = h('div', { class: 'pineap-title-card' });
|
||||
wrap.appendChild(h('div', { class: 'pineap-card-title' }, 'Deauth Targeting'));
|
||||
|
||||
Reference in New Issue
Block a user