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.
62 lines
2.7 KiB
JavaScript
62 lines
2.7 KiB
JavaScript
// 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 };
|