@andrei.fyi/devproxy
devproxy
WIP
A local HTTPS-intercepting proxy (mockttp) with user-defined request/response rewrite rules. It can also launch Chromium through the proxy.
Requires Node.js 22 or later.
Usage
devproxy init write a starter config.js
devproxy check validate config.js and list its rules
devproxy start start the proxy for any browser or tool
devproxy start --browser also launch Chromium through the proxy
devproxy start -o <url> launch Chromium on a page (implies --browser)
devproxy ca show the CA certificate to trust, creating it if missing
Run devproxy <command> --help for each command's options. start, check,
and ca read config.js unless -c <file> names another file. start flags
override the corresponding config values.
The proxy signs intercepted HTTPS traffic with a local CA stored in cert.pem
and cert.key (paths set by the ca config key). Chromium launched by start trusts it
automatically. For other browsers or tools, import the certificate that
devproxy ca prints as a trusted authority. If the CA has expired, delete both
files and run devproxy ca to create a new one.
An invalid flag, a config file that is missing, fails to load, or does not match the shape below, or an unusable CA exits with code 2 and names the problem.
Configuration
module.exports = {
// launch Chromium when running `devproxy start` (default: false)
browser: false,
// page Chromium opens (default: "https://github.com/3rd/devproxy")
open: "https://example.com",
// chromium binary to launch (default: "google-chrome-stable")
chromiumBinary: "google-chrome-stable",
// proxy port (default: auto-assigned)
port: 8080,
// chromium profile directory (default: "<cwd>/devproxy_profile")
profilePath: "./devproxy_profile",
// launch chromium with --disable-web-security (default: false)
disableWebSecurity: false,
// CA used to intercept HTTPS, created on first use
ca: {
certPath: "cert.pem",
keyPath: "cert.key",
},
// websockets pass through unless forwarded
ws: {
// apply the forward map (default: true)
enabled: true,
// optional hostname -> target forwarding
forward: {
"example.com": "ws://localhost:3000",
},
},
rules: [
{
// match receives the intercepted request
match: ({ url, method, headers }) => url.includes("example.com"),
// optionally patch the outgoing request (return {} to leave it untouched)
beforeRequest: (request) => ({
url: request.url.replace("http://", "https://"),
}),
// optionally patch the upstream response
beforeResponse: (request, response) => {
// response: { id, statusCode, headers, body, ... }
return { statusCode: 200, body: "patched" };
},
},
],
};
At most one rule may match a given request per phase (beforeRequest /
beforeResponse); if more than one rule matches, that request fails with a 500
response and the error is logged.
Rules receive mockttp 4
request and response objects. Requests have no hostname field; read the host
from new URL(request.url).hostname or request.headers.host. Bodies are read
asynchronously, for example await request.body.getText(). Hooks set the
response status with statusCode.