DEV Community

CopperSunDev
CopperSunDev

Posted on • Originally published at coppersun.dev

The verify=False Pattern AI Coders Keep Shipping

Your AI assistant just silenced an SSL error. The code works now.

The SSL check it disabled is the one that confirms you're connected to the server you intended. With it off, any server — including one controlled by an attacker positioned between your client and the endpoint — can present a certificate and the client accepts it, no questions asked.

BrassCoders flags verify=False at HIGH severity via Bandit B501. This is the pattern, why AI assistants generate it, and what the actual fix looks like.

What verify=False Actually Does

BrassCoders flags verify=False as a HIGH-severity finding because disabling certificate validation removes the identity check from a TLS connection, not the encryption. The connection is still encrypted — traffic between your client and the responding server is still protected in transit. What's gone is the guarantee that the responding server is the one you intended to reach.

TLS certificate validation works in two steps: first, the server presents a certificate signed by a trusted Certificate Authority (CA); second, the client checks that the certificate's domain matches the domain it connected to. verify=False skips both steps. A server presenting a fraudulent certificate, or a certificate for a different domain entirely, passes silently.

The Python requests documentation's security section describes the consequence directly: "verify=False means the server SSL certificate will not be verified, leaving you vulnerable to man-in-the-middle attacks."

Why AI Assistants Generate It

SSL certificate errors are common during development. Self-signed certificates, corporate proxies that intercept HTTPS traffic, and misconfigured local environments all produce ssl.SSLCertVerificationError or similar. A developer who shares the error with an AI assistant gets back the one change that makes the error stop: verify=False.

The assistant doesn't know whether this is a throwaway script or a production service call. It doesn't know whether the endpoint handles credentials or public data. It produces the shortest fix for the visible problem. The error disappears. The code ships.

This pattern appears in AI-generated code far more than in human-written code for a specific reason: humans who encounter SSL errors usually fix the certificate or research the CA bundle, because they understand what verify=False means. AI assistants default to the change that makes the error stop, and verify=False is the shortest path to that.

The Attack Path

The OWASP Transport Layer Security Cheat Sheet covers the man-in-the-middle scenario that disabled certificate validation enables. The attack path for a service call with verify=False is:

  1. An attacker positions themselves between the Python client and the intended endpoint — on the same network, via ARP spoofing, via DNS poisoning, or via a compromised proxy.
  2. The attacker presents their own TLS certificate for the connection.
  3. The client, with verify=False, accepts the certificate without checking who issued it or whether the domain matches.
  4. The attacker decrypts, reads, potentially modifies, and re-encrypts the traffic before forwarding it.

For a service call that sends an API key, a session token, or user credentials in headers or the request body, the attacker now has those credentials. The connection looked encrypted to the application. Nothing in the logs indicated a problem.

The scenario isn't theoretical. Corporate networks, cloud provider networks, and shared hosting environments all create conditions where a MITM attack is feasible. verify=False makes the attack trivial on any of them.

What BrassCoders Flags

BrassCoders catches verify=False via Bandit's B501 rule, which covers requests.get, requests.post, requests.put, requests.delete, requests.patch, requests.head, and session-based calls like session.get and session.request. The finding fires wherever verify=False appears, regardless of whether the call looks like a development utility or a production service integration.

# These all fire B501
requests.get(api_url, verify=False)
requests.post(endpoint, json=payload, verify=False)
session = requests.Session()
session.verify = False
Enter fullscreen mode Exit fullscreen mode

The finding is HIGH severity. Claude Code reads the .brass/ai_instructions.yaml findings file and applies context: is the endpoint external or internal, does the call path handle credentials, is this a CLI utility or a web service? The scanner names the risk at every call site; the AI triage layer identifies which instances are actual threats and which may be acceptable in their specific context.

The Actual Fix

verify=False is not a fix for an SSL error — it's a bypass. The certificate error has a cause, and the cause is fixable without disabling the check.

For public HTTPS endpoints: The most common cause of SSL errors against public APIs is an outdated CA bundle. Update it:

pip install --upgrade certifi
Enter fullscreen mode Exit fullscreen mode

The requests library uses certifi's CA bundle by default. An outdated certifi won't include recently added root certificates.

For self-signed certificates in internal environments: Pass the CA certificate file explicitly rather than disabling verification entirely:

# Verify against a specific CA certificate instead of disabling the check
requests.get(internal_url, verify='/path/to/internal-ca.crt')
Enter fullscreen mode Exit fullscreen mode

This preserves identity verification — the client checks that the certificate is signed by the internal CA — without accepting certificates from arbitrary issuers.

For corporate proxies that intercept HTTPS: Install the proxy's root certificate into the system trust store. Most proxies distribute their root certificate for exactly this purpose. Once installed, set the REQUESTS_CA_BUNDLE environment variable to point requests at the bundle that includes it:

export REQUESTS_CA_BUNDLE=/path/to/combined-ca-bundle.crt
Enter fullscreen mode Exit fullscreen mode

Every option above maintains the identity check. None of them require shipping verify=False.

Scanning the Codebase

BrassCoders scans every Python file in the project for B501, including files that aren't part of the main application — test utilities, one-off scripts, internal tooling, and migration helpers. A verify=False in a test helper that shares a token with a real endpoint is still a verify=False. The scan runs offline:

pip install brasscoders
brasscoders scan /path/to/your/project
Enter fullscreen mode Exit fullscreen mode

Findings appear in .brass/ai_instructions.yaml, with file path, line number, and severity. Open the file in Claude Code and ask it to triage which B501 findings are in production service call paths — those are the ones to fix first.

Top comments (0)