feat(proxy): apt proxy audit + doctor mode; reinstall-from-clone = update
apt-audit.js scans EVERY file in apt.conf.d for Acquire::*::Proxy, classifying: - creds: embedded userinfo (plaintext leak + stale-cred hammering risk) - overrides: sorts after 80proxy-bridge so apt last-wins REPLACES the bridge URL - external: points at a non-bridge proxy Installer Legacy section alerts + fixes (backup kept: *.disabled-by-proxy-bridge). New 'install.js doctor [--fix]': audits apt, bashrc, snap, service drop-ins, service state, keyring presence; exit 1 when issues found. Update semantics: deploy writes .installed-from (git commit + file hashes); runtime JSONs are never created if present and never overwritten; tests prove 3 consecutive installs leave runtime state byte-identical. Downgrade protection comes from the visible marker + diff reporting, never silent clobbering. Tests: 6 audit + 2 installer (update-semantics, doctor) — suite now 23/23.
This commit is contained in:
1 parent
a626392eb1
commit
a23fa00dfb
4 files changed
+339
-15
No files matched your search
@@ -0,0 +1,61 @@
|
||||
// Shared apt proxy audit logic — used by install.js (Legacy section) and doctor mode.
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BRIDGE_MARK = '127.0.0.1:8888';
|
||||
|
||||
// One finding per file that declares Acquire proxy directives.
|
||||
// severity: 'ok' (bridge URL, sorted before-or-at our file, no creds)
|
||||
// 'overrides' (proxy directives in a file sorting AFTER 80proxy-bridge → apt last-wins)
|
||||
// 'creds' (embedded userinfo — plaintext-leak + stale-cred hammering risk)
|
||||
// 'external' (points at a non-bridge proxy; may be intentional, but defeats the bridge)
|
||||
function scanAptProxies(aptConfDir, bridgeFileName = '80proxy-bridge') {
|
||||
const findings = [];
|
||||
const files = [];
|
||||
try {
|
||||
for (const f of fs.readdirSync(aptConfDir)) {
|
||||
const p = path.join(aptConfDir, f);
|
||||
try { if (fs.statSync(p).isFile()) files.push(f); } catch {}
|
||||
}
|
||||
} catch {
|
||||
return { findings, error: `cannot read ${aptConfDir}` };
|
||||
}
|
||||
const bridgeRank = bridgeFileName;
|
||||
const bridgeFilePresent = files.includes(bridgeRank);
|
||||
for (const f of files.sort()) {
|
||||
const p = path.join(aptConfDir, f);
|
||||
let content = '';
|
||||
try { content = fs.readFileSync(p, 'utf8'); } catch { continue; }
|
||||
const proxyLines = content.split('\n')
|
||||
.map(l => l.trim())
|
||||
.filter(l => /^(Acquire::(http|https)::Proxy)\s/i.test(l));
|
||||
if (proxyLines.length === 0) continue;
|
||||
|
||||
const hasCreds = proxyLines.some(l => /:\/\/[^/\s"]+:[^@\s"]+@/.test(l));
|
||||
const allBridge = proxyLines.every(l => l.includes(BRIDGE_MARK));
|
||||
const sortsAfterBridge = f > bridgeRank; // apt merges alphabetically; later wins
|
||||
|
||||
let severity = 'ok';
|
||||
if (f === bridgeRank) severity = allBridge && !hasCreds ? 'ok' : (hasCreds ? 'creds' : 'external');
|
||||
else if (hasCreds) severity = 'creds';
|
||||
else if (sortsAfterBridge && bridgeFilePresent) severity = 'overrides';
|
||||
else if (sortsAfterBridge && allBridge) severity = 'ok'; // redundant bridge copy, harmless
|
||||
else if (!allBridge) severity = 'external';
|
||||
// sorted BEFORE us and external -> harmless but noteworthy; we still report as external
|
||||
|
||||
findings.push({
|
||||
file: p, name: f, severity,
|
||||
directives: proxyLines.length,
|
||||
creds: hasCreds, bridge: allBridge, sortsAfterBridge,
|
||||
});
|
||||
}
|
||||
return { findings };
|
||||
}
|
||||
|
||||
function summarizeAptFindings(findings) {
|
||||
const bad = findings.filter(f => f.severity !== 'ok');
|
||||
const bridgeFile = findings.find(f => f.severity === 'ok' && f.bridge);
|
||||
return { bridgePresent: !!bridgeFile, threats: bad };
|
||||
}
|
||||
|
||||
module.exports = { scanAptProxies, summarizeAptFindings, BRIDGE_MARK };
|
||||
Reference in new issue
Block a user