Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Proxelar is a scriptable local traffic workbench written in Rust. It sits between a client and an upstream service so you can inspect, intercept, replay, and modify HTTP, HTTPS, and WebSocket traffic.

It is aimed at development and debugging workflows: API inspection, local service mocking, request/response rewriting, WebSocket debugging, and repeatable traffic transforms without changing the application under test.

What can it do?

  • Inspect traffic — see every request and response in real time, including headers and bodies
  • Intercept HTTPS — automatic CA certificate generation and per-host certificate minting
  • Modify traffic with scripts and rules — hot-reload Lua hooks or declare maps, redirects, mocks, and header changes
  • Package repeatable extensions — verify, install, discover, and run versioned integrity-checked Lua addons
  • Six capture modes — forward, reverse, WireGuard, SOCKS5, DNS, and fixed-target UDP
  • Four interfaces — interactive TUI, plain terminal output, web GUI, or headless REST API
  • Inspect WebSockets — capture WebSocket connections and browse individual frames
  • Keep portable sessions — reload native captures or exchange HAR, curl, and raw HTTP artifacts with default secret redaction

What is it not?

Proxelar is not trying to replace a mature security suite. If you need scanning, collaborative testing, a large pre-existing addon inventory, or end-to-end HTTP/2/HTTP/3 interception, use a tool built for that workflow. Proxelar is deliberately smaller: a local, scriptable proxy that is easy to install, run, and automate.

Architecture

Proxelar is built as a three-crate Rust workspace:

  • proxelar-cli — the CLI binary with terminal, TUI, web, and API interfaces
  • proxyapi — the core proxy engine, usable as a standalone library
  • proxyapi_models — shared request/response data types

The proxy engine is built on hyper 1.x, rustls 0.23, and tokio. HTTPS interception uses OpenSSL for certificate generation and rustls for TLS termination. Lua scripting is powered by mlua with a vendored Lua 5.4.

Installation

Homebrew (macOS / Linux)

brew install proxelar

winget (Windows)

winget install --id EmanueleMicheletti.Proxelar --exact

Docker / Podman

# Web GUI
docker run --rm -it -v ~/.proxelar:/root/.proxelar -p 8080:8080 -p 127.0.0.1:8081:8081 ghcr.io/emanuele-em/proxelar --interface gui --addr 0.0.0.0

# Terminal
docker run --rm -it -v ~/.proxelar:/root/.proxelar -p 8080:8080 ghcr.io/emanuele-em/proxelar --interface terminal --addr 0.0.0.0

The -v ~/.proxelar:/root/.proxelar mount reuses your existing trusted CA certificate, so you do not get browser warnings after trusting the CA once.

The published image is linux/amd64. To build it yourself, or to run on another architecture, use the Dockerfile in the repository:

docker build -t proxelar .

From crates.io

cargo install proxelar

This builds and installs the proxelar binary. Lua 5.4 and OpenSSL are vendored and compiled from source, so no system dependencies are required beyond a Rust toolchain.

From source

git clone https://github.com/emanuele-em/proxelar.git
cd proxelar
cargo build --release

The binary is at target/release/proxelar.

Without Lua scripting

If you don’t need scripting and want a smaller build:

cargo install proxelar --no-default-features

Requirements

  • Rust 1.97.1 or later
  • Works on Linux, macOS, and Windows

Quick Start

1. Start the proxy

proxelar

This starts Proxelar in forward proxy mode with the interactive TUI on 127.0.0.1:8080.

2. Install the CA certificate

Configure your system or browser proxy to 127.0.0.1:8080, then visit http://proxel.ar through the proxy. The page provides direct certificate downloads and platform-specific installation instructions.

Alternatively, manually install ~/.proxelar/proxelar-ca.pem. See CA Certificate for all platforms.

3. Browse through the proxy

All HTTP and HTTPS traffic now flows through Proxelar and appears in the TUI. Press ? for the full keybinding reference, or see Interfaces for details on the TUI, terminal, and web GUI modes.

4. Try a Lua script

Create a file called script.lua:

function on_request(request)
    request.headers["X-Proxied-By"] = "proxelar"
    return request
end

Run Proxelar with the script:

proxelar --script script.lua

Every request passing through the proxy now has the X-Proxied-By header injected.

CA Certificate

Proxelar intercepts HTTPS traffic by generating a local Certificate Authority (CA) and minting per-host leaf certificates on the fly. For this to work, your system must trust the Proxelar CA.

Automatic generation

On first run, Proxelar generates a 4096-bit RSA CA certificate and private key in ~/.proxelar/:

  • ~/.proxelar/proxelar-ca.pem — CA certificate
  • ~/.proxelar/proxelar-ca.key — CA private key (mode 0600)

If these files already exist, they are reused.

Certificate download server

The easiest way to install the CA is through the built-in download server. With the proxy running, visit:

http://proxel.ar

This page provides:

  • Direct download links for PEM and DER formats
  • Platform-specific installation instructions for macOS, Linux, Windows, iOS, and Android

Manual installation

macOS

sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain \
  ~/.proxelar/proxelar-ca.pem

Linux (Debian/Ubuntu)

sudo cp ~/.proxelar/proxelar-ca.pem /usr/local/share/ca-certificates/proxelar.crt
sudo update-ca-certificates

Linux (Fedora/RHEL)

sudo cp ~/.proxelar/proxelar-ca.pem /etc/pki/ca-trust/source/anchors/proxelar.pem
sudo update-ca-trust

Windows

certutil -addstore -f "ROOT" %USERPROFILE%\.proxelar\proxelar-ca.pem

Firefox

Firefox uses its own certificate store. Go to Settings > Privacy & Security > Certificates > View Certificates > Import, and select ~/.proxelar/proxelar-ca.pem.

Custom CA directory

Use --ca-dir to store the CA files in a different location:

proxelar --ca-dir /path/to/certs

Removing the CA

When you are done, remove the Proxelar CA from every trust store where you installed it. The generated files live in ~/.proxelar/ by default, but deleting those files does not remove trust from your OS, browser, or mobile device.

See CA trust and uninstall for platform-specific uninstall notes and limitations such as certificate pinning.

Per-host certificate caching

Leaf certificates are cached in memory (up to 1,000 hosts). Repeated connections to the same host reuse the cached certificate instead of generating a new one.

Inspect browser and curl traffic

This guide gets a basic HTTP and HTTPS capture working with the default forward proxy mode.

Start Proxelar

proxelar

The proxy listens on 127.0.0.1:8080 and opens the TUI.

Test with curl

Plain HTTP works without a certificate:

curl -x http://127.0.0.1:8080 http://httpbin.org/get

For HTTPS, curl must trust the generated Proxelar CA:

curl --proxy http://127.0.0.1:8080 \
  --cacert ~/.proxelar/proxelar-ca.pem \
  https://httpbin.org/get

The request and response should appear in the TUI. Press Enter to open details, Tab to switch request/response tabs, and / to filter.

Configure a browser

Set both HTTP and HTTPS proxy to:

127.0.0.1:8080

Then browse to:

http://proxel.ar

