Gracefully handle network failure during MITM + more logging

This commit is contained in:
Reinier Criel 2026-01-27 10:31:16 -08:00
parent 4b07619769
commit 6c65fb3f4c
2 changed files with 72 additions and 12 deletions

View file

@ -67,21 +67,29 @@ function createHttpsServer(hostname, port, interceptor) {
return;
}
const pathAndQuery = getRequestPathAndQuery(req.url);
const targetUrl = `https://${hostname}${pathAndQuery}`;
try {
const pathAndQuery = getRequestPathAndQuery(req.url);
const targetUrl = `https://${hostname}${pathAndQuery}`;
const requestInterceptor = await interceptor.handleRequest(targetUrl);
const blockResponse = requestInterceptor.blockResponse;
const requestInterceptor = await interceptor.handleRequest(targetUrl);
const blockResponse = requestInterceptor.blockResponse;
if (blockResponse) {
ui.writeVerbose(`Safe-chain: Blocking request to ${targetUrl}`);
res.writeHead(blockResponse.statusCode, blockResponse.message);
res.end(blockResponse.message);
return;
if (blockResponse) {
ui.writeVerbose(`Safe-chain: Blocking request to ${targetUrl}`);
res.writeHead(blockResponse.statusCode, blockResponse.message);
res.end(blockResponse.message);
return;
}
// Collect request body
forwardRequest(req, hostname, port, res, requestInterceptor);
} catch (/** @type {any} */ error) {
ui.writeError(
`Safe-chain: Error handling request for ${req.url}: ${error.message}`
);
res.writeHead(502, "Bad Gateway");
res.end("Bad Gateway: Error handling request");
}
// Collect request body
forwardRequest(req, hostname, port, res, requestInterceptor);
}
const server = https.createServer(

View file

@ -0,0 +1,52 @@
import { describe, it, before, beforeEach, afterEach } from "node:test";
import { DockerTestContainer } from "./DockerTestContainer.js";
import assert from "node:assert";
describe("E2E: DNS failure resilience", () => {
let container;
before(async () => {
DockerTestContainer.buildImage();
});
beforeEach(async () => {
container = new DockerTestContainer();
await container.start();
const installationShell = await container.openShell("zsh");
await installationShell.runCommand("safe-chain setup");
});
afterEach(async () => {
if (container) {
await container.stop();
container = null;
}
});
it("should not crash when the malware database is unreachable", async () => {
const shell = await container.openShell("zsh");
// Make the malware database domain unreachable
// This forces fetchMalwareDatabase to fail
await shell.runCommand(
'echo "127.0.0.1 malware-list.aikido.dev" >> /etc/hosts'
);
const result = await shell.runCommand(
"npm install lodash --safe-chain-logging=verbose"
);
assert.ok(
result.output.includes("Safe-chain: Error handling request"),
`Output did not include expected error handling message. Output was:\n${result.output}`
);
// Ensure it did NOT crash with Unhandled Promise Rejection
assert.strictEqual(
result.output.includes("Unhandled promise rejection"),
false,
`Output indicates process crash (Unhandled promise rejection). Output was:\n${result.output}`
);
});
});