Back to Blog
medium SEVERITY6 min read

How gitlab.bandit.B501 happens in Python and how to fix it

The `proverbia-scraper.py` script disabled TLS certificate verification on its `requests.get()` call and silenced the resulting security warnings, exposing the scraper to man-in-the-middle attacks. The fix removes the `verify=False` flag and the warning suppression, restoring proper certificate validation while keeping the existing 30-second timeout intact.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 7, 2026•Reviewed September 7, 2026

Answer Summary

This is gitlab.bandit.B501, a Python vulnerability where the `requests` library is called with `verify=False`, disabling TLS certificate validation (CWE-295: Improper Certificate Validation). It was found in `get_soup()` in `proverbia-scraper.py`. The fix removes the `verify=False` argument (letting `requests` default to `verify=True`) and deletes the `urllib3.disable_warnings()` call that had been masking the insecure-request warning.

Vulnerability at a Glance

cweCWE-295 (Improper Certificate Validation)
fixRemoved `verify=False` and the warning suppression so `requests` validates certificates by default
riskMan-in-the-middle attackers can intercept or tamper with HTTPS traffic undetected
languagePython
root cause`requests.get()` called with `verify=False`, and warnings suppressed via `urllib3.disable_warnings()`
vulnerabilityDisabled TLS Certificate Verification (Bandit B501)

Introduction

The proverbia-scraper.py file handles a simple but common task: fetching pages from https://proverbia.net/ and parsing them with BeautifulSoup to pull out proverbs. The heavy lifting happens in a single helper function, get_soup(url), which wraps every outbound HTTP call the scraper makes. A flaw in that one function, however, meant that every single request the scraper issued was vulnerable to interception.

The problem lived on what used to be line 33:

page = requests.get(url, headers=HEADERS, timeout=30, verify=False)

That verify=False argument tells the requests library to skip TLS certificate validation entirely. Combined with a urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) call earlier in the file, the code wasn't just insecure — it was insecure silently, with no warning ever surfacing in logs or console output.

The Vulnerability Explained

Here's the vulnerable code as it existed before the fix:

import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

BASE_URL = "https://proverbia.net/"
NOW = datetime.now()

...

def get_soup(url: str) -> BeautifulSoup:
    """Download a web page and return it as a BeautifulSoup object."""
    page = requests.get(url, headers=HEADERS, timeout=30, verify=False)
    page.raise_for_status()
    return BeautifulSoup(page.content, "html.parser")

Two things are happening here, and both matter:

  1. verify=False disables TLS certificate validation. Normally, when requests connects to https://proverbia.net/, it checks the server's certificate against a trusted certificate authority chain and verifies the hostname matches. With verify=False, none of that happens — requests will happily complete the TLS handshake with any server presenting any certificate, including a self-signed one from an attacker.

  2. urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) suppresses the InsecureRequestWarning that urllib3 would normally print to stderr every time an unverified HTTPS request is made. This is the giveaway that someone added verify=False deliberately and then silenced the tool trying to warn them about it — a pattern that removes the last line of defense (a visible warning in logs) against the misconfiguration going unnoticed.

Attack scenario: Imagine this scraper running on a schedule inside a CI job, a cron container, or a shared network (a coffee shop Wi-Fi, a compromised corporate proxy, a poisoned DNS cache). An attacker positioned on the network path between the scraper and proverbia.net can perform a classic man-in-the-middle attack:

  • The attacker intercepts the DNS resolution or ARP-spoofs the local network so traffic to proverbia.net routes through their machine.
  • They present their own TLS certificate — self-signed, expired, or issued for a completely different domain.
  • Because get_soup() calls requests.get(url, ..., verify=False), requests accepts this bogus certificate without complaint.
  • The attacker now sits inside the encrypted tunnel, able to read every byte of the response (page.content) before it's handed to BeautifulSoup, and — more dangerously — can rewrite the HTML the scraper parses.

Since get_soup()'s return value drives whatever logic consumes the parsed proverbs (storage, display, further processing), an attacker who controls the response content controls what the scraper "sees." Depending on how downstream code trusts the parsed data, this could range from serving fake/misleading content to injecting malicious payloads if the scraped text is ever rendered, executed, or stored without further sanitization.