Download and trust the Proxelar CA using the instructions shown on that page. After the CA is trusted, HTTPS pages should appear in Proxelar.

Firefox uses its own certificate store unless configured to use the system store. Import ~/.proxelar/proxelar-ca.pem in Firefox settings if HTTPS traffic still shows certificate warnings.

Troubleshooting

  • If HTTP works but HTTPS fails, the client does not trust the Proxelar CA.
  • If nothing appears, confirm the client is actually using 127.0.0.1:8080 as both HTTP and HTTPS proxy.
  • If an app uses certificate pinning, Proxelar cannot decrypt it without changing the app or test configuration.
  • On Android 7+, user-installed CAs are trusted only by apps that opt in through network security configuration.

Mock or modify a local API

Reverse proxy mode puts Proxelar in front of a service without configuring the client as an explicit proxy. This is useful for local frontend development, API testing, and scripted response changes.

Start a reverse proxy

If your app normally calls a backend on http://localhost:3000, run:

proxelar -m reverse --target http://localhost:3000

Clients connect to:

http://127.0.0.1:8080

Proxelar forwards requests to http://localhost:3000 while preserving the path and query string.

Mock one endpoint

Create mock_user.lua:

function on_request(request)
    if request.method == "GET" and string.find(request.url, "/api/user/me") then
        return {
            status = 200,
            headers = { ["Content-Type"] = "application/json" },
            body = '{"id":1,"name":"Local Test User"}',
        }
    end
end

Run:

proxelar -m reverse --target http://localhost:3000 --script mock_user.lua

Requests to /api/user/me are answered by the script. Other requests pass through to the target.

Inject development headers

function on_request(request)
    request.headers["Authorization"] = "Bearer local-dev-token"
    request.headers["X-Forwarded-By"] = "proxelar"
    return request
end

Simulate a failure

function on_request(request)
    if string.find(request.url, "/api/payments") then
        return {
            status = 503,
            headers = { ["Content-Type"] = "application/json" },
            body = '{"error":"payments unavailable in local test"}',
        }
    end
end

Notes

  • Reverse proxy mode does not require browser or OS proxy configuration.
  • Use -i gui if you prefer the web UI while developing.
  • Use --upstream-trust default+ca:/path/to/ca.pem if the upstream service uses a private HTTPS CA.
  • For HTTPS clients connecting to Proxelar itself, use forward proxy mode today; reverse proxy TLS termination for inbound clients is not the main supported workflow.

Lua recipes

Lua scripts define on_request and/or on_response hooks. Return a modified table to continue, return nil to pass through unchanged, or return a response table from on_request to short-circuit the upstream request.

Add a request header

function on_request(request)
    request.headers["X-Proxied-By"] = "proxelar"
    return request
end

Remove cookies

function on_request(request)
    request.headers["cookie"] = nil
    return request
end

Add CORS response headers

function on_response(request, response)
    response.headers["Access-Control-Allow-Origin"] = "*"
    response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
    response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
    return response
end

Block a domain

local blocked = { "ads%.example%.com", "tracker%.example%.com" }

function on_request(request)
    for _, pattern in ipairs(blocked) do
        if string.find(request.url, pattern) then
            return {
                status = 403,
                headers = { ["Content-Type"] = "text/plain" },
                body = "Blocked by Proxelar",
            }
        end
    end
end

Modify a JSON response

function on_response(request, response)
    local ct = response.headers["content-type"] or ""
    if not string.find(ct, "application/json") then return end

    if string.sub(response.body, 1, 1) == "{" then
        response.body = '{"proxied":true,' .. string.sub(response.body, 2)
    end
    return response
end

Use the checked-in examples

The repository includes complete scripts in examples/scripts/:

  • add_header.lua
  • auth_inject.lua
  • block_domain.lua
  • filter_by_method.lua
  • inject_cors.lua
  • log_traffic.lua
  • mock_api.lua
  • modify_json_response.lua
  • redirect.lua
  • request_body_modify.lua
  • rewrite_html.lua
  • strip_cookies.lua

See the Lua API reference for all fields and return values.

CA trust and uninstall

Proxelar decrypts HTTPS by generating a local Certificate Authority and minting per-host certificates. Clients must trust that CA before HTTPS interception works.

Generated files

By default, Proxelar stores the CA files in:

~/.proxelar/proxelar-ca.pem
~/.proxelar/proxelar-ca.key

The private key stays on your machine. Anyone with the key can mint certificates trusted by clients where you installed the CA, so treat it as sensitive.

Install through the built-in page

Start Proxelar, configure your browser or device to use 127.0.0.1:8080, then visit:

http://proxel.ar

The page provides PEM/DER downloads and platform notes.

Uninstall notes

Remove trust from every place where you installed the CA:

  • macOS: open Keychain Access, find the Proxelar certificate, and delete it from the trusted keychain.
  • Linux: remove the certificate from /usr/local/share/ca-certificates/ or /etc/pki/ca-trust/source/anchors/, then run the platform trust update command.
  • Windows: open Certificate Manager or run certmgr.msc, find the Proxelar root under trusted root authorities, and delete it.
  • Firefox: remove it from Settings > Privacy & Security > Certificates > View Certificates.
  • iOS/Android: remove the installed profile or user CA from system settings.

After trust is removed, deleting ~/.proxelar/ removes Proxelar’s local copy of the certificate and key.

Limitations

  • Certificate-pinned apps usually reject Proxelar’s generated certificates.
  • Android 7+ apps trust user-installed CAs only if the app opts in.
  • Some corporate-managed devices block custom CA installation.
  • If you bind Proxelar to a network interface, other devices can reach the proxy. Only do this on trusted networks and with a clear reason.

Sessions and export

Proxelar records completed HTTP flows, WebSocket connections and frames, observed raw TCP chunks, DNS exchanges, and raw UDP datagrams in one versioned session. Load prior traffic before starting and save the combined history when Proxelar shuts down cleanly:

proxelar --load-session previous.proxelar.json \
  --save-session combined.proxelar.json

Press Ctrl+C to finalize outputs. --load-session and --import-har are mutually exclusive.

Interoperable formats

proxelar --import-har input.har \
  --export-har output.har \
  --export-curl requests.sh \
  --export-raw raw-flows/
  • HAR import/export carries HTTP request and response data.
  • curl export writes one reproducible command per HTTP request.
  • raw export writes request/response pairs without collapsing duplicate headers.
  • the native format also preserves WebSocket frames, raw TCP chunks, DNS and UDP exchanges, body truncation metadata, and stable flow IDs.

HAR cannot represent all native session data. Keep the native file when capture fidelity matters.

Secret handling

HAR, curl, and raw exports redact Authorization, Proxy-Authorization, Cookie, Set-Cookie, and common secret query parameters by default. Use --export-secrets only when the output will remain in a trusted location.

Native --save-session files are lossless and are not redacted. Treat them as credentials-bearing debugging artifacts: restrict permissions, do not commit them, and delete them when no longer needed.

See Session format for compatibility details.

Rules and headless API

Declarative routing rules

Use repeatable command-line mappings for common cases:

proxelar \
  --map-local 'https://app.test/assets/=./fixtures/assets' \
  --map-remote 'https://api.test/v1/=http://127.0.0.1:3000/'

For mocks, redirects, and header changes, pass a JSON file with --rules rules.json:

{
  "rules": [
    {
      "action": "set_request_header",
      "url_prefix": "https://api.test/",
      "name": "x-debug-client",
      "value": "proxelar"
    },
    {
      "action": "mock",
      "url_prefix": "https://api.test/health",
      "method": "GET",
      "status": 200,
      "headers": [{ "name": "content-type", "value": "application/json" }],
      "body": "{\"ok\":true}"
    },
    {
      "action": "redirect",
      "url_prefix": "https://old.test/",
      "location": "https://new.test/",
      "status": 302
    }
  ]
}

Rules run in file order. Header changes can accumulate; the first matching response-producing rule wins. Map-local paths are constrained to the configured directory and reject traversal.

Supported action values are map_local, map_remote, redirect, mock, set_request_header, and remove_request_header.

Headless REST API

Start the API without opening the GUI:

proxelar -i api --api-token "$PROXELAR_TOKEN"

The server listens on --gui-port (8081 by default). Send Authorization: Bearer <token> on every request:

curl -H "Authorization: Bearer $PROXELAR_TOKEN" \
  'http://127.0.0.1:8081/api/v1/flows?filter=method:POST%20%26%20status:500'
Method and pathPurpose
GET /api/v1/statusVersion, capture counts, and intercept state
GET /api/v1/sessionComplete native session snapshot
GET /api/v1/flows?filter=...HTTP flows using the shared filter language
GET /api/v1/filter?filter=...Matching HTTP, WebSocket, TCP, DNS, and UDP IDs
GET /api/v1/flows/{id}One HTTP flow
DELETE /api/v1/flowsClear recorded traffic
GET /api/v1/flows/{id}/content/requestDecoded request content view
GET /api/v1/flows/{id}/content/responseDecoded response content view
POST /api/v1/flows/{id}/replayQueue a request replay
PUT /api/v1/interceptSet intercept with { "enabled": true }
POST /api/v1/intercept/{id}Resolve with forward, drop, or modify

For a modify decision, body can be a UTF-8 string or { "bytes": [0, 255, ...] } for lossless binary editing. Header input accepts either a JSON object (string or string-array values) or an ordered list of { "name", "value" } entries when duplicate order matters.

Filter terms include host:, method:, status:, type:, body:, header:, request_body:, and response_body:. Combine terms with &, |, !, parentheses, or adjacent implicit AND. The aliases ~d, ~m, ~s, ~t, ~b, and ~h are also accepted.

The API is intended for one trusted local operator. A token is authentication, not transport security; keep the listener on loopback or put remote access behind an authenticated TLS tunnel.

Forward Proxy

Forward proxy is the default mode. Clients send their traffic to Proxelar, which forwards it to the destination server. This is the standard setup for inspecting browser or application traffic.

Usage

proxelar

Configure your client (browser, curl, application) to use 127.0.0.1:8080 as the HTTP/HTTPS proxy.

How it works

  1. The client sends a request to the proxy
  2. For HTTPS, the client sends a CONNECT request. Proxelar upgrades the connection and detects the protocol:
    • TLS ClientHello — generates a leaf certificate for the target host, terminates TLS, and inspects the decrypted traffic
    • Plain HTTP (e.g., GET prefix) — serves the stream directly
    • Unknown protocol — tunnels the raw TCP connection without inspection
  3. For plain HTTP, the request is forwarded directly
  4. Lua on_request / on_response hooks run at each step (if a script is loaded)

Examples

# Start forward proxy on default port
proxelar

# Custom port and bind address
proxelar -p 9090 -b 0.0.0.0

# With terminal output instead of TUI
proxelar -i terminal

# With a Lua script
proxelar --script block_ads.lua

# Test with curl
curl -x http://127.0.0.1:8080 http://httpbin.org/get
curl -x http://127.0.0.1:8080 https://httpbin.org/get

# Trust an extra private CA when Proxelar connects to upstream HTTPS servers
proxelar --upstream-trust default+ca:/path/to/ca.pem

For upstream HTTPS, Proxelar uses bundled Mozilla/WebPKI roots by default. --upstream-trust ca-only:/path/to/ca.pem trusts only a supplied CA file, and --upstream-trust insecure disables upstream certificate and hostname verification for controlled debugging. insecure makes upstream HTTPS traffic vulnerable to MITM.

Reverse Proxy

In reverse proxy mode, Proxelar sits in front of a backend service. Clients connect to Proxelar directly (without proxy configuration), and all requests are forwarded to the specified target.

This is useful for debugging local APIs, injecting headers, mocking endpoints, or testing how your frontend handles modified responses.

Usage

proxelar -m reverse --target http://localhost:3000

Clients connect to http://127.0.0.1:8080 and Proxelar forwards everything to http://localhost:3000.

How it works

  1. The client sends a request to 127.0.0.1:8080
  2. Proxelar rewrites the URI to the target (preserving path and query)
  3. The Host header is updated to match the target
  4. Lua on_request / on_response hooks run (if a script is loaded)
  5. The response is returned to the client

Examples

# Reverse proxy to a local service
proxelar -m reverse --target http://localhost:3000

# Custom port (clients connect to 4000, forwarded to 3000)
proxelar -m reverse --target http://localhost:3000 -p 4000

# With a Lua script that injects auth headers
proxelar -m reverse --target http://localhost:3000 --script auth_dev.lua

# With web GUI
proxelar -m reverse --target http://localhost:3000 -i gui

# HTTPS upstream with a private CA
proxelar -m reverse --target https://localhost:3000 --upstream-trust default+ca:/path/to/ca.pem

For upstream HTTPS, Proxelar uses bundled Mozilla/WebPKI roots by default. Use --upstream-trust default+ca:/path/to/ca.pem to add a private CA, --upstream-trust ca-only:/path/to/ca.pem to trust only that CA, or --upstream-trust insecure for temporary debugging without certificate or hostname verification. insecure makes upstream HTTPS traffic vulnerable to MITM.

Common use cases with scripting

Inject authentication

function on_request(request)
    request.headers["Authorization"] = "Bearer dev-token-12345"
    return request
end

Add security headers

function on_response(request, response)
    response.headers["Strict-Transport-Security"] = "max-age=31536000"
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    return response
end

Simulate errors

function on_request(request)
    if string.find(request.url, "/api/payments") then
        return {
            status = 500,
            headers = { ["Content-Type"] = "application/json" },
            body = '{"error": "Internal Server Error"}',
        }
    end
end

WireGuard, SOCKS5, DNS, and raw UDP modes

WireGuard capture

proxelar -m wireguard -b 0.0.0.0 -p 51820 \
  --wireguard-endpoint 192.168.1.10:51820

WireGuard mode accepts a mobile or IoT device without firewall rules or system-proxy configuration. On first start it creates owner-only server/client keys and ~/.proxelar/proxelar-wg.conf. The TUI and authenticated web GUI show the profile as a scannable QR code while the capture is empty; terminal mode prints the same QR at startup. You can also import the file directly. The short proxelar-wg profile name stays within Android’s 15-character WireGuard interface-name limit.

