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.
85 lines
3.5 KiB
JavaScript
85 lines
3.5 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { test } = require('node:test');
|
|
const { scanAptProxies, summarizeAptFindings } = require('./apt-audit.js');
|
|
|
|
function mkAptDir(t, files) {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'apt-audit-'));
|
|
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
for (const [name, content] of Object.entries(files)) {
|
|
fs.writeFileSync(path.join(dir, name), content);
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
const BRIDGE_CONF = 'Acquire::http::Proxy "http://127.0.0.1:8888/";\nAcquire::https::Proxy "http://127.0.0.1:8888/";\n';
|
|
|
|
test('clean setup: only 80proxy-bridge with bridge URL → no threats', t => {
|
|
const dir = mkAptDir(t, {
|
|
'20archive': '// no proxies here\n',
|
|
'80proxy-bridge': BRIDGE_CONF,
|
|
'99update-notifier': '// unrelated\n',
|
|
});
|
|
const { findings } = scanAptProxies(dir);
|
|
const summary = summarizeAptFindings(findings);
|
|
assert.equal(summary.bridgePresent, true);
|
|
assert.deepEqual(summary.threats, []);
|
|
});
|
|
|
|
test('legacy 80proxy with embedded creds is flagged creds even when sorting before ours', t => {
|
|
const dir = mkAptDir(t, {
|
|
'80proxy': 'Acquire::http::Proxy "http://user:p%40ss@proxy.corp:8080/";\n',
|
|
'80proxy-bridge': BRIDGE_CONF,
|
|
});
|
|
const { findings } = scanAptProxies(dir);
|
|
const credFinding = findings.find(f => f.name === '80proxy');
|
|
assert.equal(credFinding.severity, 'creds');
|
|
assert.equal(summarizeAptFindings(findings).threats.length, 1);
|
|
});
|
|
|
|
test('a later-sorting file with an external proxy overrides the bridge (apt last-wins)', t => {
|
|
const dir = mkAptDir(t, {
|
|
'80proxy-bridge': BRIDGE_CONF,
|
|
'99custom': 'Acquire::http::Proxy "http://other.proxy:3128/";\n',
|
|
});
|
|
const { findings } = scanAptProxies(dir);
|
|
const f99 = findings.find(f => f.name === '99custom');
|
|
assert.equal(f99.severity, 'overrides');
|
|
assert.equal(f99.sortsAfterBridge, true);
|
|
assert.equal(summarizeAptFindings(findings).threats.length, 1);
|
|
});
|
|
|
|
test('earlier-sorting external proxy does not override but is still reported external', t => {
|
|
const dir = mkAptDir(t, {
|
|
'10proxy': 'Acquire::https::Proxy "http://other.proxy:3128/";\n',
|
|
'80proxy-bridge': BRIDGE_CONF,
|
|
});
|
|
const { findings } = scanAptProxies(dir);
|
|
const f10 = findings.find(f => f.name === '10proxy');
|
|
assert.equal(f10.severity, 'external');
|
|
assert.equal(f10.sortsAfterBridge, false);
|
|
});
|
|
|
|
test('missing directory returns an error instead of crashing', t => {
|
|
const { findings, error } = scanAptProxies('/nonexistent-apt-dir-xyz');
|
|
assert.deepEqual(findings, []);
|
|
assert.ok(error);
|
|
});
|
|
|
|
test('bridge file missing entirely: bridgePresent false, all proxy files reported', t => {
|
|
const dir = mkAptDir(t, {
|
|
'50proxy': 'Acquire::http::Proxy "http://some.proxy:8080/";\n',
|
|
});
|
|
const { findings } = scanAptProxies(dir);
|
|
const summary = summarizeAptFindings(findings);
|
|
assert.equal(summary.bridgePresent, false);
|
|
assert.equal(summary.threats.length, 1);
|
|
assert.equal(summary.threats[0].severity, 'external'); // effective config; not "overriding" a bridge that isn't there
|
|
// A cred-bearing file must still be flagged creds even without a bridge file:
|
|
const dir2 = mkAptDir(t, { '99bad': 'Acquire::http::Proxy "http://u:p@proxy:8080/";\n' });
|
|
const { findings: f2 } = scanAptProxies(dir2);
|
|
assert.equal(f2[0].severity, 'creds');
|
|
});
|