Back to Blog
critical SEVERITY7 min read

How Insecure Update Manifest Fetching happens in Node.js and how to fix it

A critical vulnerability in `server.js` allowed the application to fetch update manifests over unencrypted HTTP connections, opening the door to man-in-the-middle attacks that could serve malicious update payloads. The fix enforces HTTPS-only connections by tightening a single regular expression in the `readUpdateManifest` function. This change closes an attack vector that could have led to remote code execution via a trojanized installer.

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

Answer Summary

This vulnerability is an insecure transport (CWE-319: Cleartext Transmission of Sensitive Information) in a Node.js application where the `readUpdateManifest` function in `server.js` accepted both `http://` and `https://` URLs for fetching update manifests. An attacker with a MITM position could intercept the unencrypted HTTP request and serve a malicious update manifest pointing to a trojanized installer, achieving RCE. The fix changes the URL validation regex from `/^https?:\/\//i` to `/^https:\/\//i`, rejecting any non-HTTPS manifest URL before a network request is made.

Vulnerability at a Glance

cweCWE-319
fixTightened regex to `^https:\/\/` so only HTTPS manifest URLs are accepted
riskAttacker-controlled update manifests can deliver trojanized installers, leading to RCE
languageJavaScript (Node.js)
root causeURL validation regex `^https?:\/\/` permitted plain HTTP manifest URLs
vulnerabilityInsecure Update Manifest Fetching over HTTP (MITM/Downgrade)

The Vulnerability: One Character That Opened the Door to RCE

The server.js file in the Mineradio application handles the critical task of fetching update manifests — the metadata documents that tell the application where to download new versions of itself. A flaw at line 567 in the readUpdateManifest function meant this process could silently fall back to an unencrypted HTTP connection, handing any network-positioned attacker the ability to serve a fake update and achieve remote code execution on every affected machine.

The root cause? A single character in a regular expression: the ? that made the s in https optional.


The Vulnerability Explained

The Vulnerable Code

Here is the readUpdateManifest function as it existed before the fix:

async function readUpdateManifest(ref) {
  const value = String(ref || '').trim();
  if (!value) throw new Error('UPDATE_MANIFEST_MISSING');
  if (/^https?:\/\//i.test(value)) {   // ← BUG: accepts http:// too
    const resp = await fetch(value, {
      headers: { 'User-Agent': `Mineradio/${APP_VERSION}` },
    });
    // ...
  }
}

The regex /^https?:\/\//i uses ? to make the s optional, meaning it matches both:

  • https://legitimate-update-server.com/manifest.json
  • http://attacker-controlled.com/manifest.json

This means any caller — whether from configuration, a deep link, or a network-sourced value — could pass an http:// URL and have it accepted without complaint. The application would then fetch the manifest over a completely unencrypted TCP connection.

Why This Is Critical: The Attack Scenario

Consider the following attack chain specific to this code:

  1. Initial position: An attacker gains a MITM position on the network (e.g., via a rogue Wi-Fi access point, ARP spoofing on a corporate LAN, or a compromised router). Alternatively, they obtain a TLS certificate from a rogue or compromised Certificate Authority — a real-world scenario demonstrated multiple times with DigiNotar, Comodo, and others.

  2. Interception: The application calls readUpdateManifest with an http:// manifest URL. Because there is no TLS, the request travels in plaintext. The attacker intercepts it transparently.

  3. Malicious manifest injection: The attacker responds with a crafted manifest.json that points to an attacker-controlled installer URL, with a matching (but fake) checksum if no signature verification is in place.

  4. Installer delivery: The application calls normalizeManifestUpdateInfo (visible in the diff context at line 563) to parse the attacker's manifest, then proceeds to download and execute the trojanized installer.

  5. Result: Full remote code execution under the user's account — on every machine running the affected version of Mineradio.

This is not a theoretical attack. The User-Agent header in the fetch call (Mineradio/${APP_VERSION}) even reveals the application name and version to any passive observer, making targeted exploitation trivially easy.

What Makes This Worse

  • Auto-update trust: Users implicitly trust auto-update mechanisms. A malicious update that arrives silently is far more dangerous than a phishing link a user might scrutinize.
  • No integrity check visible in the diff: The code fetches the manifest and normalizes it, but there is no visible cryptographic signature verification of the manifest content, meaning HTTPS enforcement is the only line of defense.
  • Production code: This is not test infrastructure. The server.js file is in the production codebase and runs on every end-user machine.