The QR code contains the client private key. Display it only on a trusted screen. It disappears from the TUI and web GUI after the first captured event, although the configuration file remains available for later import.

TCP is reconstructed in a userspace network stack and follows the normal HTTP, TLS MITM, WebSocket, and raw-stream paths. UDP is relayed to its original destination, while port 53 uses --dns-upstream and repeatable --dns-map overrides.

The endpoint must be reachable by the device. When binding to a concrete address it is derived automatically; when binding to 0.0.0.0 or ::, Proxelar probes the route to the configured DNS server. Set --wireguard-endpoint explicitly for NAT, containers, multiple network interfaces, or a public hostname. Deleting the three wireguard-* files in the CA directory rotates the generated single-client identity; this also requires re-importing the client config.

SOCKS5

proxelar -m socks5 -p 1080

The SOCKS5 listener supports unauthenticated CONNECT requests with IPv4, IPv6, and domain targets. HTTP traffic is inspected, TLS traffic uses the normal local-CA MITM flow, and unknown protocols fall back to observed raw TCP tunneling. Bind it to loopback unless exposure is intentional; client authentication is not currently implemented.

Upstream chaining

Any HTTP proxy mode can route outbound connections through another HTTP CONNECT or SOCKS5 proxy:

proxelar --upstream-proxy http://proxy.example:8080
proxelar --upstream-proxy socks5://127.0.0.1:9050
proxelar --upstream-proxy http://proxy.example:8080 \
  --upstream-proxy-auth 'user:password'

This applies consistently to ordinary forwarding, reverse proxy requests, and replay. Credentials are sensitive command-line values and may be visible to local process inspection; prefer a dedicated low-privilege account.

DNS inspection and rewriting

proxelar -m dns -p 5353 --dns-upstream 1.1.1.1:53 \
  --dns-map api.example.test=127.0.0.1

DNS mode is a UDP DNS listener. It records queries and responses, forwards unmatched queries to the configured recursive resolver, and can synthesize A/AAAA answers. It is not a DNS-over-HTTPS resolver.

Fixed-target raw UDP

UDP mode forwards each client datagram to one configured upstream and captures both directions losslessly:

proxelar --mode udp --port 9001 --target upstream.example:9000

This is useful for testing a known datagram service. It is intentionally not an arbitrary-destination router: each invocation has one target, receives at most one response per request, and reports no-response after five seconds.

Intercept & Modify Traffic

Intercept mode pauses requests mid-flight so you can inspect, edit, and decide what to do before they reach the server.

How it works

When intercept is on, every request is held until you act on it. Nothing is forwarded automatically. When intercept is off, traffic flows through normally (still captured and displayed).

TUI

Toggle intercept

Press i to turn intercept on or off. The status bar shows a red INTERCEPT badge when active.

Act on a request

When a request arrives it appears as a row. Navigate to it with j/k, then:

KeyAction
fForward the request (as-is or with your edits)
dDrop — returns a 504 to the client
eOpen the inline editor

Edit inline

Press e to open the editor. The full raw HTTP request is shown and fully editable — method, URI, headers, and body.

POST /api/login HTTP/1.1
host: example.com
content-type: application/json

{"user":"alice","pass":"secret"}
  • Arrow keys / Home / End — move the cursor
  • Enter — insert a new line
  • Backspace / Delete — delete characters
  • Esc — finish editing (request stays held, ready to forward)
  • f — forward (with your edits applied)
  • d — drop
  • Esc (again, when not typing) — discard your edits

Binary bodies — if the original body is not valid UTF-8 the editor shows a ⚠ warning. The content is displayed lossily; edits may corrupt binary data.

Web GUI

Click the ⏸ Intercept: OFF button in the toolbar to enable intercept. The button turns red and shows a pending-request count.

Pending requests appear in the table with an amber left border. Click a row to open the editor panel:

  • Edit the method, URI, headers, and body directly
  • Click Forward to send (with any edits you made)
  • Click Drop (504) to block the request
  • Press Ctrl+Enter as a keyboard shortcut for Forward
  • Press Esc or × to close the panel without acting (request stays pending)

Turning intercept off

Press i (TUI) or click the intercept button (web) again. All pending requests are forwarded immediately so clients do not hang.

Lua Scripting

Proxelar supports Lua scripts that hook into the request/response lifecycle. You can modify headers, rewrite URLs, block requests, mock API responses, transform bodies, and more — all without recompiling or changing your application.

Running a script

proxelar --script my_script.lua

The script is loaded at startup and automatically reloaded after file changes. A valid edit becomes active on the next hook call. If a reload is invalid, Proxelar logs the error and keeps the last known-good script. Hooks apply across HTTP-capable proxy modes.

Writing a script

A script can define any of these global functions:

function on_request(request)
    -- Called before forwarding the request to the upstream server.
    -- Modify and return the request, return a response to short-circuit,
    -- or return nil to pass through unchanged.
end

function on_response(request, response)
    -- Called before returning the response to the client.
    -- Modify and return the response, or return nil to pass through unchanged.
end

function on_websocket_frame(frame)
    -- frame.direction is "client_to_server" or "server_to_client".
    -- frame.opcode names the WebSocket opcode; frame.payload is binary-safe.
    -- Return a string to replace the payload, false to drop, or nil to pass.
end

Every function is optional. If a function is not defined, traffic passes through unchanged.

Portable addon directories

--script also accepts a directory containing init.lua. Pure-Lua modules beside it are automatically available through require, including nested module/init.lua packages:

my-addon/
├── init.lua
└── redact.lua
-- my-addon/init.lua
local redact = require("redact")

function on_request(request)
    return redact.request(request)
end

Run it with proxelar --script ./my-addon. This directory convention is the distributable unit for community addons. Addons must use pure Lua; native C modules are intentionally unavailable.

Validated packages and the local catalog

Published addons should include proxelar-addon.json. Schema version 1 records the package name, semantic version, description, entrypoint, hooks, whether it requires native Lua modules, and a lowercase SHA-256 digest for every package file. Files not declared by the manifest are rejected, as are missing files, digest mismatches, symlinks, special files, absolute paths, and parent traversal.

{
  "schema_version": 1,
  "name": "header-tagger",
  "version": "1.0.0",
  "description": "Adds a diagnostic request header.",
  "entrypoint": "init.lua",
  "hooks": ["request"],
  "requires_native_modules": false,
  "files": {
    "init.lua": "<lowercase SHA-256>"
  }
}

Use the same catalog from development, CI, and production:

proxelar addon verify ./my-addon
proxelar addon install ./my-addon
proxelar addon list
proxelar addon inspect my-addon
proxelar --addon my-addon

Installation validates the complete source package first, copies only regular declared files with private permissions, and atomically renames the result into CA_DIR/addons. It never overwrites an existing version. Use --addons-dir to select another catalog. Manifest-free directories remain accepted through --script for an edit-and-reload development loop; they are deliberately not installable catalog packages.

