Flag code that turns off TLS certificate verification — before it ships.
Disabling certificate verification is the most common copy-paste "fix" for a TLS
error: verify=False, rejectUnauthorized: false, InsecureSkipVerify: true.
It makes the error go away, the tests pass, and review sees a normal HTTPS call.
But the client now accepts any certificate from anyone: HTTPS has quietly
become unauthenticated transport, and a machine on the network path can present
its own certificate, decrypt the traffic, and re-encrypt it onward — a silent
man-in-the-middle. It's a real, well-documented bug class
(CWE-295, Improper Certificate Validation),
and nothing fails when you introduce it.
trustfell is a zero-config, cross-language static gate that scans your code for
disabled certificate verification and fails the build.
$ trustfell .
● 1 disabled TLS certificate verification:
api/client.py:14:34 TLS certificate verification disabled (verify=False).
↳ Remove verify=False; pass a CA bundle path (verify='/path/ca.pem') or fix the real certificate.
[TF001 · CWE-295]
1 blocker · 0 warnings
Exit code 1 when it finds one, so it drops straight into pre-commit or CI.
The detection is deliberately simple and language-agnostic. trustfell scans
each source line for a curated set of fragments that disable certificate or
hostname verification, tuned so they match only the disabling form. Every
pattern is boundary-aware: verify=False fires, but verify=True and a custom
CA path like verify='/etc/ssl/certs/ca-bundle.crt' never do; InsecureSkipVerify: true fires, InsecureSkipVerify: false does not.
No code is executed, no network calls are made, and there's nothing to configure. It's a single static binary.
go install github.com/jay-tank/trustfell@latestOr build from source: go build -o trustfell .
trustfell # scan the current directory
trustfell ./src # scan a path
trustfell --json # machine-readable output
trustfell --strict # treat warnings (TF002) as failures too- run: go run github.com/jay-tank/trustfell@latest ./Exit codes: 0 clean · 1 a disable (TF001), or any finding under --strict ·
2 usage error.
| Rule | Severity | What |
|---|---|---|
| TF001 | blocker | An explicit disable of TLS certificate / hostname verification (CWE-295) |
| TF002 | warning | A related trust-weakening pattern that isn't a hard disable (e.g. suppressing urllib3's InsecureRequestWarning) |
| Language / tool | Disable (flagged) | Fix instead |
|---|---|---|
| Python (requests/httpx) | verify=False |
pass a CA bundle path, or fix the cert |
| Python (ssl) | ssl._create_unverified_context(), ssl.CERT_NONE, check_hostname = False |
keep create_default_context() defaults |
| Python (aiohttp) | ssl=False, verify_ssl=False |
pass a real ssl.SSLContext |
| Node (JS/TS) | rejectUnauthorized: false, NODE_TLS_REJECT_UNAUTHORIZED=0 |
set the ca option to your bundle |
| Go | InsecureSkipVerify: true |
set RootCAs to a real CA pool |
| Java | NoopHostnameVerifier, ALLOW_ALL_HOSTNAME_VERIFIER, a HostnameVerifier returning true |
keep the platform-default verifier |
| C#/.NET | ServerCertificateValidationCallback => true, DangerousAcceptAnyServerCertificateValidator |
validate the chain / pin the cert |
| curl / libcurl | -k / --insecure, CURLOPT_SSL_VERIFYPEER = 0 |
pass --cacert / set CURLOPT_CAINFO |
The honest fix in every case is the same: trust the right CA (or pin the certificate), don't disable the check.
# before — trustfell: TF001
requests.get("https://internal.example", verify=False)
# after — verification stays on, internal CA trusted explicitly
requests.get("https://internal.example", verify="/etc/ssl/certs/internal-ca.pem")For the rare legitimate case (a pinned-cert client, an internal test harness),
add a trustfell:ignore comment on the line:
requests.get(url, verify=False) # trustfell:ignore reviewed: pinned internallyOr skip whole paths with a .trustfellignore file in the scanned root (one
path-substring per line, # for comments).
trustfell answers one question well — "is TLS certificate verification being disabled here?" — across languages, as a fast static gate. Be honest about what that means:
- It is a line-based heuristic, not data-flow. A disable split across many
lines, or hidden behind a custom wrapper function, may be missed. (A multi-line
Go
tls.Config{ InsecureSkipVerify: true }is still caught on theInsecureSkipVerify: trueline.) - It matches specific fragments only, so it will not invent findings for a
truthy
verify=value or a re-enabling line. - It does not prove a connection is otherwise secure; it catches the single most common shape of the bug.
MIT © Jay Tank