Created
May 8, 2026 17:14
-
-
Save Inndy/d3dbc72a1b2775833669480c889bab90 to your computer and use it in GitHub Desktop.
Node in the middle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "node-in-the-middle", | |
| "version": "1.0.0", | |
| "description": "", | |
| "main": "server.mjs", | |
| "keywords": [], | |
| "author": "", | |
| "license": "ISC", | |
| "packageManager": "pnpm@10.33.0", | |
| "dependencies": { | |
| "mockttp": "^4.3.1" | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import mockttp from 'mockttp'; | |
| import { promises as fs } from 'fs'; | |
| import path from 'path'; | |
| import { spawn } from 'child_process'; | |
| const CA_KEY_PATH = './proxy-ca.key'; | |
| const CA_CERT_PATH = './proxy-ca.pem'; | |
| const LOG_FILE = './mitm.log'; | |
| async function ensureCAExists() { | |
| try { | |
| await fs.access(CA_KEY_PATH); | |
| await fs.access(CA_CERT_PATH); | |
| console.log('??CA certificate already exists'); | |
| return { keyPath: CA_KEY_PATH, certPath: CA_CERT_PATH }; | |
| } catch (error) { | |
| console.log('? Generating new CA certificate...'); | |
| const caCertificate = await mockttp.generateCACertificate({ | |
| commonName: 'Proxy Testing CA - DO NOT TRUST', | |
| organizationName: 'Mockttp Proxy', | |
| countryName: 'US', | |
| bits: 2048 | |
| }); | |
| await fs.writeFile(CA_KEY_PATH, caCertificate.key); | |
| await fs.writeFile(CA_CERT_PATH, caCertificate.cert); | |
| console.log('??CA certificate generated and saved'); | |
| return { keyPath: CA_KEY_PATH, certPath: CA_CERT_PATH }; | |
| } | |
| } | |
| async function spawnGeminiCli(proxyUrl) { | |
| console.log('?? Spawning Gemini CLI...'); | |
| return new Promise((resolve, reject) => { | |
| const env = { | |
| ...process.env, | |
| NODE_EXTRA_CA_CERTS: path.resolve(CA_CERT_PATH), | |
| https_proxy: proxyUrl, | |
| HTTPS_PROXY: proxyUrl, | |
| NODE_USE_ENV_PROXY: '1', | |
| }; | |
| // Using 'pnpm dlx' to run gemini-cli. | |
| // We'll run '--version' as a simple test that might trigger a check for updates or similar. | |
| // If the user wants to test real traffic, they can run a command that requires network. | |
| const worker = spawn('pnpx', ['@google/gemini-cli'], { | |
| stdio: 'inherit', | |
| env, | |
| //shell: true | |
| }); | |
| worker.on('error', reject); | |
| worker.on('exit', (code) => { | |
| console.log(`\nGemini CLI exited with code ${code}`); | |
| resolve(); | |
| }); | |
| }); | |
| } | |
| async function startProxy() { | |
| const caConfig = await ensureCAExists(); | |
| const server = mockttp.getLocal({ | |
| https: caConfig, | |
| debug: false | |
| }); | |
| const captured = []; | |
| await fs.writeFile(LOG_FILE, `MITM Log - ${new Date().toISOString()}\n\n`); | |
| await server.start(3128); | |
| await server.forAnyRequest().thenPassThrough({ | |
| beforeRequest: async (req) => { | |
| const body = await req.body.getText(); | |
| const entry = { | |
| url: req.url, | |
| method: req.method, | |
| requestBody: body, | |
| timestamp: new Date().toISOString() | |
| }; | |
| captured.push(entry); | |
| await fs.appendFile(LOG_FILE, `[${entry.timestamp}] REQ: ${req.method} ${req.url}\nBody: ${body}\n\n`); | |
| // Force no compression to make reading response body easier | |
| const headers = { ...req.headers }; | |
| delete headers['accept-encoding']; | |
| return { ...req, headers }; | |
| }, | |
| beforeResponse: async (res) => { | |
| const body = await res.body.getText(); | |
| const lastEntry = captured.find(e => e.url === res.url && !e.responseBody); | |
| if (lastEntry) { | |
| lastEntry.responseBody = body; | |
| lastEntry.statusCode = res.statusCode; | |
| } | |
| await fs.appendFile(LOG_FILE, `[${new Date().toISOString()}] RES: ${res.statusCode} ${res.url}\nBody: ${body}\n\n`); | |
| return { | |
| statusCode: res.statusCode, | |
| headers: res.headers, | |
| body: body | |
| }; | |
| } | |
| }); | |
| console.log(`?? Proxy running on: ${server.url}`); | |
| try { | |
| await spawnGeminiCli(server.url); | |
| } catch (err) { | |
| console.error('Error running Gemini CLI:', err.message); | |
| } | |
| // Print Summary | |
| console.log('\n--- MITM Summary ---'); | |
| console.log(`Captured Requests: ${captured.length}`); | |
| if (captured.length > 0) { | |
| const last = captured[captured.length - 1]; | |
| console.log(`Last URL: ${last.url}`); | |
| try { | |
| const json = JSON.parse(last.responseBody || last.requestBody || '{}'); | |
| console.log('Last JSON data (sample):', JSON.stringify(json, null, 2).substring(0, 500) + (JSON.stringify(json).length > 500 ? '...' : '')); | |
| } catch (e) { | |
| console.log('Last body was not JSON or was empty.'); | |
| } | |
| } | |
| console.log(`Full log available at: ${LOG_FILE}`); | |
| await server.stop(); | |
| process.exit(); | |
| } | |
| startProxy().catch(console.error); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment