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 Quadratic CPU Consumption Vulnerabilities Happen in JavaScript YAML Parsers and How to Fix Them

A high-severity denial-of-service vulnerability in js-yaml versions 3.x and 4.x allowed attackers to trigger quadratic CPU consumption through specially crafted YAML documents using the !!omap tag. This fix upgrades js-yaml from 4.1.1 to 4.3.1 and from 3.14.2 to 3.15.1, eliminating the algorithmic complexity attack vector that could freeze Node.js applications processing untrusted YAML input.

high

How javascript.lang.security.detect-child-process.detect-child-process happens in Node.js and how to fix it

A high-severity command injection vulnerability was discovered in `scripts/build.js` where `execSync` was called with string-interpolated arguments (`sourceDir` and `outputPath`) inside a shell command. By replacing `execSync` with `spawnSync` using an argument array (no shell), the fix eliminates the possibility of shell metacharacter injection while preserving identical build behavior.

high

How Command Injection happens in Node.js child_process and how to fix it

A command injection vulnerability in nix.js's Release class allowed potentially malicious input through the `arch` parameter to be executed via shell commands. The fix replaced `execSync()` with `execFileSync()`, eliminating shell interpretation and preventing command injection by passing arguments as an array instead of a concatenated string.

critical

How Sensitive Data Exposure in Error Logging happens in TypeScript/Deno and how to fix it

A critical vulnerability in Supabase Edge Functions allowed sensitive authentication errors and API credentials to leak through verbose error logging. The `cancel-subscription/index.ts` function logged full error objects to the console, potentially exposing Paddle API keys and auth tokens in deployment logs. The fix sanitizes all error messages to log only safe error text while preserving debugging capability.

critical

How HTTP Header Injection Happens in Go and How to Fix It

A critical vulnerability in the file upload handler allowed attackers to inject CRLF sequences into HTTP response headers through crafted filenames. The fix sanitizes user-supplied filenames before using them in Content-Disposition headers, preventing header injection attacks that could lead to cache poisoning, session fixation, or XSS.

high

How Path Traversal and Security Policy Bypass Happens in Node.js Dependencies and How to Fix It

A high-severity vulnerability in the fast-uri package (CVE-2026-6321) allowed attackers to bypass security policies through improper Unicode hostname canonicalization and path traversal. This issue affected the @apralabs/apra-fleet project through its dependency tree, and was resolved by upgrading fast-uri from version 3.1.0 to 4.1.2 using npm overrides.