Merge pull request #130 from AikidoSec/socket-error-events

This commit is contained in:
bitterpanda 2025-10-31 13:37:01 +01:00 committed by GitHub
commit 04751df30c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 9 deletions

View file

@ -11,6 +11,21 @@ export async function main(args) {
const proxy = createSafeChainProxy(); const proxy = createSafeChainProxy();
await proxy.startServer(); await proxy.startServer();
// Global error handlers to log unhandled errors
process.on("uncaughtException", (error) => {
ui.writeError(`Safe-chain: Uncaught exception: ${error.message}`);
ui.writeVerbose(`Stack trace: ${error.stack}`);
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
ui.writeError(`Safe-chain: Unhandled promise rejection: ${reason}`);
if (reason instanceof Error) {
ui.writeVerbose(`Stack trace: ${reason.stack}`);
}
process.exit(1);
});
try { try {
// This parses all the --safe-chain arguments and removes them from the args array // This parses all the --safe-chain arguments and removes them from the args array
args = initializeCliArguments(args); args = initializeCliArguments(args);

View file

@ -1,6 +1,7 @@
import https from "https"; import https from "https";
import { generateCertForHost } from "./certUtils.js"; import { generateCertForHost } from "./certUtils.js";
import { HttpsProxyAgent } from "https-proxy-agent"; import { HttpsProxyAgent } from "https-proxy-agent";
import { ui } from "../environment/userInteraction.js";
export function mitmConnect(req, clientSocket, isAllowed) { export function mitmConnect(req, clientSocket, isAllowed) {
const { hostname } = new URL(`http://${req.url}`); const { hostname } = new URL(`http://${req.url}`);
@ -37,13 +38,25 @@ function createHttpsServer(hostname, isAllowed) {
forwardRequest(req, hostname, res); forwardRequest(req, hostname, res);
} }
return https.createServer( const server = https.createServer(
{ {
key: cert.privateKey, key: cert.privateKey,
cert: cert.certificate, cert: cert.certificate,
}, },
handleRequest handleRequest
); );
server.on("error", (err) => {
ui.writeError(`Safe-chain: HTTPS server error: ${err.message}`);
if (!res.headersSent) {
res.writeHead(502);
res.end("Bad Gateway");
} else if (res.writable) {
res.destroy();
}
});
return server;
} }
function getRequestPathAndQuery(url) { function getRequestPathAndQuery(url) {
@ -62,6 +75,11 @@ function forwardRequest(req, hostname, res) {
res.end("Bad Gateway"); res.end("Bad Gateway");
}); });
req.on("error", (err) => {
ui.writeError(`Safe-chain: Error reading client request: ${err.message}`);
proxyReq.destroy();
});
req.on("data", (chunk) => { req.on("data", (chunk) => {
proxyReq.write(chunk); proxyReq.write(chunk);
}); });
@ -88,6 +106,16 @@ function createProxyRequest(hostname, req, res) {
} }
const proxyReq = https.request(options, (proxyRes) => { const proxyReq = https.request(options, (proxyRes) => {
proxyRes.on("error", (err) => {
ui.writeError(
`Safe-chain: Error reading upstream response: ${err.message}`
);
if (!res.headersSent) {
res.writeHead(502);
res.end("Bad Gateway");
}
});
res.writeHead(proxyRes.statusCode, proxyRes.headers); res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res); proxyRes.pipe(res);
}); });

View file

@ -43,8 +43,13 @@ export function handleHttpProxyRequest(req, res) {
} }
) )
.on("error", (err) => { .on("error", (err) => {
if (!res.headersSent) {
res.writeHead(502); res.writeHead(502);
res.end(`Bad Gateway: ${err.message}`); res.end(`Bad Gateway: ${err.message}`);
} else {
// Headers already sent, just destroy the response
res.destroy();
}
}); });
req.on("error", () => { req.on("error", () => {

View file

@ -24,12 +24,6 @@ export function tunnelRequest(req, clientSocket, head) {
function tunnelRequestToDestination(req, clientSocket, head) { function tunnelRequestToDestination(req, clientSocket, head) {
const { port, hostname } = new URL(`http://${req.url}`); const { port, hostname } = new URL(`http://${req.url}`);
clientSocket.on("error", () => {
// NO-OP
// This can happen if the client TCP socket sends RST instead of FIN.
// Not subscribing to 'close' event will cause node to throw and crash.
});
const serverSocket = net.connect(port || 443, hostname, () => { const serverSocket = net.connect(port || 443, hostname, () => {
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
serverSocket.write(head); serverSocket.write(head);
@ -37,6 +31,14 @@ function tunnelRequestToDestination(req, clientSocket, head) {
clientSocket.pipe(serverSocket); clientSocket.pipe(serverSocket);
}); });
clientSocket.on("error", () => {
// This can happen if the client TCP socket sends RST instead of FIN.
// Not subscribing to 'error' event will cause node to throw and crash.
if (serverSocket.writable) {
serverSocket.end();
}
});
serverSocket.on("error", (err) => { serverSocket.on("error", (err) => {
ui.writeError( ui.writeError(
`Safe-chain: error connecting to ${hostname}:${port} - ${err.message}` `Safe-chain: error connecting to ${hostname}:${port} - ${err.message}`
@ -103,6 +105,13 @@ function tunnelRequestViaProxy(req, clientSocket, head, proxyUrl) {
if (clientSocket.writable) { if (clientSocket.writable) {
clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n"); clientSocket.end("HTTP/1.1 502 Bad Gateway\r\n\r\n");
} }
} else {
ui.writeError(
`Safe-chain: proxy socket error after connection - ${err.message}`
);
if (clientSocket.writable) {
clientSocket.end();
}
} }
}); });