Proxy Control
The proxy control API manages Ghost’s core traffic interception engine. These endpoints let you start and stop the MITM proxy, configure the operating system to route traffic through Ghost, simulate slow network conditions, bypass SSL interception for specific hosts, manage the CA certificate, and query network information.
Think of these endpoints as the control panel for Ghost’s traffic capture — they’re the switches and dials that determine whether traffic is being intercepted, how it’s being modified (throttled, bypassed), and whether the operating system knows to send traffic through Ghost in the first place.
Proxy Status
Section titled “Proxy Status”GET /api/v1/proxy/statusReturns the current state of the proxy server — whether it’s running, whether it can actually capture traffic, which port it’s listening on, and the health of the network connection.
Response:
{ "running": true, "capturing": true, "port": 4545, "addr": ":4545", "session_id": "01HWXYZ...", "network_available": true}| Field | Type | Description |
|---|---|---|
running | boolean | Whether the proxy listener is active (TCP socket is open and accepting connections) |
capturing | boolean | Whether traffic can actually flow through Ghost — a computed field that goes beyond just “running” (see below) |
port | integer | The TCP port the proxy is listening on. If the proxy was configured with port 0 (ephemeral), this shows the actual port assigned by the OS |
addr | string | The full listener address string (e.g., ":4545" meaning all interfaces on port 4545) |
session_id | string | The ID of the currently active session — all captured flows are saved to this session |
network_available | boolean | Whether the host machine has a working network connection (tracked by the watchdog, see below) |
The “Capturing” Field
Section titled “The “Capturing” Field”The capturing field is smarter than just checking if the proxy is running. It answers the question: “If a request happened right now, would Ghost actually see it?” The logic depends on how Ghost is configured to intercept traffic:
- Network down: If the host machine has no network connectivity,
capturingis alwaysfalseregardless of other settings — there’s nothing to capture - Manual proxy or device mode: If the user configured their browser or device to point at Ghost manually (not using the system proxy),
capturingistruewheneverrunningistrue— Ghost is ready to receive traffic, the routing is the user’s responsibility - System proxy mode: If Ghost is configured to use the OS system proxy,
capturingchecks that: (1) the proxy is running, (2) the OS system proxy is enabled, AND (3) the system proxy is actually pointing at Ghost’s port (not at some other proxy). This catches the edge case where a VPN or security agent has overridden the system proxy settings
Start Proxy
Section titled “Start Proxy”POST /api/v1/proxy/startStarts the MITM proxy listener. No request body needed.
What happens internally:
- Checks if already running — returns 409 Conflict if the proxy is already active
- Starts the listener — opens the TCP socket on the configured port. Port binding is synchronous, so “port already in use” errors fail immediately with 500
- Flushes stale connections — calls
ResetConnections()to clear any idle HTTP/1.1 and HTTP/2 connections left over from a previous run (these would try to reuse connections to servers that may have timed them out) - Enables system proxy (if configured) — when the proxy method is set to
"system_proxy", Ghost tells the OS to route all traffic through itself using a PAC (Proxy Auto-Config) file - Detects VPN interference — checks if a VPN or security agent (like GlobalProtect) is overriding the proxy settings at the macOS SCDynamicStore level. If detected, requests admin privileges to override the VPN’s settings so Ghost’s proxy takes priority
- Starts the watchdog — launches a background goroutine that monitors network health and proxy drift every 5 seconds (see Watchdog section below)
- Broadcasts a
proxy.startedWebSocket event with the full proxy status
Response: {"ok": true}
Errors: 409 (already running), 500 (failed to bind port)
Stop Proxy
Section titled “Stop Proxy”POST /api/v1/proxy/stopGracefully stops the proxy. No request body needed.
What happens internally (order matters):
- Checks if running — returns 409 Conflict if the proxy isn’t active
- Stops the watchdog — halts the background monitoring goroutine
- Restores VPN state — if Ghost had overridden a VPN’s proxy settings, restores them
- Disables system proxy FIRST — this is critical. The system proxy must be disabled before the listener shuts down. If the listener closed first, all system traffic would be routed to a dead port, breaking the user’s internet connection until the system proxy is cleaned up
- Retry logic for disable — disabling the system proxy can fail transiently (especially on macOS during network interface transitions), so Ghost retries up to 3 times with a 500ms delay between attempts
- Stops the listener — closes the TCP socket and drains active connections
- Broadcasts a
proxy.stoppedWebSocket event
Response: {"ok": true}
Errors: 409 (not running), 500 (failed to stop)
System Proxy Watchdog
Section titled “System Proxy Watchdog”Not an API endpoint, but critical background behavior that starts when the proxy starts. The watchdog runs every 5 seconds and handles three scenarios:
Network goes down (was up, now down):
- Immediately disables the system proxy — if the upstream network is dead, routing traffic through Ghost just adds a hop to a dead end
- Broadcasts a
proxy.statusevent so the frontend shows the network-down state
Network comes back (was down, now up):
- Flushes all stale connections (old HTTP keep-alive connections won’t work after a network drop)
- Waits 2 seconds for the network stack to stabilize (DHCP lease renewal, DNS resolution, route table updates)
- Re-enables the system proxy
- Broadcasts a
proxy.statusevent
Proxy drift (network is stable):
- Checks if all network services still have Ghost’s proxy settings — on macOS, VPN clients, security agents, and network interface changes can silently reset the proxy configuration
- If drift is detected, re-applies Ghost’s proxy settings
- Also checks the effective proxy via
scutil --proxy— if a VPN has overridden Ghost at the SCDynamicStore level mid-session, applies the VPN state override
System Proxy Enable
Section titled “System Proxy Enable”POST /api/v1/proxy/system/enableConfigures the operating system to route HTTP/HTTPS traffic through Ghost’s proxy.
Request body (1 MB limit):
{ "host": "127.0.0.1", "port": 4545}| Field | Required | Default | Description |
|---|---|---|---|
host | No | "127.0.0.1" | The host address to set in the system proxy configuration |
port | Yes | — | The proxy port. Must be greater than 0 |
Response: {"ok": true}
Errors: 400 (invalid body or port ≤ 0), 501 (platform doesn’t support system proxy — e.g., some Linux configurations), 500 (enable failed)
System Proxy Disable
Section titled “System Proxy Disable”POST /api/v1/proxy/system/disableRemoves the system proxy configuration, returning the OS to its default “direct connection” state. No request body needed.
Response: {"ok": true}
Errors: 501 (unsupported platform), 500 (disable failed)
System Proxy Status
Section titled “System Proxy Status”GET /api/v1/proxy/system/statusChecks whether the OS system proxy is currently enabled and where it’s pointing.
Response:
{ "enabled": true, "host": "127.0.0.1", "port": 4545}| Field | Type | Description |
|---|---|---|
enabled | boolean | Whether the OS is currently routing traffic through a proxy |
host | string | The proxy host address configured in the OS |
port | integer | The proxy port configured in the OS |
Errors: 501 (unsupported platform), 500 (status check failed)
PAC File
Section titled “PAC File”GET /proxy.pacNo authentication required — this endpoint is accessed by the OS proxy auto-configuration system, which can’t include auth headers.
Serves a PAC (Proxy Auto-Configuration) file — a small JavaScript function that the OS evaluates for every HTTP request to decide whether to use a proxy.
Ghost’s PAC file routes:
- Direct (no proxy):
localhost,127.0.0.1,::1, and plain hostnames (no dots — these are typically local network names) - Through Ghost: Everything else →
PROXY 127.0.0.1:{proxyPort}; DIRECT(try Ghost first, fall back to direct if Ghost is unreachable)
Content-Type: application/x-ns-proxy-autoconfig
Cache-Control: no-cache (prevents the OS from caching stale PAC files after Ghost restarts on a different port)
Network Throttling
Section titled “Network Throttling”Ghost can simulate slow network conditions by limiting bandwidth and adding latency. This is useful for testing how your application behaves on slow connections (3G, EDGE) or high-latency networks.
Get Current Throttle
Section titled “Get Current Throttle”GET /api/v1/proxy/throttleReturns the current throttling configuration.
When throttling is disabled:
{ "enabled": false}When throttling is enabled:
{ "enabled": true, "profile": { "name": "3G", "download_kbps": 750, "upload_kbps": 250, "latency_ms": 100 }}Set Throttle
Section titled “Set Throttle”POST /api/v1/proxy/throttleActivates network throttling. You can either use a built-in preset name or specify custom values.
Using a preset:
{ "preset": "slow-3g"}Using custom values:
{ "download_kbps": 500, "upload_kbps": 200, "latency_ms": 150}Validation:
- If
presetis provided, it must match one of the preset keys (see table below) — otherwise returns 400 - If no preset, at least one of
download_kbps,upload_kbps, orlatency_msmust be non-zero — setting all three to zero is meaningless (that’s just “no throttle”) - A value of 0 for any field means “unlimited” (no restriction on that dimension)
Response: The throttle state after applying (same format as GET)
Broadcasts a proxy.throttle WebSocket event with the new state.
Built-in Presets
Section titled “Built-in Presets”| Preset Key | Display Name | Download (kbps) | Upload (kbps) | Latency (ms) |
|---|---|---|---|---|
"slow-3g" | Slow 3G | 400 | 400 | 200 |
"3g" | 3G | 750 | 250 | 100 |
"4g" | 4G | 4,000 | 3,000 | 20 |
"wifi" | WiFi | 10,000 | 5,000 | 2 |
"edge" | EDGE | 240 | 200 | 300 |
These simulate real-world network conditions. The implementation uses golang.org/x/time/rate token buckets with a 32 KB burst size — data is released in chunks rather than byte-by-byte, which models real network behavior (data arrives in packets, not continuous streams). Latency is injected once per connection on the first read, simulating the initial connection delay.
Clear Throttle
Section titled “Clear Throttle”DELETE /api/v1/proxy/throttleRemoves all throttling — traffic flows at full speed. No request body needed.
Response: {"ok": true}
Broadcasts a proxy.throttle WebSocket event with {"enabled": false}.
List Presets
Section titled “List Presets”GET /api/v1/proxy/throttle/presetsReturns all built-in throttle profiles as a JSON array.
Response:
[ {"name": "3G", "download_kbps": 750, "upload_kbps": 250, "latency_ms": 100}, {"name": "Slow 3G", "download_kbps": 400, "upload_kbps": 400, "latency_ms": 200}, {"name": "4G", "download_kbps": 4000, "upload_kbps": 3000, "latency_ms": 20}, {"name": "WiFi", "download_kbps": 10000, "upload_kbps": 5000, "latency_ms": 2}, {"name": "EDGE", "download_kbps": 240, "upload_kbps": 200, "latency_ms": 300}]SSL Bypass
Section titled “SSL Bypass”Some hosts should not be intercepted — either because they use certificate pinning (your app checks that the certificate is exactly the one it expects, not Ghost’s generated certificate) or because you simply don’t need to see that traffic. SSL bypass tells Ghost to pass these connections through as raw TCP tunnels without any interception.
Get Bypass Hosts
Section titled “Get Bypass Hosts”GET /api/v1/proxy/ssl-bypassReturns the current list of SSL bypass host patterns.
Response:
{ "hosts": ["*.apple.com", "accounts.google.com"]}The hosts field may be null if no bypass hosts are configured (empty list).
Set Bypass Hosts
Section titled “Set Bypass Hosts”PUT /api/v1/proxy/ssl-bypassReplaces the entire bypass list (not append — send the complete list).
Request body (1 MB limit):
{ "hosts": ["*.apple.com", "accounts.google.com", "*.googleapis.com"]}Patterns support:
- Exact match:
accounts.google.com— only that exact hostname - Wildcard suffix:
*.apple.com— any subdomain of apple.com (e.g.,push.apple.com,api.apple.com)
The bypass list is both applied to the running proxy immediately (in-memory update) and persisted to the config file (so it survives restarts). The config is copied before mutation to avoid race conditions with concurrent readers.
Response: {"ok": true}
Certificates
Section titled “Certificates”Ghost generates its own Certificate Authority (CA) to create per-host certificates on the fly. These endpoints manage the CA certificate — downloading it, checking its status, installing it into the OS trust store, and verifying the installation.
Download CA Certificate
Section titled “Download CA Certificate”GET /api/v1/cert/ca.crtDownloads Ghost’s CA certificate in PEM format. This is the certificate you need to install on devices or in browsers to trust Ghost’s generated certificates.
Content-Type: application/x-pem-file
Content-Disposition: attachment; filename="ghost-ca.crt"
Returns 404 if no CA certificate is loaded (shouldn’t happen in normal operation — the CA is created on first run).
Certificate Status
Section titled “Certificate Status”GET /api/v1/cert/statusReturns detailed information about the loaded CA certificate.
Response:
{ "loaded": true, "subject": "Ghost Root CA", "issuer": "Ghost Root CA", "not_before": "2024-01-01T00:00:00Z", "not_after": "2034-01-01T00:00:00Z", "serial": "123456789...", "is_ca": true}| Field | Type | Description |
|---|---|---|
loaded | boolean | Whether a CA certificate is loaded in memory. If false, all other fields are zero-valued |
subject | string | The certificate’s Common Name (CN) — always “Ghost Root CA” |
issuer | string | Who signed this certificate — same as subject since it’s self-signed |
not_before | ISO 8601 | When the certificate becomes valid (1 hour backdated from creation to handle clock skew) |
not_after | ISO 8601 | When the certificate expires (10 years from creation) |
serial | string | The certificate’s serial number (128-bit random value) |
is_ca | boolean | Whether this is a CA certificate (always true — this is the root CA, not a leaf cert) |
Install Certificate
Section titled “Install Certificate”POST /api/v1/cert/installInstalls Ghost’s CA certificate into the operating system’s trust store so the OS (and browsers that use the OS store) will trust Ghost’s generated certificates. No request body needed.
Platform behavior:
- macOS: Adds to the Login Keychain using the
securitycommand-line tool (60-second timeout, 10-second verification) - Windows: Adds to the Current User Root store using
certutil -user(10-second timeout, no UAC prompt needed) - Linux: Installs to the system CA bundle — auto-detects the distro family from
/etc/os-release(Debian, Red Hat, Arch, SUSE) and uses the appropriate command (30-second timeout, requires sudo)
Response: {"ok": true}
Errors: 501 (unsupported platform), 500 (installation failed)
Check Installation
Section titled “Check Installation”GET /api/v1/cert/installedChecks whether Ghost’s CA certificate is currently installed and trusted by the OS.
Response:
{ "installed": true, "store": "Login Keychain", "method": "security"}| Field | Type | Description |
|---|---|---|
installed | boolean | Whether Ghost’s CA cert is in the OS trust store |
store | string | Which trust store it was found in (platform-specific name) |
method | string | The tool/method used to check (platform-specific) |
Errors: 501 (unsupported platform), 500 (check failed)
Network Info
Section titled “Network Info”GET /api/v1/network/infoReturns the machine’s local IP addresses and the proxy port. Used by the frontend to generate QR codes and display setup instructions for mobile devices.
Response:
{ "local_ips": ["192.168.1.100", "10.0.0.5"], "proxy_port": 4545}The local_ips array contains all non-loopback IPv4 addresses found on the machine’s network interfaces. This typically includes your Wi-Fi address and any VPN tunnel addresses.
WebSocket Events
Section titled “WebSocket Events”| Event | Trigger | Payload |
|---|---|---|
proxy.started | Proxy started via POST /start | Full ProxyStatusResponse (running, capturing, port, addr, session_id, network_available) |
proxy.stopped | Proxy stopped via POST /stop | Full ProxyStatusResponse |
proxy.status | Network state change or proxy drift detected by watchdog | Full ProxyStatusResponse |
proxy.throttle | Throttle set or cleared | {"enabled": true, "profile": {...}} or {"enabled": false} |
Summary of Endpoints
Section titled “Summary of Endpoints”| Method | Path | Purpose |
|---|---|---|
| GET | /proxy.pac | PAC file for auto-configuration (no auth) |
| GET | /api/v1/proxy/status | Current proxy state |
| POST | /api/v1/proxy/start | Start the proxy |
| POST | /api/v1/proxy/stop | Stop the proxy |
| POST | /api/v1/proxy/system/enable | Enable OS system proxy |
| POST | /api/v1/proxy/system/disable | Disable OS system proxy |
| GET | /api/v1/proxy/system/status | Check OS system proxy state |
| GET | /api/v1/proxy/throttle | Get current throttle |
| POST | /api/v1/proxy/throttle | Set throttle (preset or custom) |
| DELETE | /api/v1/proxy/throttle | Clear throttle |
| GET | /api/v1/proxy/throttle/presets | List built-in throttle profiles |
| GET | /api/v1/proxy/ssl-bypass | Get SSL bypass host list |
| PUT | /api/v1/proxy/ssl-bypass | Set SSL bypass host list |
| GET | /api/v1/cert/ca.crt | Download CA certificate |
| GET | /api/v1/cert/status | CA certificate details |
| POST | /api/v1/cert/install | Install CA to OS trust store |
| GET | /api/v1/cert/installed | Check CA installation |
| GET | /api/v1/network/info | Local IPs and proxy port |