Files
gists/scripts/proxy/apt-audit.js
T
tke a23fa00dfb 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.
2026-09-23 16:07:53 +02:00

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 };