Transparent TLS identity proxy for local automation, security testing, and network-fingerprint research.
Doppel runs as a local HTTP CONNECT and SOCKS5 proxy. Your application connects to Doppel instead of connecting directly to the public internet. Doppel terminates the client-side TLS connection with a locally generated certificate authority, then opens a fresh upstream TLS connection using a browser-like fingerprint selected from a profile.
The result is a coherent transport identity: TLS ClientHello, ALPN, User-Agent, common browser headers, and HTTP/1.1 header ordering are applied from the same profile instead of being mixed by accident.
Doppel is source-available under the PolyForm Noncommercial License 1.0.0. You may use, study, modify, and redistribute it for non-commercial purposes allowed by that license.
Commercial use is not permitted unless you receive a separate written license from the project maintainers. This includes embedding Doppel in a paid product, using it to provide a paid service, or using it inside commercial operations.
Doppel is intended for authorised work only:
- Testing WAF, bot-detection, anti-fraud, or API-gateway controls that you own or are explicitly authorised to assess.
- Reproducing client-fingerprint issues in development and staging environments.
- Security research, protocol research, and non-commercial automation against systems where you have permission.
- Building internal experiments that need consistent browser-like TLS and header behaviour.
Doppel is not a browser, does not solve JavaScript challenges, does not improve IP reputation, and does not grant permission to access any third-party system. You are responsible for complying with law, contracts, robots policies, and the terms of services you interact with.
your app / scraper / CLI / agent
|
| SOCKS5 or HTTP CONNECT
v
Doppel local proxy
|
| 1. accepts the client connection
| 2. terminates TLS with a machine-local CA
| 3. rewrites headers from the selected profile
| 4. opens upstream TLS with a uTLS ClientHello
v
target server sees the selected profile's network identity
Because Doppel decrypts and re-encrypts traffic, each client that uses it must
trust Doppel's local CA certificate. The CA is generated on your machine by
doppel init; it is not bundled with the repository or releases.
| Layer | Status |
|---|---|
| TLS fingerprint, including JA3 / JA4 shape | Supported via uTLS |
| Browser-like User-Agent and request headers | Supported |
| HTTP/1.1 header order | Supported |
| HTTP/2 connection fingerprint | Supported for SETTINGS, initial WINDOW_UPDATE and header order |
| TCP/IP stack fingerprint | Not supported; this is controlled by the host OS |
| Browser JavaScript, DOM, canvas, WebGL, cookies | Not supported |
| CAPTCHA or challenge solving | Not supported |
| IP reputation or residential egress | Not supported |
| Behavioural modelling | Not supported |
Consistency matters. A Safari TLS fingerprint with a Python User-Agent is more suspicious than a normal automation client. Use one profile as a complete identity and avoid overriding individual pieces unless you know the downstream effect.
Download prebuilt binaries from GitHub Releases, or install with Go 1.23 or newer:
go install github.com/redstone-md/Doppel/cmd/doppel@latestRelease archives include the binary, README, changelog, and license. Build from source:
git clone https://github.com/redstone-md/Doppel.git
cd Doppel
go build -o doppel ./cmd/doppelFor reproducible local builds, the Makefile wraps the common commands:
make build
make checkGenerate a local CA and follow the printed trust-store instructions:
doppel initStart the proxy with a built-in profile:
doppel run --profile safari-ios-iphone15Point a client at Doppel:
export HTTPS_PROXY=socks5://127.0.0.1:8080
curl https://example.comOn Windows, PowerShell syntax is:
$env:HTTPS_PROXY = "socks5://127.0.0.1:8080"
curl.exe https://example.comVerify the upstream fingerprint path without running the proxy:
doppel verify --profile safari-ios-iphone15The verify command fetches a fingerprint-reporting endpoint and prints what
the server observed:
profile : safari-ios-iphone15
url : https://get.ja3.zone/
status : 200 OK
protocol: HTTP/2.0
---
{ ... fingerprint report ... }
| Command | Purpose |
|---|---|
doppel init |
Generate or reuse the local CA and print setup guidance |
doppel run |
Start the local proxy |
doppel launch |
Start Doppel and run one application through it |
doppel profiles |
List built-in and user-supplied identity profiles |
doppel ca |
Show or export the local CA certificate |
doppel verify |
Check the selected profile against a remote endpoint |
doppel version |
Print the CLI version |
Run doppel <command> -h for command-specific flags.
Useful flags:
| Flag | Commands | Purpose |
|---|---|---|
--profile <name> |
run, launch, verify |
Select the identity profile |
--addr <host:port> |
init, run, launch |
Change the proxy address; default is 127.0.0.1:8080 |
--data <path> |
init, run, launch, profiles, ca, verify |
Use a custom data directory |
--force |
init |
Regenerate the local CA |
--export <path> |
ca |
Write the CA certificate to a file |
--insecure |
run, launch |
Skip upstream certificate verification for debugging only |
--upstream-proxy <url> |
run, launch, verify |
Route Doppel egress through a SOCKS5 proxy |
-v |
run, launch |
Enable debug logging |
--electron |
launch |
Add Chromium/Electron proxy switches to the child process |
A profile is a complete network identity: TLS ClientHello template, ALPN list, User-Agent, browser headers, header order, HTTP/2 SETTINGS, initial WINDOW_UPDATE and pseudo-header ordering.
| Name | Device identity |
|---|---|
chrome-win11 |
Chrome 131 on Windows 11 x64 |
chrome-win10 |
Chrome 131 on Windows 10 x64 |
edge-win11 |
Microsoft Edge 131 on Windows 11 x64 |
edge-win10 |
Microsoft Edge 131 on Windows 10 x64 |
firefox-win11 |
Firefox 133 on Windows 11 x64 |
firefox-win10 |
Firefox 133 on Windows 10 x64 |
chrome-macos-sonoma |
Chrome 131 on macOS Sonoma |
safari-macos-sonoma |
Safari 17.4 on macOS Sonoma |
safari-macos-ventura |
Safari 17.4 on macOS Ventura |
chrome-android-pixel8 |
Chrome 131 on Android 14, Pixel 8 |
chrome-android-galaxy-s24 |
Chrome 131 on Android 14, Galaxy S24 |
safari-ios-iphone15 |
Mobile Safari on iPhone 15 Pro, iOS 17.4 |
safari-ios-iphone14 |
Mobile Safari on iPhone 14 Pro, iOS 17.4 |
Older names such as chrome-windows, firefox-windows, chrome-android,
iphone15-safari, and safari-macos are still accepted as aliases.
List profiles available on your machine:
doppel profilesCustom profiles are JSON files stored in the user profiles directory. Use
doppel ca to see the active data directory, then create a profiles
subdirectory inside it. A custom profile with the same name as a built-in one
overrides the built-in profile.
Minimal profile example:
{
"name": "my-device",
"description": "Example custom browser identity",
"client_hello": "chrome",
"user_agent": "Mozilla/5.0 ...",
"alpn": ["h2", "http/1.1"],
"headers": {
"order": ["host", "connection", "user-agent", "accept", "accept-language"],
"set": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9"
}
}
}Supported client_hello templates are chrome, firefox, safari,
safari-ios, edge, and randomized.
Use --upstream-proxy when Doppel itself should egress through another proxy:
doppel verify --upstream-proxy socks5://user:pass@proxy.example:1080The same value can be supplied with DOPPEL_UPSTREAM_PROXY for run, launch,
and verify. Provider-style socks5://host:port:user:pass URLs are accepted.
doppel launch starts a temporary Doppel proxy, launches one child process with
HTTPS proxy environment variables, and stops the proxy when the child exits:
doppel launch --profile chrome-win11 -- curl https://example.comFor Electron or Chromium-based desktop apps, add --electron. Doppel appends
Chromium proxy switches so the app does not need proxychains or LD_PRELOAD:
doppel launch --profile chrome-win11 --electron -- /path/to/electron-appBy default the Chromium switch proxies HTTPS URLs only, because Doppel expects a TLS stream after CONNECT. See Launching applications for Windows, macOS, Linux, and Electron notes.
doppel run --profile chrome-win11
curl --proxy socks5h://127.0.0.1:8080 https://example.comIf curl on Windows uses Schannel and rejects the local CA because of revocation
checks, add --ssl-no-revoke.
Install SOCKS support:
python -m pip install "requests[socks]"Then route traffic through Doppel and trust the exported CA certificate:
import requests
proxies = {
"http": "socks5h://127.0.0.1:8080",
"https": "socks5h://127.0.0.1:8080",
}
response = requests.get(
"https://example.com",
proxies=proxies,
verify="/path/to/doppel-ca.pem",
timeout=30,
)
print(response.status_code)
print(response.text[:200])Export the CA with:
doppel ca --export ./doppel-ca.pempackage main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"net/url"
"os"
"time"
)
func main() {
proxyURL, err := url.Parse("http://127.0.0.1:8080")
if err != nil {
panic(err)
}
caPEM, err := os.ReadFile("./doppel-ca.pem")
if err != nil {
panic(err)
}
roots := x509.NewCertPool()
if !roots.AppendCertsFromPEM(caPEM) {
panic("failed to load Doppel CA")
}
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{RootCAs: roots},
}
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
resp, err := client.Get("https://example.com")
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}For non-commercial products or authorised internal tooling, treat Doppel as an external local service rather than a library embedded into your process:
- Run one Doppel process per host or per isolated workload.
- Generate a separate CA per environment. Never share a development CA with CI, staging, production, or another developer.
- Keep the proxy bound to localhost unless there is a deliberate network isolation boundary in front of it.
- Prefer profile-level changes over per-request header overrides.
- Log profile name, target host, and status codes. Do not log request bodies, secrets, cookies, bearer tokens, or full URLs containing credentials.
- Rotate the CA when a machine is reassigned or when the key may have been copied.
Doppel's current Go packages live under internal/, so they are intentionally
not a public library API. The supported integration surface is the CLI and the
local proxy protocol.
The local CA is powerful. Anyone with the generated private key can sign certificates for any host trusted by clients that installed the CA.
- The CA is generated per machine and stored in the Doppel data directory.
- The private key must never be committed, uploaded, copied to another host, or shipped in a container image.
- The default proxy bind address is
127.0.0.1:8080to avoid exposing the proxy to the network by accident. - Doppel verifies upstream certificates by default. Use
--insecureonly in a controlled debugging environment. - Remove the CA from all OS and language-runtime trust stores when uninstalling Doppel.
See SECURITY.md for vulnerability reporting.
Run doppel init first, or pass the same --data directory that was used when
the CA was generated.
The client process does not trust Doppel's CA. Some runtimes ignore the OS trust
store and need explicit configuration. Export the CA with doppel ca --export
and configure the runtime or HTTP library to trust that certificate.
Doppel changes transport-level and header-level signals only. It cannot solve JavaScript challenges, repair IP reputation, execute browser APIs, or make unauthorised access acceptable.
cmd/doppel CLI entry point
internal/ca local CA generation, loading, and leaf certificate minting
internal/config runtime paths and defaults
internal/mitm TLS termination and request re-origination
internal/profile profile schema, validation, built-in profiles
internal/proxy single-port SOCKS5 and HTTP CONNECT listener
internal/upstream uTLS dialer, HTTP round tripper, response decoding
internal/wizard first-run setup guidance
The implementation keeps profile data separate from TLS mechanics: profiles are
validated as pure data, while internal/upstream maps them onto uTLS and HTTP
transport behaviour. This keeps profile authoring independent from proxy and CA
state.
- Broader HTTP/2 browser parity, including priority frames and deeper HPACK tuning.
- Version-pinned ClientHello specs for each built-in profile.
- Realistic mutation within a profile's normal browser envelope.
- Idle timeout controls for long-lived keep-alive client connections.
- Release artifacts for common operating systems.
Before opening a pull request, run:
go build ./...
go vet ./...
go test ./...
gofmt -l .Or run the combined Make target on systems with make:
make checkDo not commit machine-local data. In particular, never commit data/, CA
private keys, .env files, local agent state, request logs, cookies, or tokens.
See CHANGELOG.md.
PolyForm Noncommercial License 1.0.0. Commercial use requires a separate written license.