mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-02 14:28:59 +02:00
84 lines
3.9 KiB
JavaScript
84 lines
3.9 KiB
JavaScript
// ENG-422: the Windows Defender exclusion, wired at last.
|
|
//
|
|
// Antivirus quarantining the bundled agent runtime left 22 of 25 affected installs permanently
|
|
// broken, and a real user answered the old card with "don't know how to take a file out of
|
|
// quarantine". The self-heal restores the file from the Squirrel package cache; an exclusion is
|
|
// what makes that restore STAY (the repair reports `retaken` when it does not).
|
|
//
|
|
// The rules this surface has to hold, all of them from the script's own .SECURITY block:
|
|
// - Windows only. On any other platform this is not offered and cannot be invoked.
|
|
// - OFF by default, and only ever flipped by the user in Settings. Never auto-run, never a
|
|
// startup prompt, never a banner. Excluding a folder from Defender reduces AV coverage of it,
|
|
// so the user owns that call.
|
|
// - Fully reversible: turning it off runs the same script with -Remove.
|
|
// - The elevation is Windows' own UAC dialog (Start-Process -Verb RunAs). We never ask for a
|
|
// password and never hold one.
|
|
|
|
'use strict';
|
|
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const { spawn } = require('child_process');
|
|
|
|
const SCRIPT_REL = path.join('backend', 'scripts', 'add-defender-exclusion.ps1');
|
|
|
|
// The script ships as a real file under Resources/backend (the whole backend tree is copied there),
|
|
// NOT inside app.asar: PowerShell cannot execute a path inside an asar archive.
|
|
function scriptPath(resourcesPath, devRoot) {
|
|
const packaged = path.join(resourcesPath || '', SCRIPT_REL);
|
|
if (resourcesPath && fs.existsSync(packaged)) return packaged;
|
|
const dev = path.join(devRoot || '', SCRIPT_REL);
|
|
return fs.existsSync(dev) ? dev : null;
|
|
}
|
|
|
|
// The argv handed to powershell.exe. Split out so a test can assert the exact shape without
|
|
// spawning anything: -Verb RunAs is what raises UAC, and the inner script gets exactly one switch.
|
|
function buildArgs(script, enable) {
|
|
const inner = [
|
|
'-NoProfile', '-ExecutionPolicy', 'Bypass',
|
|
'-File', script,
|
|
enable ? '-Apply' : '-Remove',
|
|
];
|
|
// Quote for the nested PowerShell that Start-Process parses, and double any embedded quote so a
|
|
// path with one in it cannot end the argument early.
|
|
const quoted = inner.map((a) => `'${String(a).replace(/'/g, "''")}'`).join(',');
|
|
return [
|
|
'-NoProfile', '-ExecutionPolicy', 'Bypass',
|
|
'-Command',
|
|
`Start-Process -FilePath powershell.exe -Verb RunAs -Wait -ArgumentList ${quoted}`,
|
|
];
|
|
}
|
|
|
|
// Returns { ok, detail }. Never throws: a failed exclusion must leave the app working, and the
|
|
// toggle reverting is the honest signal that nothing was changed.
|
|
function applyDefenderExclusion({ enable, platform, resourcesPath, devRoot, spawnFn } = {}) {
|
|
if (platform !== 'win32') {
|
|
return Promise.resolve({ ok: false, detail: 'the Defender exclusion is a Windows-only setting' });
|
|
}
|
|
const script = scriptPath(resourcesPath, devRoot);
|
|
if (!script) {
|
|
return Promise.resolve({ ok: false, detail: 'the exclusion script is missing from this install' });
|
|
}
|
|
const run = spawnFn || spawn;
|
|
return new Promise((resolve) => {
|
|
let child;
|
|
try {
|
|
child = run('powershell.exe', buildArgs(script, enable), { windowsHide: true });
|
|
} catch (e) {
|
|
resolve({ ok: false, detail: `could not start PowerShell: ${e && e.message}` });
|
|
return;
|
|
}
|
|
let settled = false;
|
|
const done = (r) => { if (!settled) { settled = true; resolve(r); } };
|
|
child.on('error', (e) => done({ ok: false, detail: `PowerShell failed: ${e && e.message}` }));
|
|
child.on('exit', (code) => done(
|
|
code === 0
|
|
? { ok: true, detail: enable ? 'exclusion added' : 'exclusion removed' }
|
|
// A non-zero exit is almost always the user declining UAC, which is a legitimate answer.
|
|
: { ok: false, detail: 'the change was not approved, so nothing was altered' },
|
|
));
|
|
});
|
|
}
|
|
|
|
module.exports = { applyDefenderExclusion, buildArgs, scriptPath, SCRIPT_REL };
|