Request hook

on_request receives a request table and can return one of three things:

  • The request table — forward it (modified or not)
  • A response table (with a status field) — short-circuit and return that response directly, without contacting the upstream server
  • nil (or no return) — pass through unchanged
function on_request(request)
    -- Pass through logging only
    if string.find(request.url, "blocked%.com") then
        return { status = 403, headers = {}, body = "Blocked" }  -- short-circuit
    end

    request.headers["X-Custom"] = "value"
    return request  -- forward modified request
end

Response hook

on_response receives both the request (for context) and the response. It can modify and return the response, or return nil to pass through.

function on_response(request, response)
    response.headers["X-Proxy"] = "proxelar"
    return response
end

Error handling

Script errors are caught, logged, and the request passes through unchanged. A buggy script can never crash the proxy. Check the log output (set RUST_LOG=debug for details) to see script errors.

The same fail-open policy applies to response and WebSocket hooks: runtime failures log and forward the original message.

Native C modules

Native C modules are intentionally unavailable. Proxelar uses mlua’s safe standard-library subset and preserves #![forbid(unsafe_code)] in the core crate. A validated package whose manifest sets requires_native_modules: true fails closed before its entrypoint runs. Prefer pure Lua modules or implement broadly useful functionality in the audited Rust core.

Feature flag

Lua scripting is behind the scripting feature flag, enabled by default. To build without it:

cargo install proxelar --no-default-features

API Reference

Request table

The on_request function receives a table with these fields:

FieldTypeDescription
methodstringHTTP method ("GET", "POST", "PUT", "DELETE", etc.)
urlstringFull request URL ("https://example.com/path?q=1")
headerstableRequest headers (see Headers below)
bodystringRequest body (may contain binary data, empty string for GET/HEAD)

All fields are readable and writable. Modify them in place and return the table to forward the modified request.

body is always plaintext: if the message uses a supported Content-Encoding (gzip, deflate, or br), the proxy decompresses it before calling the hook and re-compresses your result to the same encoding on the way out, refreshing Content-Length. Remove the Content-Encoding header to forward the body uncompressed instead. Any other encoding is passed through untouched. See Content encoding below.

Response table

The on_response function receives two arguments:

  1. request — a table with method and url fields (for context)
  2. response — a table with these fields:
FieldTypeDescription
statusnumberHTTP status code (200, 404, 500, etc.)
headerstableResponse headers
bodystringResponse body (plaintext — see Content encoding)

Short-circuit response

To respond immediately without contacting the upstream server, return a table with a status field from on_request:

return {
    status = 403,
    headers = { ["Content-Type"] = "text/plain" },
    body = "Forbidden",
}

The presence of the status field is what distinguishes a response from a modified request.

Headers

Headers are Lua tables mapping lowercase header names to values.

Single-value headers are plain strings:

request.headers["content-type"]     -- "application/json"
request.headers["authorization"]    -- "Bearer token123"

Multi-value headers (like Set-Cookie) are arrays:

response.headers["set-cookie"]      -- {"session=abc", "lang=en"}

When setting headers, both forms are accepted:

-- Single value (most common)
request.headers["x-custom"] = "value"

-- Multiple values
response.headers["set-cookie"] = {"a=1", "b=2"}

-- Remove a header
request.headers["cookie"] = nil

Content encoding

Scripts work on decompressed bodies. When a request or response carries a Content-Encoding the proxy understands, the body is decoded before your hook runs and re-encoded to the same scheme afterward, with Content-Length updated to match.

Content-EncodingBehavior
gzip / deflate / brDecoded for the hook, re-encoded on output
absent / identityPassed through as-is
anything else (e.g. zstd)Passed through compressed, untouched

To change the wire encoding, edit the Content-Encoding header in your hook:

-- Forward the response uncompressed
response.headers["content-encoding"] = nil
response.body = "now plaintext on the wire"

If re-encoding fails, the proxy strips Content-Encoding and sends the body uncompressed rather than corrupting it. Bodies larger than --body-capture-limit stream through unchanged and are never decoded.

Return values

on_request

ReturnEffect
Request tableForward the (modified) request to upstream
Response table (has status)Short-circuit — return this response directly
nil (or no return)Pass through unchanged

on_response

ReturnEffect
Response tableReturn the (modified) response to the client
nil (or no return)Pass through unchanged

Available Lua standard libraries

Scripts run in a standard Lua 5.4 environment with access to:

  • string — pattern matching, formatting, manipulation
  • table — array/table operations
  • math — mathematical functions
  • os.date(), os.time(), os.clock() — time functions
  • print() — output to proxy stdout
  • tostring(), tonumber(), type() — type conversion

Script Examples

All examples below are complete, working scripts. They are also available in the examples/scripts/ directory.

Add headers to requests

function on_request(request)
    request.headers["X-Forwarded-By"] = "proxelar"
    request.headers["X-Request-Time"] = os.date("%Y-%m-%dT%H:%M:%S")
    return request
end

Block domains

local blocked = {
    "ads%.example%.com",
    "tracker%.example%.com",
    "analytics%.bad%.com",
}

function on_request(request)
    for _, pattern in ipairs(blocked) do
        if string.find(request.url, pattern) then
            return {
                status = 403,
                headers = { ["Content-Type"] = "text/plain" },
                body = "Blocked by Proxelar: " .. request.url,
            }
        end
    end
end

Mock API endpoints

function on_request(request)
    if request.method == "GET" and string.find(request.url, "/api/user/me") then
        return {
            status = 200,
            headers = { ["Content-Type"] = "application/json" },
            body = '{"id": 1, "name": "Test User", "email": "test@example.com"}',
        }
    end
end

Redirect requests to a different host

function on_request(request)
    request.url = string.gsub(request.url, "old%-api%.example%.com", "new-api.example.com")
    return request
end

Inject CORS headers

function on_response(request, response)
    response.headers["Access-Control-Allow-Origin"] = "*"
    response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
    response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
    return response
end

Log traffic to stdout

function on_request(request)
    print(string.format("[REQ] %s %s", request.method, request.url))
end

function on_response(request, response)
    local ct = response.headers["content-type"] or "unknown"
    local size = #response.body
    print(string.format("[RES] %s %s -> %d (%s, %d bytes)",
        request.method, request.url, response.status, ct, size))
end

Run with proxelar -i terminal -q --script log.lua to see only the script’s output, without the proxy’s own per-request lines.

Inject authentication

local TOKEN = "Bearer my-dev-token-12345"

function on_request(request)
    if string.find(request.url, "api%.example%.com") then
        request.headers["Authorization"] = TOKEN
    end
    return request
end

Modify JSON response bodies

function on_response(request, response)
    local ct = response.headers["content-type"] or ""
    if not string.find(ct, "application/json") then return end

    if string.sub(response.body, 1, 1) == "{" then
        response.body = '{"proxied":true,' .. string.sub(response.body, 2)
    end
    return response
end

Inject a banner into HTML pages

