Back to Blog
high SEVERITY5 min read

API_URL Defaults to HTTP Without HTTPS Enforcement

An API client defaults to unencrypted HTTP connections, leaving all API communications—including sensitive project data—vulnerable to interception. Although an `upgradeToHttps()` helper function existed, it was applied inconsistently across the codebase. The fix ensures HTTPS enforcement is applied uniformly before every HTTP request.

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

Answer Summary

The API client (first-party code in src/api.js) defaults to HTTP (http://localhost:3000), allowing attackers to intercept all project data transmitted in plaintext. A network attacker could observe, modify, or replay API requests without authentication. The fix applies the existing `upgradeToHttps()` helper consistently to both the `fetchWithRetry()` function and direct fetch calls. CWE-319: Cleartext Transmission of Sensitive Information.

Vulnerability at a Glance

cweCWE-319
fixApply `upgradeToHttps()` uniformly before all HTTP requests in the retry and direct-fetch code paths.
riskNetwork attackers can intercept, observe, and modify all API communications including authentication and project data.
languageJavaScript
root causeAPI_URL defaults to HTTP and HTTPS enforcement is applied inconsistently across fetch calls.
vulnerabilityCleartext Transmission of Sensitive Data

The Vulnerability Explained

The API client in this application ships with a critical but silent flaw: its default API endpoints default to HTTP, not HTTPS. This means that when the application makes requests to fetch project data, authentication tokens, or other sensitive information, that traffic travels in plaintext across the network.

Consider the default configuration: http://localhost:3000. On a production server or across any untrusted network, this is an open invitation to passive and active network attacks. An attacker positioned on the same network—whether physical Wi-Fi, a compromised ISP router, or a cloud infrastructure hypervisor—can read, modify, or replay every API request without breaking a single authentication mechanism.

The irony is that a helper function to enforce HTTPS already existed in the codebase:

// src/api.js:55-59
function upgradeToHttps(url) {
    // Helper exists but is not applied everywhere
}

However, this helper was applied inconsistently. The fetchWithRetry() function—the main retry logic for robust API calls—did not upgrade URLs before sending them. Neither did direct fetch calls in the songUrl() method. This inconsistency meant that even if one code path was protected, another remained exposed.

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code)
Ecosystem N/A
CVE / GHSA not assigned
CWE CWE-319: Cleartext Transmission of Sensitive Information

Attack Scenario

A developer deploys this application to a production cloud environment. The API_URL defaults to http://localhost:3000, or perhaps a similarly insecure scheme. A network-level attacker—for example, a compromised cloud neighbor, a rogue ISP, or an actor on a shared VPN—observes the following unencrypted request:

GET http://api.example.com/project/data?id=12345 HTTP/1.1
Authorization: Bearer eyJhbGc...

The attacker now has:
- The bearer token (replay attacks, token forgery)
- The project ID (enumeration, targeted attacks)
- The internal API schema (reverse engineering)
- A record of when the application communicates (timing attacks, inference)

None of this requires breaking authentication. The plaintext channel does the work.


The Fix

The fix is direct: apply upgradeToHttps() consistently before every fetch call. Two changes were needed:

Change 1: Upgrade URLs in fetchWithRetry()

// Before
async function fetchWithRetry(url, options = {}, retryConfig = config.retry) {
    const { maxRetries, baseDelay } = retryConfig;
    let lastError;
    // url is used as-is, HTTP not enforced
}

// After
async function fetchWithRetry(url, options = {}, retryConfig = config.retry) {
    const { maxRetries, baseDelay } = retryConfig;
    let lastError;
    url = upgradeToHttps(url);  // <-- Enforce HTTPS before retry loop
}

fetchWithRetry() is the application's main mechanism for resilient API calls. By upgrading the URL at the entry point, every request retried through this function is now protected, regardless of which retry attempt succeeds.

Change 2: Upgrade URLs in songUrl()

// Before
async songUrl(id, br) {
    const url = this.buildUrl({ server: 'netease', type: 'url', id, br });
    const response = await fetch(url, {
        method: 'GET',
        redirect: 'follow',
    });
}

// After
async songUrl(id, br) {
    const url = upgradeToHttps(this.buildUrl({ server: 'netease', type: 'url', id, br }));
    const response = await fetch(url, {
        method: 'GET',
        redirect: 'follow',
    });
}

Direct fetch calls that bypass the retry wrapper now also enforce HTTPS. This is especially important for the songUrl() method, which requests media metadata—sensitive data that should not travel in the clear.

Why Both Changes Were Necessary

The retry wrapper handles most cases, but not all. If songUrl() or other methods call fetch() directly, they bypass fetchWithRetry() entirely. The attacker could target these direct calls, leaving a security gap. By applying the upgrade in both places, the fix ensures defense-in-depth: no matter which code path executes, HTTPS is enforced.


