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

Prevention & Best Practices

For Python Developers Using requests

  1. Always validate external URLs before following them:
    ```python
    from urllib.parse import urlparse

def is_safe_url(url, allowed_hosts=None):
parsed = urlparse(url)
if parsed.scheme != 'https':
return False
if allowed_hosts and parsed.netloc not in allowed_hosts:
return False
return True
```

  1. Use session objects with controlled redirects:
    python session = requests.Session() session.max_redirects = 5 response = session.get(url, allow_redirects=False) # Handle redirects manually

  2. Always set timeouts on network requests:
    ```python
    # Good: explicit timeout
    requests.get(url, timeout=(5, 30)) # (connect timeout, read timeout)

# Bad: no timeout (can hang forever)
requests.get(url)
```

  1. Consider using requests-toolbelt for advanced URL validation

Security Standards

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.

References

Frequently Asked Questions

What is cleartext credential transmission?

Cleartext credential transmission occurs when authentication credentials like passwords, API keys, or OAuth tokens are sent over unencrypted HTTP connections instead of HTTPS, allowing network attackers to intercept them.

How do you prevent credential exposure in Python requests?

Always validate that URLs use HTTPS before sending credentials, use `requests.get()` with explicit URL scheme validation, set `verify=True` for TLS certificate verification, and consider using session objects with base URL restrictions.

What CWE is cleartext credential transmission?

CWE-319: Cleartext Transmission of Sensitive Information covers this vulnerability class where sensitive data is transmitted without encryption.

Is using HTTPS for the initial request enough to prevent credential leaks?

No, if your code follows redirects or pagination URLs from API responses, those subsequent URLs must also be validated for HTTPS to prevent credential exposure through malicious redirects.

Can static analysis detect credential exposure over HTTP?

Yes, static analysis tools can detect patterns where `requests.get()` with authentication is called on URLs that haven't been validated for HTTPS, especially when those URLs come from external sources like API responses.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #352

Related Articles

high

How Remote Code Execution via serialize-javascript happens in Node.js and how to fix it

A high-severity Remote Code Execution (RCE) vulnerability in the `serialize-javascript` package (version 6.0.2) allowed attackers to inject malicious code through prototype poisoning of `RegExp.flags` and `Date.prototype.toISOString()`. The fix upgrades the dependency to version 7.0.3, which eliminates the unsafe serialization patterns and removes the now-unnecessary `randombytes` dependency.

high

How Denial of Service via unbounded brace expansion happens in Node.js and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-14257) in the `brace-expansion` package version 1.1.12 allowed attackers to craft malicious brace patterns that caused exponential-time complexity, leading to out-of-memory process crashes. The fix upgrades the dependency to version 1.1.16 using npm overrides to ensure the patched version is used throughout the entire dependency tree.

critical

How Insecure API Key Transmission Happens in JavaScript Browser Extensions and How to Fix It

A critical vulnerability in `utils/common.js` allowed API keys to be transmitted over unencrypted HTTP connections to remote servers, exposing them to network interception. The `buildModelApiRequest` function at line 490 constructed API requests without validating the transport protocol, enabling man-in-the-middle attacks. The fix enforces HTTPS for all remote API endpoints while preserving HTTP access for local development servers on loopback addresses.

critical

How URL Injection via Unvalidated User Input happens in Node.js and how to fix it

A critical URL injection vulnerability in the QQ info lookup feature allowed attackers to manipulate API request parameters by sending specially crafted messages. Without proper input validation, user-controlled data was directly embedded into external API URLs, potentially exposing sensitive authentication credentials (skey and pskey) to attacker-controlled servers.

high

How Denial of Service via Exponential-Time Complexity Happens in Node.js Dependencies and How to Fix It

A high-severity denial of service vulnerability (CVE-2026-14257) was discovered in the brace-expansion package within the zeroshot-oecp Docker container's dependency tree. The vulnerability allows attackers to craft malicious input patterns that trigger exponential-time processing, potentially freezing or crashing Node.js applications. This fix upgrades the nested brace-expansion dependency to version 5.0.9 using a targeted Dockerfile modification.

critical

How Insecure HTTPS Requests and Missing Timeouts Happen in Python and How to Fix Them

A critical security hardening issue was discovered in `scripts/maimai/songs.py` where HTTP requests were made without SSL certificate verification and timeout values. This combination creates a Man-in-the-Middle (MITM) attack vector that could allow adversaries to intercept sensitive data or inject malicious content. The fix adds explicit SSL verification enforcement and request timeouts to all HTTP calls.