function on_response(request, response)
    local ct = response.headers["content-type"] or ""
    if not string.find(ct, "text/html") then return end

    local banner = '<div style="position:fixed;top:0;left:0;right:0;'
        .. 'background:#ff6b35;color:white;text-align:center;'
        .. 'padding:4px;z-index:99999;font-size:12px;">'
        .. 'Proxied by Proxelar</div>'

    response.body = string.gsub(response.body, "<body>", "<body>" .. banner, 1)
    return response
end

Only allow GET and HEAD

function on_request(request)
    if request.method ~= "GET" and request.method ~= "HEAD" then
        return {
            status = 405,
            headers = {
                ["Content-Type"] = "text/plain",
                ["Allow"] = "GET, HEAD",
            },
            body = "Method " .. request.method .. " not allowed by proxy policy",
        }
    end
end

Strip tracking cookies

local tracking_cookies = { "fbp", "_ga", "_gid", "fr", "datr" }

function on_request(request)
    local cookie = request.headers["cookie"]
    if not cookie then return end

    local parts = {}
    for pair in string.gmatch(cookie, "([^;]+)") do
        pair = string.match(pair, "^%s*(.-)%s*$")
        local name = string.match(pair, "^([^=]+)")
        local dominated = false
        for _, tc in ipairs(tracking_cookies) do
            if name == tc then dominated = true; break end
        end
        if not dominated then table.insert(parts, pair) end
    end

    if #parts > 0 then
        request.headers["cookie"] = table.concat(parts, "; ")
    else
        request.headers["cookie"] = nil
    end
    return request
end

Modify POST request bodies

function on_request(request)
    if request.method ~= "POST" then return end
    local ct = request.headers["content-type"] or ""

    if string.find(ct, "application/json") and string.sub(request.body, 1, 1) == "{" then
        request.body = '{"injected_by":"proxelar",' .. string.sub(request.body, 2)
    end
    return request
end

CLI Reference

proxelar [OPTIONS]
proxelar addon <list|inspect|verify|install> [OPTIONS]

Options

FlagShortDefaultDescription
--interface-ituiInterface: terminal, tui, gui, or headless api
--mode-mforwardMode: forward, reverse, wireguard, socks5, dns, or udp
--port-p8080Port to listen on
--addr-b127.0.0.1Bind address
--target-tUpstream URI for reverse or HOST:PORT for UDP
--script-sLua script file or addon directory containing init.lua
--addonLoad a validated installed addon by name (conflicts with --script)
--addons-dirCA_DIR/addonsLocal addon catalog used by runtime and addon commands
--quiet-qSuppress per-request output (only used with -i terminal)
--gui-port8081Web GUI port (only used with -i gui)
--ca-dir~/.proxelarDirectory for CA certificate and key files
--body-capture-limitfreeMaximum body bytes buffered for capture/editing; use free, unlimited, or none for unlimited
--upstream-trustdefaultUpstream TLS trust policy: default, default+ca:/path/ca.pem, ca-only:/path/ca.pem, or insecure
--upstream-proxyChain traffic through http://HOST:PORT or socks5://HOST:PORT
--upstream-proxy-authUpstream proxy credentials as USERNAME:PASSWORD
--load-sessionLoad a native session before capture
--import-harImport HAR before capture (conflicts with --load-session)
--save-sessionSave a native session on clean shutdown
--export-harExport HTTP flows as HAR on clean shutdown
--export-curlExport requests as curl commands on clean shutdown
--export-rawWrite raw request/response files to a directory on clean shutdown
--export-secretsoffDisable default credential/query-secret redaction in exports
--rulesLoad declarative routing rules from JSON
--map-localRepeatable URL_PREFIX=DIR local mapping
--map-remoteRepeatable URL_PREFIX=TARGET_PREFIX rewrite
--api-tokenrandomFixed bearer token for the GUI/headless API
--launch-browseroffLaunch an isolated Chromium-family profile through the proxy
--dns-upstream1.1.1.1:53Recursive resolver used in DNS mode
--dns-mapRepeatable DNS override as DOMAIN=IP
--wireguard-endpointderivedPublic/LAN HOST:PORT written to the generated WireGuard client config

--upstream-trust insecure disables upstream certificate and hostname verification. Use it only for controlled debugging; it makes upstream HTTPS traffic vulnerable to MITM.

Environment variables

VariableDescription
RUST_LOGControls log verbosity. Examples: debug, proxyapi=trace, warn

Examples

# Default: forward proxy with TUI
proxelar

# Terminal output on custom port
proxelar -i terminal -p 9090

# Web GUI accessible from the network
proxelar -i gui -b 0.0.0.0

# Reverse proxy with script
proxelar -m reverse --target http://localhost:3000 --script auth.lua

# Forward proxy with logging script
proxelar --script log_traffic.lua

# Verify, install, discover, and run an integrity-checked addon package
proxelar addon verify ./examples/addons/header-tagger
proxelar addon install ./examples/addons/header-tagger
proxelar addon list
proxelar --addon header-tagger

# Show only the script's print() output, no per-request lines
proxelar -i terminal -q --script log_traffic.lua

# Capture only the first 1 MiB of large bodies while streaming traffic through
proxelar --body-capture-limit 1048576

# Trust a private upstream CA in addition to the default Mozilla roots
proxelar --upstream-trust default+ca:/path/to/ca.pem

# Trust only a private upstream CA
proxelar --upstream-trust ca-only:/path/to/ca.pem

# Capture through a corporate proxy and save redacted interoperable exports
proxelar --upstream-proxy http://proxy.example:8080 \
  --save-session capture.proxelar.json --export-har capture.har

# SOCKS5 listener
proxelar -m socks5 -p 1080

# DNS inspection with a local override
proxelar -m dns -p 5353 --dns-map api.example.test=127.0.0.1

# Fixed-target raw UDP inspection
proxelar -m udp -p 9001 --target upstream.example:9000

# Mobile/IoT capture; scan the displayed QR or import ~/.proxelar/proxelar-wg.conf
proxelar -m wireguard -b 0.0.0.0 -p 51820 \
  --wireguard-endpoint 192.168.1.10:51820

# Headless bearer-token API
proxelar -i api --api-token "$PROXELAR_TOKEN"

Session and export outputs are finalized after Ctrl+C or another clean shutdown. Native session files contain the full captured data; HAR, curl, and raw exports redact authorization, proxy authorization, cookies, and common secret query parameters unless --export-secrets is supplied.

Interfaces

Proxelar provides terminal, TUI, web GUI, and headless API interfaces over the same capture stream.

TUI (default)

proxelar
# or
proxelar -i tui

An interactive terminal interface built with ratatui. Shows a table of all captured requests and WebSocket connections with nine columns: time, protocol, method, host, path, status, content-type, size, and duration.

In WireGuard mode, an empty capture displays the generated client profile as a QR code. Scan it from the WireGuard mobile app; the table replaces it when the first event arrives.

Key bindings

KeyAction
j / k / / Navigate requests
EnterOpen detail panel; press again to focus it for scrolling
j / k (focused)Scroll detail content
TabSwitch between Request and Response (or Frames) tabs
/Enter filter mode
EscClose detail panel or clear filter
g / GJump to first / last request
rReplay selected request
cClear all captured requests
?Show keybinding help
q / Ctrl+CQuit

