Script to automatically verify and fix MITM TLS distrust issues commonly afflicting corporate device users who are subject to traffic inspection via agents such as Cloudflare WARP or Netskope. ZScaler is not yet supported.
# Fix everything in one shot (no prompts, no download needed)
_fumitm_status=0; python3 <(curl -LsSf https://raw.githubusercontent.com/aberoham/fumitm/main/fumitm.py) --fix --yes || _fumitm_status=$?
[ -r "$HOME/.config/fumitm/env.sh" ] && . "$HOME/.config/fumitm/env.sh"
case $- in *i*) ;; *) (exit "$_fumitm_status");; esac
# With sudo (needed for Java keystores, DBeaver, and other system-level fixes)
_fumitm_status=0; sudo python3 <(curl -LsSf https://raw.githubusercontent.com/aberoham/fumitm/main/fumitm.py) --fix --yes --run-as-user $USER || _fumitm_status=$?
[ -r "$HOME/.config/fumitm/env.sh" ] && . "$HOME/.config/fumitm/env.sh"
case $- in *i*) ;; *) (exit "$_fumitm_status");; esacThe middle line activates the new TLS environment in your current terminal — a child process cannot modify its parent shell, so without it the fixes only apply to newly opened shells. It runs even after a partial-success run (exit code 3 — some tools fixed, some not), sourcing the env file whenever it exists, which is exactly what any new shell would do. The final line restores fumitm's exit status in scripts only: a wrapper script sees fumitm's real status as $?, and under set -e it stops there — after activation has run. Interactive shells are deliberately excluded (the case $- in *i*) guard): restoring a failure status into a terminal that has set -e enabled would terminate the session instead of returning to the prompt, so when pasted into a terminal the block always ends with status 0 and the outcome is read from fumitm's printed summary.
For more control, download the script first:
curl -LsSf https://raw.githubusercontent.com/aberoham/fumitm/main/fumitm.py -o fumitm.py
chmod +x ./fumitm.py
# Check status (no changes made)
./fumitm.py
# Apply fixes (prompts before each change)
./fumitm.py --fix
# Run with detailed debug output (useful for troubleshooting)
./fumitm.py --debug
# List supported tools + tags (for use with --tools)
./fumitm.py --list-tools
# Fix only selected tools (keys and tags are both supported)
./fumitm.py --fix --tools brew-cacerts,node
./fumitm.py --fix --tools gcp --tools db
# Explicit provider selection (default is auto-detect)
./fumitm.py --fix --provider warp
./fumitm.py --fix --provider netskope
# Aikido Endpoint Protection (supplemental TLS interception) is auto-detected
# and its root CA is added to every bundle alongside the primary provider.
# Force it on (e.g. in CI where detection signals are absent) or off:
./fumitm.py --fix --with-aikido
./fumitm.py --fix --no-aikido
# Running in a devcontainer/WSL?
# See the "VS Code Devcontainers / WSL" section below.# Download the Windows-specific script
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/aberoham/fumitm/main/fumitm_windows.py" -OutFile "fumitm_windows.py"
# Check status (no changes made)
python fumitm_windows.py
# Apply fixes to all supported tools
python fumitm_windows.py --fixWhen your organization runs a man-in-the-middle (MITM) gateway with TLS inspection enabled, the gateway intercepts and records virtually all HTTPS traffic for policy enforcement and security auditing. MITM gateways achieves this introspection by presenting their own root certificate to your TLS clients -- essentially performing sanctioned wiretapping on your TLS (aka SSL) connections.
Typically, MacOS and Windows themselves will automatically trust your MITM's certificate through system keychains. Most third-party development tools completely ignore these system certificates. Each tool maintains its own certificate bundle or looks for specific environment variables. This fragmentation creates endless annoying "certificate verify failed" errors across your toolchain whenever your MITM gateway's inspection is turned on.
One particularly annoying detail is that simply pointing tools to your organization's MITM gateway certificate by itself rarely works. You often need to append the custom MITM CA to an existing bundle of public CAs, which quickly becomes a brittle process that needs repeating for each tool.
FU MITM!
Whilst the quick temporary workaround might be to toggle your MITM gateway OFF, this is incredibly distressing to any nearby Information Security professionals who will one day need to forensically examine dodgy dependencies or MCPs that have slipped onto your laptop.
The act of toggling your MITM off also seriously hints that you have no clue what you're doing, as understanding TLS certificate-based trust is a critical concept underpinning modern vibe'n.
- Cloudflare WARP or Netskope Client should be installed and connected
warp-cliis needed for WARP flows. Netskope auto-detection uses known certificate paths or a running STAgent process (nsdiagis optional)- Python 3 (macOS/Linux, Windows/WSL)
Some environments run Aikido Endpoint Protection performing selective TLS
interception on top of the primary provider. fumitm auto-detects Aikido (via its
AikidoSecurity support directory, combined PEM, or System Keychain entry) and
adds the Aikido root CA to every bundle and keystore it manages, alongside
the primary provider's root — never replacing it, since traffic Aikido does not
intercept still presents the underlying provider's certificate. This matters for
rustls clients such as uv, which honor SSL_CERT_FILE: a bundle missing the
Aikido root fails Aikido-intercepted hosts with invalid peer certificate: UnknownIssuer. Use --with-aikido to force the supplemental root on or
--no-aikido to skip it. On a CI or no-agent image where the live detection
signals are absent, supply the root with --aikido-cert /path/to/aikido-root.pem
(it implies --with-aikido); fumitm also reuses a root persisted at
~/.aikido-ca.pem from an earlier run. Aikido support currently targets
macOS/Linux (fumitm.py); the Windows port does not yet add the Aikido root.
Newer Aikido agents ship aikido-doctor certconfig adopt <pem>, which registers
an external root and rebuilds every bundle Aikido maintains (node, npm, pip,
git, ruby, curl, nix, bazel) around it. When that binary is on PATH, --fix
runs it against the primary provider's root (tool key aikido-adopt) so the
bundles Aikido env-injects trust the primary proxy too. It needs root: non-root
runs ask for confirmation (--yes auto-confirms) and use sudo, and without a
terminal they print the command to run manually instead. Adoption is macOS-only,
since the adopted-CA record it is verified against lives under
/Library/Application Support; bundle inclusion still works everywhere.
Whether the root has been adopted is read from Aikido's own record of it,
EndpointProtection/run/adopted-cas/<sha256>.pem, so repeat runs report
already_ok without asking for sudo. Note that finding the root inside one of
Aikido's bundles proves nothing on its own — those are built from the system
trust store, so any keychain-installed root appears there without having been
adopted; that weaker check is used only for agents that keep no such record.
The existing workarounds (trust-var reclaim, curlrc override, exports kept last
in shell startup files) stay in place for hosts whose agent predates
certconfig.
warp-cli.execommand must be available- Administrator privileges may be required for some fixes
Something amiss or not quite right? Please post the full output of a run to an issue or simply submit a PR
./fumitm.py --list-tools currently reports these Linux/macOS tool keys:
aikido-adopt, brew-cacerts, node, python, gcloud, java, jenv, gradle, dbeaver, docker, wget, podman, rancher, android, colima, git, curl, aws.
- Homebrew CA Certificates (
brew-cacerts): configures Homebrew's CA bundle (covers Homebrew OpenSSL consumers) - Node.js/npm: configures
NODE_EXTRA_CA_CERTSfor Node.js and the cafile setting for npm - Python: sets the
REQUESTS_CA_BUNDLE,SSL_CERT_FILE, andCURL_CA_BUNDLEenvironment variables - gcloud: configures the
core/custom_ca_certs_filefor the Google CloudgcloudCLI - Git: configures Git to use the custom certificate bundle via
http.sslCAInfo - curl: configures
CURL_CA_BUNDLEenvironment variable for curl - AWS CLI: configures
AWS_CA_BUNDLEenvironment variable for AWS CLI (aws configure sso,aws s3, etc.) - Java/JVM: adds the provider certificate to any found Java keystore (cacerts); detects installations from
/Library/Java/JavaVirtualMachines(macOS),/usr/lib/jvm(Linux), and SDKMAN (~/.sdkman/candidates/java/, or$SDKMAN_DIRif set) - jenv: adds the provider certificate to all jenv-managed Java installations
- DBeaver: targets the bundled JRE and adds the certificate to its keystore
- wget: configures the
ca_certificatein the.wgetrcfile - Podman: installs certificate in
~/.docker/certs.d/(persistent) and Podman VM's trust store (if running) - Rancher Desktop: installs certificate in
~/.docker/certs.d/(persistent) and Rancher VM's trust store (if running) - Colima: installs certificate in
~/.docker/certs.d/(persistent, applied on start) and Colima VM's trust store (if running) - Android Emulator: helps install certificate on running Android emulators
- Gradle: rebuilds
~/.gradle/custom-cacerts(respectingGRADLE_USER_HOME) as a PKCS12 truststore seeded from the active Javacacerts, imports the current proxy CA chain, and pointsgradle.propertiesat that managed store.
- Node.js/npm: configures
NODE_EXTRA_CA_CERTSfor Node.js and the cafile setting for npm - Python: sets the
REQUESTS_CA_BUNDLE,SSL_CERT_FILE, andCURL_CA_BUNDLEenvironment variables - Google Cloud SDK (gcloud): configures the
core/custom_ca_certs_filefor the Google CloudgcloudCLI - Java/JVM: adds the provider certificate to any found Java keystore (cacerts)
- wget: configures the
ca_certificatein the.wgetrcfile - Podman: installs certificate in Podman container runtime
- Rancher Desktop: installs certificate in Rancher Desktop Kubernetes environment
- Git: configures Git to use the custom certificate bundle via
http.sslCAInfo - Windows Certificate Store: installs the certificate in the Windows system certificate store
The Windows version (fumitm_windows.py) includes Windows-specific functionality:
- Uses Windows Registry to locate certificates and configuration
- Handles Windows paths and file permissions
- Works with Windows-specific certificate stores
- Supports PowerShell environment variable management
Fumitm should auto-detect VS Code devcontainers and WSL environments where the provider CLI is only available on the underlying host. Within these environments, fumitm will guide you where to obtain your MITM cert and will skip slow verification tests.
If the cert cannot be pulled automatically from inside the container, use one of these flows:
# Use an existing cert file from your host/dev environment
./fumitm.py --fix --cert-file ./company-ca.pem --skip-verify
# Paste cert content manually
./fumitm.py --fix --manual-cert --skip-verifyfumitm supports JAMF Pro, Ansible, Puppet, and similar headless orchestration tools with --headless, --run-as-user, structured logging, and machine-parseable exit codes. See README-automation.md for full details including wrapper scripts, Ansible playbooks, and Puppet manifests.
Quick example (JAMF):
/usr/bin/python3 /path/to/fumitm.py --fix --yes --headless --provider netskope --run-as-user "$3" --log-dir /var/log/fumitmFor a pre-login/loginwindow-safe wrapper with $3 guards, see README-automation.md.
If you encounter issues:
- Ensure your MITM is connected:
warp-cli status(WARP) or confirm Netskope Client/STAgent is running (nsdiag -fis optional) - Run with debug output:
./fumitm.py --debug(Linux/macOS) orpython fumitm_windows.py --debug(Windows) - Check that Python 3 is properly installed and in your PATH
- Verify you have appropriate permissions for the tools you're trying to fix