Files
ai-agent-deep-dive/extracted-source/node_modules/@anthropic-ai/sandbox-runtime/dist/sandbox/http-proxy.js

217 lines
9.9 KiB
JavaScript

import { Agent, createServer } from 'node:http';
import { request as httpRequest } from 'node:http';
import { request as httpsRequest } from 'node:https';
import { connect } from 'node:net';
import { URL } from 'node:url';
import { logForDebugging } from '../utils/debug.js';
export function createHttpProxyServer(options) {
const server = createServer();
// Handle CONNECT requests for HTTPS traffic
server.on('connect', async (req, socket) => {
// Attach error handler immediately to prevent unhandled errors
socket.on('error', err => {
logForDebugging(`Client socket error: ${err.message}`, { level: 'error' });
});
try {
const [hostname, portStr] = req.url.split(':');
const port = portStr === undefined ? undefined : parseInt(portStr, 10);
if (!hostname || !port) {
logForDebugging(`Invalid CONNECT request: ${req.url}`, {
level: 'error',
});
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
return;
}
const allowed = await options.filter(port, hostname, socket);
if (!allowed) {
logForDebugging(`Connection blocked to ${hostname}:${port}`, {
level: 'error',
});
socket.end('HTTP/1.1 403 Forbidden\r\n' +
'Content-Type: text/plain\r\n' +
'X-Proxy-Error: blocked-by-allowlist\r\n' +
'\r\n' +
'Connection blocked by network allowlist');
return;
}
// Check if this host should be routed through a MITM proxy
const mitmSocketPath = options.getMitmSocketPath?.(hostname);
if (mitmSocketPath) {
// Route through MITM proxy via Unix socket
logForDebugging(`Routing CONNECT ${hostname}:${port} through MITM proxy at ${mitmSocketPath}`);
const mitmSocket = connect({ path: mitmSocketPath }, () => {
// Send CONNECT request to the MITM proxy
mitmSocket.write(`CONNECT ${hostname}:${port} HTTP/1.1\r\n` +
`Host: ${hostname}:${port}\r\n` +
'\r\n');
});
// Buffer to accumulate the MITM proxy's response
let responseBuffer = '';
const onMitmData = (chunk) => {
responseBuffer += chunk.toString();
// Check if we've received the full HTTP response headers
const headerEndIndex = responseBuffer.indexOf('\r\n\r\n');
if (headerEndIndex !== -1) {
// Remove data listener, we're done parsing the response
mitmSocket.removeListener('data', onMitmData);
// Check if MITM proxy accepted the connection
const statusLine = responseBuffer.substring(0, responseBuffer.indexOf('\r\n'));
if (statusLine.includes(' 200 ')) {
// Connection established, now pipe data between client and MITM
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
// If there's any data after the headers, write it to the client
const remainingData = responseBuffer.substring(headerEndIndex + 4);
if (remainingData.length > 0) {
socket.write(remainingData);
}
mitmSocket.pipe(socket);
socket.pipe(mitmSocket);
}
else {
logForDebugging(`MITM proxy rejected CONNECT: ${statusLine}`, {
level: 'error',
});
socket.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
mitmSocket.destroy();
}
}
};
mitmSocket.on('data', onMitmData);
mitmSocket.on('error', err => {
logForDebugging(`MITM proxy connection failed: ${err.message}`, {
level: 'error',
});
socket.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
});
socket.on('error', err => {
logForDebugging(`Client socket error: ${err.message}`, {
level: 'error',
});
mitmSocket.destroy();
});
socket.on('end', () => mitmSocket.end());
mitmSocket.on('end', () => socket.end());
}
else {
// Direct connection (original behavior)
const serverSocket = connect(port, hostname, () => {
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
serverSocket.pipe(socket);
socket.pipe(serverSocket);
});
serverSocket.on('error', err => {
logForDebugging(`CONNECT tunnel failed: ${err.message}`, {
level: 'error',
});
socket.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
});
socket.on('error', err => {
logForDebugging(`Client socket error: ${err.message}`, {
level: 'error',
});
serverSocket.destroy();
});
socket.on('end', () => serverSocket.end());
serverSocket.on('end', () => socket.end());
}
}
catch (err) {
logForDebugging(`Error handling CONNECT: ${err}`, { level: 'error' });
socket.end('HTTP/1.1 500 Internal Server Error\r\n\r\n');
}
});
// Handle regular HTTP requests
server.on('request', async (req, res) => {
try {
const url = new URL(req.url);
const hostname = url.hostname;
const port = url.port
? parseInt(url.port, 10)
: url.protocol === 'https:'
? 443
: 80;
const allowed = await options.filter(port, hostname, req.socket);
if (!allowed) {
logForDebugging(`HTTP request blocked to ${hostname}:${port}`, {
level: 'error',
});
res.writeHead(403, {
'Content-Type': 'text/plain',
'X-Proxy-Error': 'blocked-by-allowlist',
});
res.end('Connection blocked by network allowlist');
return;
}
// Check if this host should be routed through a MITM proxy
const mitmSocketPath = options.getMitmSocketPath?.(hostname);
if (mitmSocketPath) {
// Route through MITM proxy via Unix socket
// Use an agent that connects via the Unix socket
logForDebugging(`Routing HTTP ${req.method} ${hostname}:${port} through MITM proxy at ${mitmSocketPath}`);
const mitmAgent = new Agent({
// @ts-expect-error - socketPath is valid but not in types
socketPath: mitmSocketPath,
});
// Send request to MITM proxy with full URL (proxy-style request)
const proxyReq = httpRequest({
agent: mitmAgent,
// For proxy requests, path should be the full URL
path: req.url,
method: req.method,
headers: {
...req.headers,
host: url.host,
},
}, proxyRes => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
proxyReq.on('error', err => {
logForDebugging(`MITM proxy request failed: ${err.message}`, {
level: 'error',
});
if (!res.headersSent) {
res.writeHead(502, { 'Content-Type': 'text/plain' });
res.end('Bad Gateway');
}
});
req.pipe(proxyReq);
}
else {
// Direct request (original behavior)
// Choose http or https module
const requestFn = url.protocol === 'https:' ? httpsRequest : httpRequest;
const proxyReq = requestFn({
hostname,
port,
path: url.pathname + url.search,
method: req.method,
headers: {
...req.headers,
host: url.host,
},
}, proxyRes => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
proxyReq.on('error', err => {
logForDebugging(`Proxy request failed: ${err.message}`, {
level: 'error',
});
if (!res.headersSent) {
res.writeHead(502, { 'Content-Type': 'text/plain' });
res.end('Bad Gateway');
}
});
req.pipe(proxyReq);
}
}
catch (err) {
logForDebugging(`Error handling HTTP request: ${err}`, { level: 'error' });
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Internal Server Error');
}
});
return server;
}
//# sourceMappingURL=http-proxy.js.map