The detail panel shows headers plus decoded content-aware body views and a visible truncation marker when only a prefix was captured. JSON/XML/HTML/forms/multipart are formatted, CSS/JavaScript and structured values are highlighted, and validated raster formats render inline in the web UI. Protobuf wire fields and MessagePack values open as editable JSON; other binary request bodies open as hexadecimal bytes so invalid UTF-8 is never silently replaced. For WebSocket connections the Frames tab lists every captured frame with its direction ( client→server, server→client), opcode, size, and payload preview. Raw TCP, DNS, and fixed-target/WireGuard UDP exchanges also appear as inspectable rows.

Filtering

Press / to enter filter mode. Plain text searches across the flow. Use column:value to scope a term:

SyntaxMatches
time:14:rows captured after 14:00
proto:httpsrows using HTTPS or WSS
method:POSTrows whose method contains POST
host:githubrows whose host contains github
path:/apirows whose path contains /api
status:404rows whose status contains 404
type:jsonrows whose content-type contains json
size:1.5rows whose formatted size contains 1.5
duration:slowrows whose formatted duration contains slow
body:errorrequest or response body contains error
header:x-tracerequest or response header contains x-trace

Column names are case-insensitive. Combine terms with &, |, !, parentheses, or implicit AND. Press Enter to apply, Esc to cancel.

Terminal

proxelar -i terminal

Prints each request/response as a colored line to stdout. Useful for quick inspection or when piping output to other tools.

In WireGuard mode, terminal output begins with the client-profile QR code and configuration path before printing captured events.

Output includes timestamp, HTTP method (color-coded), URL, status code, and response size.

Pass --quiet (-q) to suppress the per-request lines; errors still go to stderr. This is useful with a Lua script that produces its own output via print():

proxelar -i terminal -q --script log_traffic.lua

Web GUI

proxelar -i gui

Opens a web interface at http://127.0.0.1:8081 (configurable with --gui-port). Built with axum and WebSocket for real-time streaming.

Features:

  • Interactive request table with live updates — nine columns: Time, Proto, Method, Host, Path, Status, Type, Size, Duration
  • WebSocket inspection — connections appear as live/closed rows; click to browse frames
  • Unified column:value search bar — same syntax as the TUI filter (e.g. status:404, type:json, proto:https)
  • Click a row to view full request/response detail
  • Intercept mode — pause requests, edit method/URI/headers/body, then forward or drop
  • Decoded and content-aware request/response views with truncation metadata
  • Lossless text/hex request-body editing and raw TCP/DNS/UDP detail views
  • Authenticated WireGuard client QR shown until the first captured event
  • Light and dark mode (follows system preference)

To make the web GUI accessible from other machines:

proxelar -i gui -b 0.0.0.0

The current web GUI is designed for local use. Proxelar opens a login URL whose token is carried in the URL fragment, exchanges it for an HttpOnly, SameSite=Strict browser-session cookie, and immediately removes the fragment from browser history. The token is never embedded in downloadable assets. REST automation uses a separate bearer token. WebSocket connections additionally validate browser origin/host consistency. There is no TLS or multi-user authorization, so remote browser access should use an authenticated TLS tunnel.

Headless API

proxelar -i api --api-token "$PROXELAR_TOKEN"

This serves the same bearer-token REST API without opening a browser. See Rules and headless API for endpoints and examples.

Session format

The native Proxelar session is JSON with a mandatory numeric version. Version 1 has this top-level shape:

{
  "version": 1,
  "created_at": 1784450000000,
  "flows": [],
  "websockets": [],
  "tcp_streams": [],
  "dns_exchanges": [],
  "udp_exchanges": []
}
  • created_at and message/frame timestamps are Unix epoch milliseconds.
  • flows contain stable IDs and complete request/response snapshots.
  • request and response bodies include body_metadata.truncated and body_metadata.total_seen so a captured prefix is never presented as complete.
  • websockets contain handshake snapshots, ordered frames, direction/opcode, and closure state.
  • tcp_streams contain target, opening time, ordered directional chunks, and closure state.
  • dns_exchanges contain the query name/type, parsed IP answers, override state, and completion state.
  • udp_exchanges contain the client and fixed target addresses, lossless request/response bytes, response-received state, and capture-limit flags.
  • duplicate HTTP header values are preserved.

Readers reject versions newer than the implementation supports. Additive collection fields use empty defaults so version-1 readers remain tolerant of data written before those collections existed. Any incompatible schema change must increment the version and provide an explicit migration or a clear rejection.

The format prioritizes fidelity and debuggability over compactness. It is not encrypted and native saves are not redacted. Use filesystem permissions appropriate for secrets-bearing traffic.

Threat model

Proxelar is a local debugging proxy for a single trusted operator. It is not a hardened shared interception service.

Assets and trust boundaries

The highest-value assets are the root CA private key, captured authorization/cookie data, API token, upstream proxy credentials, and any code loaded by Lua. Traffic crosses client → Proxelar → upstream boundaries; the GUI/API and exported files create additional local boundaries.

Local CA

The CA private key can mint certificates trusted by any client that installs the root. Keep ~/.proxelar private, never share proxelar-ca.key, and remove the root from client trust stores when Proxelar is no longer used. Each generated leaf certificate has a distinct private key rather than reusing the CA key.

Certificate-pinned clients will reject interception. Android 7+ applications trust user-installed CAs only when their network security configuration opts in. See CA trust and uninstall.

GUI and API

The API requires a random runtime bearer token unless --api-token is supplied. The GUI uses a separate random bootstrap token in the URL fragment, exchanges it for an HttpOnly, SameSite=Strict session cookie, and removes the fragment before making network requests. These credentials authorize reading captured credentials, replaying requests, and resolving intercepts. Bind to loopback by default. The server does not provide TLS, users/roles, rate limiting, or multi-tenant isolation; use an authenticated TLS tunnel for intentional remote access.

Do not place API tokens in URLs when logs or browser history are untrusted. Prefer the Authorization: Bearer header.

Scripts and rules

Lua scripts can read and change all proxied traffic. Lua is constrained by mlua’s safe standard library, but it still has access to captured secrets supplied to hooks. Native C modules are not loadable: proxyapi preserves #![forbid(unsafe_code)], and validated addon packages that declare a native-module requirement are rejected.

Scripts hot-reload after file changes. Invalid updates retain the last known-good script and log the error; hook runtime errors log and pass traffic through.

Map-local rules read files below explicitly configured directories and reject traversal. Rules can redirect, mock, or alter requests, so rule files are trusted configuration.

Captures and exports

Native sessions preserve data exactly, including secrets, and are created with owner-only permissions on Unix. HAR, curl, and raw exports redact common credentials and secret query keys unless --export-secrets is used. Redaction is a safety baseline, not a data-loss-prevention system; application-specific secrets in bodies or custom headers may remain.

Release artifacts

Release automation produces SHA-256 checksums, an SPDX SBOM, and GitHub artifact provenance attestations. Consumers should verify the artifact they install and still apply normal host/package-manager controls.

