Affected Versions
| Affected | upload-release-asset.py (all versions prior to fix) |
| Fixed in | unknown (first-party code fix) |
| Ecosystem | pypi |
| CVE / GHSA | not assigned |
| CWE | unknown |
The Vulnerability Explained
The upload-release-asset.py script automates GitHub release asset management through four HTTPS requests. Each call to the Python requests library included verify=False:
# Vulnerable pattern - repeated four times
r = requests.get(api, headers=h, timeout=60, verify=False)
r = requests.get(assets_url, headers=h, timeout=60, verify=False)
dr = requests.delete("...", headers=h, timeout=60, verify=False)
up = requests.post(url, headers=dict(h, **{"Content-Type": "application/octet-stream"}),
data=f, timeout=900, verify=False)
The verify=False parameter disables TLS certificate validation entirely. When Python Requests encounters this flag, it accepts any certificate presented by the server—even invalid, expired, or attacker-controlled ones—without raising SSLError.
This creates a certificate validation bypass (CWE-295) with two distinct attack surfaces:
-
Token theft: The
headers=hdictionary contains anAuthorization: token <oauth_token>header. An attacker intercepting the connection captures this bearer token, gaining the script's full GitHub permissions (typicallyreposcope for release management). -
Asset substitution: The 15-minute upload window (
timeout=900) sends binary data touploads.github.com. An attacker can terminate the TLS connection, serve a malicious file in place of the legitimate release asset, and forward the (now compromised) token to complete the upload—poisoning the release with attacker-controlled code.
The attack requires network-level access: ARP spoofing on a corporate LAN, a compromised VPN concentrator, DNS hijacking, or simply a rogue Wi-Fi access point at a coffee shop where a developer runs the release process.
The Fix
The remediation removes verify=False from all four request sites, restoring Python Requests' default behavior of validating server certificates against system trust stores:
# Fixed: certificate validation now enforced
r = requests.get(api, headers=h, timeout=60)
r = requests.get(assets_url, headers=h, timeout=60)
dr = requests.delete("...", headers=h, timeout=60)
up = requests.post(url, headers=dict(h, **{"Content-Type": "application/octet-stream"}),
data=f, timeout=900)
This single change eliminates the vulnerability because:
requestsnow validates thatapi.github.comanduploads.github.compresent certificates chained to a trusted root CA- Certificate mismatches, expiration, or revocation trigger
requests.exceptions.SSLError, halting execution before token transmission - No code changes are needed elsewhere—the same
headersandtimeoutparameters function identically
Key Takeaways
-
verify=Falseis never appropriate for production API clients: The parameter exists only for testing against self-signed certificates in controlled environments. Production code using it against public APIs like GitHub's introduces immediate, exploitable MITM risk. -
Release scripts are supply-chain critical paths: Compromised release assets propagate automatically to users via package managers and update mechanisms. The integrity of these scripts matters as much as the application itself.
-
OAuth tokens in automation require transport-layer protection: Unlike interactive browser flows with PKCE or short-lived codes, bearer tokens in scripts are long-lived and high-privilege. Their exposure grants persistent repository access.
-
Python Requests' defaults are safe; overrides must be justified: The library validates certificates by default. Any override—
verify=False,cert_reqs='CERT_NONE'inurllib3, or customSSLContext—demands explicit risk assessment and documentation.
How Orbis AppSec Detected This
Source: The repo and tag parameters constructing GitHub API URLs (though static in this script, the pattern generalizes to dynamic URL building)
Sink: requests.get(), requests.delete(), and requests.post() invoked with verify=False
Missing control: Certificate validation was explicitly disabled where the default trusted validation should apply; no REQUESTS_CA_BUNDLE override or pinned certificate was present to mitigate
CWE: unknown (vulnerability class: Improper Certificate Validation, CWE-295)
Fix: Remove verify=False from all four HTTP request calls to restore TLS certificate validation against system trust stores
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
The verify=False pattern in upload-release-asset.py exemplifies how a single parameter—often added during development to bypass certificate errors—can persist into production with critical consequences. The fix demonstrates that secure defaults, when not overridden, provide adequate protection against network-level attackers. For teams maintaining release automation, audit all HTTP client configurations: any explicit verify=False, ssl=False, or insecure flag warrants immediate review and removal.