The Fix

The fix is surgical and precise — a one-character change to the URL validation regex:

Before

if (/^https?:\/\//i.test(value)) {

After

if (/^https:\/\//i.test(value)) {

Removing the ? quantifier makes the s in https mandatory. Any URL that begins with http:// (without the s) will no longer match the condition, and the fetch call will never be reached. The function will fall through to whatever non-URL handling exists below, or throw an appropriate error.

Why This Change Is Sufficient (and What It Doesn't Cover)

This fix is the correct and minimal change for this specific problem:

  • It rejects HTTP at the validation layer, before any network I/O occurs — the best place to stop a bad input.
  • It preserves all valid behavior: any legitimate manifest URL using https:// continues to work exactly as before.
  • It is not bypassable via case variation because the regex already uses the i (case-insensitive) flag, meaning HTTP://, Http://, and all other capitalizations are also rejected.

However, developers maintaining this code should be aware that HTTPS enforcement alone does not protect against:
- Compromised CAs issuing fraudulent certificates for the update server domain
- Manifest tampering if the update server itself is compromised

Defense-in-depth would add cryptographic signing of manifests (e.g., verifying an Ed25519 signature on the manifest JSON before parsing it).


Prevention & Best Practices

1. Always Enforce HTTPS in URL Validation

Whenever your code accepts a URL from any external or configurable source and uses it for a network request, validate that it begins with https:// — not http:// or any other scheme:

// Unsafe: accepts http://
if (/^https?:\/\//i.test(url)) { ... }

// Safe: HTTPS only
if (/^https:\/\//i.test(url)) { ... }

// Even safer: use the URL constructor for robust parsing
const parsed = new URL(url);
if (parsed.protocol !== 'https:') throw new Error('HTTPS_REQUIRED');

Using new URL() is preferable to regex for URL parsing because it handles edge cases (e.g., HTTPS:, https://user:pass@host) more robustly.

2. Sign Your Update Manifests

Enforce cryptographic signature verification on all update manifests before acting on their contents. Tools like Tauri's built-in updater (relevant here given the Rust/Tauri dependencies in the repo) support Ed25519 manifest signing out of the box.

3. Audit All Fetch Call Sites for Scheme Validation

Run a codebase-wide search for fetch( and http.get( calls that accept dynamic URLs, and verify each one enforces HTTPS. Semgrep can automate this:

# Semgrep rule sketch
rules:
  - id: http-url-in-fetch
    pattern: fetch($URL, ...)
    message: Verify $URL is validated to HTTPS-only before this fetch call

4. Relevant Standards


Key Takeaways

  • The ? in https? is a silent HTTPS downgrade: In any security-sensitive URL check, https? means "HTTP is fine too." Audit every such regex in your codebase.
  • readUpdateManifest was the single point of failure: All update fetching funneled through this one function, making it both the highest-value target and the right place to add enforcement.
  • Update mechanisms deserve the same scrutiny as authentication: A compromised update channel is equivalent to a compromised login — both give an attacker persistent access.
  • The User-Agent header (Mineradio/${APP_VERSION}) advertised the target: Over HTTP, this header is visible to any passive observer, enabling version-targeted attacks. HTTPS hides it.
  • One-line fixes can close critical attack paths: The entire RCE chain described above is broken by removing a single ? character.

How Orbis AppSec Detected This

  • Source: The ref parameter passed into readUpdateManifest(ref) — an externally controllable string representing the manifest URL.
  • Sink: The fetch(value, { headers: { 'User-Agent': ... } }) call at server.js:567, reached only after the insufficiently strict regex check passes.
  • Missing control: No enforcement that the URL scheme is exclusively https:. The regex /^https?:\/\//i permitted plain HTTP URLs to reach the fetch call without any warning or error.
  • CWE: CWE-319 — Cleartext Transmission of Sensitive Information (also related to CWE-494: Download of Code Without Integrity Check).
  • Fix: The regex was tightened from /^https?:\/\//i to /^https:\/\//i, making the s in https mandatory and rejecting all non-HTTPS manifest URLs before any network request is made.

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

A single ? in a regular expression at line 567 of server.js was the difference between a secure update mechanism and a remote code execution vulnerability. The readUpdateManifest function accepted http:// URLs, meaning any attacker with a network position between the user and the update server could intercept the request, inject a malicious manifest, and deliver a trojanized installer — silently, on every affected machine.

The fix is a one-character change: removing the ? from https? so that only https:// URLs are accepted. Small changes in security-critical validation code carry outsized consequences in both directions — a single permissive character can open a critical attack path, and removing it can close it entirely.

When building or reviewing update mechanisms, treat the transport layer as a security boundary, not an implementation detail. Enforce HTTPS strictly, verify manifest signatures cryptographically, and audit every URL validation regex for accidental permissiveness.


References

Frequently Asked Questions

What is insecure update manifest fetching?

It occurs when an application downloads update metadata or installers over unencrypted HTTP instead of HTTPS, allowing an attacker to intercept and replace the content with malicious payloads.

How do you prevent MITM attacks on update fetching in Node.js?

Enforce HTTPS-only URLs in all update-related fetch calls, validate URL schemes strictly before making network requests, and consider adding checksum or signature verification of downloaded content.

What CWE is insecure update manifest fetching?

CWE-319: Cleartext Transmission of Sensitive Information, and it also relates to CWE-494 (Download of Code Without Integrity Check).

Is HTTPS alone enough to prevent update-related MITM attacks?

HTTPS is a necessary baseline but not sufficient on its own. Combining HTTPS enforcement with cryptographic signature verification of manifests and installers provides defense-in-depth against compromised CAs.

Can static analysis detect insecure HTTP usage in update logic?

Yes. Tools like Semgrep can flag regex patterns or string literals that permit `http://` in security-sensitive fetch calls, and Orbis AppSec detected exactly this pattern automatically.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #17

Related Articles

high

How Dependabot Missing Cooldown happens in GitHub Actions and how to fix it

A high-severity misconfiguration in `.github/dependabot.yml` left this Node.js library without a cooldown period, meaning Dependabot would immediately propose updates to newly published packages — including potentially malicious or unstable ones. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` package ecosystem entries, introducing a mandatory 7-day waiting period before any new package version is surfaced as an update candidate.

critical

How CSRF Protection Failures Happen in FastAPI and How to Fix Them

A critical CORS misconfiguration in `backend/main.py` allowed cookies to be sent alongside wildcard-origin requests, violating the CORS specification and opening the door to cross-site request forgery attacks. The fix conditionally disables `allow_credentials` when the allowed origins list contains a wildcard, bringing the configuration into compliance with browser security rules. This change closes a subtle but dangerous gap that could have let attackers on sibling subdomains forge authenticate

critical

How Missing Rate Limiting Happens in Node.js SSE Handlers and How to Fix It

A critical missing rate-limiting control in `src/sse/handlers/chat.js` allowed any caller to flood the SSE chat endpoint with unlimited requests, risking server resource exhaustion, denial of service, and runaway AI provider API costs. The fix introduces a per-IP sliding-window rate limiter that caps requests at 60 per minute and returns HTTP 429 on violations. Because the endpoint was publicly reachable and only validated API keys — not request frequency — exploitation required nothing more tha

medium

How Denial of Service via Catastrophic Backtracking happens in Node.js and how to fix it

CVE-2026-4867 is a Denial of Service vulnerability in path-to-regexp 0.1.12 where malformed URL parameters can trigger catastrophic backtracking in the library's regular expression engine, allowing an attacker to hang or crash a Node.js application with a single crafted request. The fix upgrades path-to-regexp to version 0.1.13, which patches the vulnerable regex patterns. This change was applied via a package-level override to ensure the patched version is used throughout the entire dependency

high

How Denial of Service via Exponential-Time Complexity happens in Node.js and how to fix it

CVE-2026-13149 is a high-severity Denial of Service vulnerability in the `brace-expansion` npm package, where crafted input strings trigger exponential-time processing that can freeze or crash a Node.js application. The fix upgrades `brace-expansion` from `2.0.2` to `2.1.4` and `minimatch` from `5.1.6` to `5.1.9`, along with npm `overrides` to ensure the patched versions are used throughout the entire dependency tree.

critical

How Unrestricted File Upload happens in Node.js/Express and how to fix it

A critical unrestricted file upload vulnerability was discovered in `mainsystem/routes/admin/profile.js`, where the avatar upload endpoint accepted any file type without validation. An authenticated attacker could upload a malicious server-side script to a web-accessible directory and execute arbitrary code on the server. The fix adds MIME type filtering, an allowlist of safe image formats, and a 2 MB file size limit to the multer middleware.