Known limitations

Proxelar is usable today for local traffic inspection, scripting, intercept, replay, and WebSocket inspection. These are the main gaps to understand before choosing it for a workflow.

Sessions and export fidelity

Proxelar can save/reload its versioned native session format, import/export HAR, emit curl commands, and write raw HTTP pairs. HAR cannot represent every Proxelar concept: raw TCP chunks, live intercept state, and some WebSocket metadata remain available only in the native session. Exports redact common credentials by default; native session saves preserve captured data exactly.

Body decoding and editing

Bodies can be capped with --body-capture-limit; the UI records captured and total byte counts and marks truncation. Views decode gzip, br, zstd, deflate, declared charsets, formatted JSON/XML/HTML/forms/multipart, highlighted CSS/JavaScript, safe raster images, and bounded binary formats. Protobuf wire fields and MessagePack values are rendered as structured JSON when valid. Multipart parts are separated for inspection, not presented as a structured part editor.

When an intercepted body changes, Proxelar removes stale transfer/content encodings and recalculates Content-Length. Invalid UTF-8 request bodies use a lossless hex editor in the TUI and web GUI. The Protobuf editor preserves and edits field numbers, wire types, integer values, UTF-8 values, and base64 byte values without a schema. Semantic field names and uncommon deprecated group wire types still require an external Lua decoder/schema.

Capture modes

Proxelar supports forward, reverse, WireGuard, SOCKS5, DNS, and fixed-target UDP modes plus upstream HTTP CONNECT/SOCKS5 chaining. WireGuard capture uses a userspace TCP/IP stack and currently generates one client identity per CA directory. Proxelar does not install firewall rules or modify system proxy settings.

Unknown TCP streams can be observed as directional chunks, but there is no protocol-aware binary editor.

HTTP versions

HTTP/2 client connections are accepted, but intercepted requests are deliberately normalized and forwarded upstream as HTTP/1.1. HTTP/3/QUIC interception is not supported.

HTTPS and mobile apps

HTTPS interception requires trusting the Proxelar CA. Certificate-pinned clients will reject the generated certificates. Android 7+ apps trust user-installed CAs only if the app explicitly opts in.

Remote web GUI

The web GUI and REST API are designed for one trusted local operator. Both require a runtime bearer token, but they do not provide user accounts, TLS termination, rate limits, or multi-tenant isolation. Bind to loopback by default. If remote access is necessary, put it behind an authenticated TLS tunnel and protect the token as a credential.

Security-suite features

Proxelar is not a scanner, crawler, collaborative testing platform, or vulnerability management tool. For those workflows, tools such as Burp Suite, Caido, or mitmproxy may be a better fit.

Comparison with other tools

This page is intentionally practical, not promotional. Proxelar overlaps with several proxy tools, but it is not the best choice for every workflow.

Summary

Use Proxelar when you want a local, scriptable, Rust-native traffic workbench with a TUI, web GUI, Lua hooks, request intercept, replay, and WebSocket frame inspection.

Choose another tool when you need end-to-end HTTP/2/HTTP/3 interception, a large pre-existing addon inventory, polished desktop UX, or professional security testing workflows.

mitmproxy

mitmproxy is the category default for many developers and security testers. It has mature HTTP tooling, a large addon ecosystem, strong flow persistence/export workflows, local capture modes, and broad documentation.

Proxelar is smaller. Its strengths are a Rust-native implementation, one CLI with terminal/TUI/web/API interfaces, portable redacted exports, Lua/rule automation, and integrity-checked addon packages with a local catalog. It is not yet a mitmproxy replacement for protocol depth or the size of mitmproxy’s community addon inventory.

Choose mitmproxy if you need the most mature general-purpose MITM proxy today. Choose Proxelar if you value a compact Rust-native tool with Lua transforms and are comfortable with a younger feature set.

proxyfor

proxyfor is the closest Rust CLI neighbor: it provides forward/reverse proxy modes, TUI/WebUI, filtering, CA install help, export formats, and portable binaries.

Proxelar emphasizes interactive intercept/edit, replay, redacted native/HAR/curl/raw exports, Lua request/response/WebSocket hooks, declarative rules, and an embeddable proxyapi core.

Choose proxyfor if its simpler capture workflow and interface fit are the main requirement. Choose Proxelar if traffic transformation, automation, portable sessions, or library embedding are central.

Burp Suite and Caido

Burp Suite and Caido are security testing platforms. They are built for manual web security testing, scanning, collaboration, history management, and security-oriented workflows.

Proxelar is not a security suite. It can help inspect and modify traffic, but it does not provide scanners, project collaboration, vulnerability workflows, or the same depth of manual testing tools.

Choose Burp or Caido for professional web security testing. Choose Proxelar for local development debugging and scriptable traffic transforms.

Charles, Proxyman, and HTTP Toolkit

These tools focus on polished desktop inspection workflows. They are often easier for GUI-first app debugging, especially when users want a desktop product rather than a terminal tool.

Proxelar is CLI-first and open source. Its interface is practical rather than desktop-polished, and its strongest workflows are scriptability, terminal use, and local proxy automation.

Choose a desktop proxy when UI polish and app onboarding matter most. Choose Proxelar when you want a terminal-friendly tool you can script and run in development environments.

Architecture

Proxelar is a Rust workspace with a strict dependency direction:

proxelar-cli  →  proxyapi  →  proxyapi_models
interfaces      engine       pure data

proxyapi_models owns serializable request, response, WebSocket, TCP, DNS, UDP, and session types. It must stay free of async and network behavior. proxyapi owns listeners, TLS, handlers, capture, filtering, content views, sessions/export, rules, and Lua hooks. proxelar-cli owns argument parsing and the terminal, TUI, web, API, and browser-launch experiences.

Runtime flow

  1. A mode-specific listener accepts TCP or UDP traffic.
  2. HTTP/TLS/SOCKS/DNS/UDP routing sends traffic to the relevant handler; unknown TCP can use observed tunneling.
  3. Requests pass through rules, Lua hooks, optional interactive intercept, normalization, and the shared outbound client.
  4. Responses pass through Lua/intercept processing and capture.
  5. ProxyEvent values fan out once to the selected interface and SessionRecorder.
  6. The recorder backs the REST API and clean-shutdown exporters.

The upstream client is shared by forward, reverse, replay, and chaining paths. Preserve its normalization invariants: remove Host, join duplicate Cookie fields with ; , strip hop-by-hop metadata, and pin upstream HTTP/1.1.

Extension points

  • HttpHandler provides library-level request/response interception.
  • RouteRules provides deterministic configuration without code.
  • Lua provides hot-reloaded request, response, and WebSocket frame hooks.
  • ProxyEvent is the stable internal observation stream used by interfaces and persistence.

Change checklist

Keep #![forbid(unsafe_code)]/the narrowly audited Lua exception intact, preserve the crate dependency direction, and make script errors log and pass through. Add socket-level integration tests for proxy behavior and serialization tests for model changes. Run the full commands in the repository’s AGENTS.md and CONTRIBUTING.md before submitting.