The Fix

The fix is minimal but precise — it removes the insecure argument and the warning suppression that was hiding it:

Before:

import requests
from random import choice
from datetime import datetime
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

BASE_URL = "https://proverbia.net/"
NOW = datetime.now()

def get_soup(url: str) -> BeautifulSoup:
    """Download a web page and return it as a BeautifulSoup object."""
    page = requests.get(url, headers=HEADERS, timeout=30, verify=False)
    page.raise_for_status()
    return BeautifulSoup(page.content, "html.parser")

After:

import requests
from random import choice
from datetime import datetime
BASE_URL = "https://proverbia.net/"
NOW = datetime.now()

def get_soup(url: str) -> BeautifulSoup:
    """Download a web page and return it as a BeautifulSoup object."""
    page = requests.get(url, headers=HEADERS, timeout=30)
    page.raise_for_status()
    return BeautifulSoup(page.content, "html.parser")

Two coordinated changes made this work:

  1. verify=False was dropped entirely from the requests.get() call in get_soup(). Since requests defaults to verify=True, simply omitting the argument restores full certificate chain and hostname validation. requests will now reject any connection to proverbia.net that doesn't present a valid, trusted certificate for that hostname.

  2. The urllib3 import and disable_warnings() call were deleted. Once certificate verification is back on, there's no InsecureRequestWarning to suppress — but removing this code also eliminates the risk that a future contributor copies the verify=False pattern elsewhere in the file without noticing the warning had been silenced project-wide.

Notably, the fix preserved timeout=30, which was already correctly set — this wasn't a timeout issue, it was purely a certificate-validation issue. The PR is scoped to exactly one file and one function, so no behavioral change occurs for legitimate traffic: valid HTTPS connections to proverbia.net continue to work exactly as before, while spoofed or attacker-controlled certificates are now correctly rejected.

Key Takeaways

  • The get_soup() function in proverbia-scraper.py was making every HTTPS request to proverbia.net without validating the server's certificate — a single verify=False argument undermined TLS for the entire scraper.
  • The accompanying urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) call was actively hiding the security warning that would have flagged this misconfiguration.
  • The fix required no new logic — deleting verify=False and the warning suppression was sufficient, since requests defaults to secure behavior (verify=True).
  • The existing timeout=30 was correctly preserved, showing that not every part of a flagged pattern needs to change — only the actual insecure argument did.
  • Any future requests calls added to this scraper should be checked for the same pattern before merging.

How Orbis AppSec Detected This

  • Source: The url parameter passed into get_soup(url), ultimately derived from BASE_URL and scraper-constructed page URLs targeting https://proverbia.net/.
  • Sink: requests.get(url, headers=HEADERS, timeout=30, verify=False) in proverbia-scraper.py:33.
  • Missing control: TLS certificate validation was explicitly disabled via verify=False, and the resulting InsecureRequestWarning was suppressed via urllib3.disable_warnings(), removing both the protection and the visible warning of its absence.
  • CWE: CWE-295 — Improper Certificate Validation.
  • Fix: Removed verify=False from the requests.get() call and deleted the urllib3.disable_warnings() call, restoring default certificate validation for all outbound requests.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

A single argument — verify=False — was enough to strip TLS protection from every outbound request in proverbia-scraper.py, and a companion call to urllib3.disable_warnings() made sure no one would notice. The fix demonstrates that hardening doesn't always require complex logic changes: sometimes the most secure code is the code you remove. Going forward, treat verify=False and warning suppression calls as immediate red flags in code review, and let static analysis tools like Bandit and Semgrep catch these patterns automatically before they ship.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #877

Related Articles

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.

high

CVE-2026-54673: builder-util-runtime Leaks Auth Headers on Redirect

electron-updater and electron-builder rely on builder-util-runtime to fetch update manifests and artifacts over HTTP. A flaw in that shared HTTP executor allowed credential headers attached to the original update-feed request to be re-sent after a redirect, exposing them to any host the redirect pointed to. The project fixes this by upgrading builder-util-runtime to 9.7.0 and collapsing a duplicate, older copy of the package that electron-updater had pinned on its own.

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.