How Orbis AppSec Detected This

Source: The API_URL and BASE_URL configuration parameters, which default to unencrypted schemes (http://localhost:3000).

Sink: Direct fetch() calls and the fetchWithRetry() function, which transmit these URLs without scheme validation.

Missing control: No automatic, mandatory upgrade of insecure schemes before the fetch() call, even though an upgradeToHttps() helper existed but was not universally applied.

CWE: CWE-319: Cleartext Transmission of Sensitive Information.

Fix: Invoke upgradeToHttps() at the entry point of fetchWithRetry() and on the result of buildUrl() in songUrl() before passing the URL to fetch().

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.


Key Takeaways

  • Inconsistent application of security helpers is a vulnerability. If an upgradeToHttps() function exists but only some code paths use it, the unprotected paths become attack surface. Security controls must be applied uniformly or enforced at a layer that applies to all code paths (e.g., at the HTTP client library level).

  • Default configurations must not expose sensitive data. Defaulting to HTTP is worse than requiring explicit HTTPS opt-in, because developers will ship the defaults without realizing the risk. Always default to the secure option.

  • Configuration is not a substitute for code-level enforcement. Even if documentation says to configure API_URL as HTTPS, the code should not allow HTTP to succeed silently. Validate and upgrade schemes in the code, not just in environment variable instructions.

  • Network attackers do not need to break authentication to steal data. Plaintext protocols expose authentication tokens, session cookies, API keys, and request contents directly. Encryption is a prerequisite for authentication, not an alternative.


Conclusion

The fix ensures that all API communications—whether retried through fetchWithRetry() or sent directly via songUrl()—are protected by HTTPS before they leave the application. By applying the existing upgradeToHttps() helper consistently, the application closes the window for network-level interception of sensitive project data and authentication credentials. This change exemplifies why security utilities must be applied uniformly across the codebase, and why defaults should never sacrifice confidentiality.

Prevention and further reading

Frequently Asked Questions

Is the `upgradeToHttps()` helper sufficient if only applied to one fetch path and not the other?

No. The `fetchWithRetry()` function and direct fetch calls in `songUrl()` both need the upgrade applied. Skipping either path leaves data vulnerable to interception on the unprotected branch.

What happens if a developer passes a custom URL that is already HTTPS to `fetchWithRetry()`?

The `upgradeToHttps()` helper is idempotent—it checks the scheme and returns HTTPS URLs unchanged, so there is no double-upgrade or side effect.

Does this fix affect local development with http://localhost:3000?

The `upgradeToHttps()` helper converts the default config to HTTPS. Development environments should use an HTTPS-capable server or a reverse proxy; continuing to use plain HTTP in production is not recommended.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #7

Related Articles

critical

Hybridauth Telegram OAuth Timing Attack in `authenticateCheckError()`

The Telegram OAuth provider in Hybridauth relied on `strcmp()` to validate HMAC-SHA256 signatures, exposing a timing side-channel that could allow attackers to forge authentication tokens character by character. The fix replaces this with PHP's timing-safe `hash_equals()` function, eliminating the information leak.

high

undici 7.29.0 TLS Bypass: BalancedPool Drops Connect Options

A critical flaw in undici's BalancedPool implementation silently discards TLS certificate validation settings when routing requests through certain connection paths. Attackers on the network could intercept HTTPS traffic without triggering validation errors. The fix preserves connect options throughout the connection lifecycle.

critical

Model Fetching Without Integrity Verification in ONNX Loading

A machine learning application was fetching ONNX model files and chunks over the network without verifying their integrity, creating an opening for model poisoning attacks. The fix adds cryptographic integrity verification at the point where downloaded chunks are reassembled and cached, ensuring models have not been modified in transit or at rest.

high

nanoid 3.3.11 Integer Overflow: Predictable ID Generation

An integer overflow in nanoid 3.3.11's internal randomness generation causes the library to fall back to predictable ID sequences, undermining the cryptographic guarantees of its supposedly unguessable identifiers. The fix upgrades the dependency tree to patched versions 3.3.12 or 5.1.11.

high

generateUUID() Ditches MD5 for SHA-256 to Fix CWE-328

The `generateUUID()` helper built request-identifying UUIDs by hashing an input string with MD5, a cryptographically broken algorithm susceptible to collisions. The fix swaps the hash function for SHA-256, reducing the chance that two different inputs produce the same generated identifier.

high

picomatch 2.3.1 ReDoS: Extglob Pattern Catastrophic Backtracking

picomatch versions below 2.3.2, 3.0.2, or 4.0.4 contain a Regular Expression Denial of Service vulnerability in extglob pattern parsing. An attacker can cause catastrophic backtracking with patterns containing nested alternations and quantifiers, freezing any Node.js process that evaluates untrusted glob expressions.