Back to Blog
critical SEVERITY8 min read

How Server-Side Request Forgery (SSRF) happens in JavaScript and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `libraries/microworlds/video.js` where the `VideoCanvasWrapper.loadVideo()` function passed user-controlled URLs directly to `fetch()` without any validation. An attacker could exploit this by supplying URLs pointing to internal services, localhost endpoints, or malicious external servers. The fix introduces strict URL parsing and protocol validation before any network request is made.

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

Answer Summary

This is a Server-Side Request Forgery (SSRF) vulnerability (CWE-918) in JavaScript's `VideoCanvasWrapper.loadVideo()` function in `libraries/microworlds/video.js`. The function passed a user-controlled `src` parameter directly to `fetch()` with no URL scheme or domain validation, allowing attackers to probe internal services or exfiltrate data. The fix uses the `URL` constructor to parse and validate the URL, then enforces that only `http:` or `https:` protocols are permitted before any network call is made.

Vulnerability at a Glance

cweCWE-918
fixParse the URL with `new URL()` and enforce `http:` or `https:` protocol before calling `fetch()`
riskAttackers can make the application fetch arbitrary internal or external URLs, exposing internal services and enabling data exfiltration
languageJavaScript
root causeUser-controlled `src` parameter passed directly to `fetch()` in `VideoCanvasWrapper.loadVideo()` without URL validation
vulnerabilityServer-Side Request Forgery (SSRF)

How Server-Side Request Forgery (SSRF) Happens in JavaScript and How to Fix It

The Vulnerability at a Glance

Field Detail
Vulnerability Server-Side Request Forgery (SSRF)
CWE CWE-918
Language JavaScript
Risk Attackers can make the application fetch arbitrary internal or external URLs
Root Cause User-controlled URL passed directly to fetch() without validation
Fix Parse URL with new URL() and enforce http:/https: protocol allowlist

Introduction

The libraries/microworlds/video.js file handles video loading for the Microworlds environment — a seemingly straightforward task of fetching a video resource by URL. But a flaw in VideoCanvasWrapper.loadVideo() at line 213 turned this routine operation into a serious security risk: any URL a user could pass to the vid_costume primitive would be handed directly to fetch() with zero validation.

This is a textbook Server-Side Request Forgery (SSRF) vulnerability. The function trusted the caller completely, meaning an attacker who could influence the src argument could point the application at internal services, cloud metadata endpoints, or attacker-controlled servers — all without any warning or restriction.


The Vulnerability Explained

What the Code Was Doing

Before the fix, VideoCanvasWrapper.loadVideo() looked like this:

VideoCanvasWrapper.prototype.loadVideo = function(src){
    fetch(src).then(function(response) {
        return response;
    }).catch(function() {
        // ...
    });
}

The src parameter flows directly into fetch() — no parsing, no protocol check, no domain restriction. Whatever string arrives as src, the browser or runtime will dutifully attempt to fetch it.

How an Attacker Would Exploit This

The vid_costume primitive in the Microworlds environment is the user-facing entry point that calls loadVideo(). An attacker (or a malicious project shared with another user) could invoke it like this:

vid_costume(sprite, 'http://localhost:8080/admin', false, 'Loading')

This would cause the application to make an HTTP request to the local machine's admin interface on port 8080 — a port that would normally be inaccessible from the outside. The response data could then be exfiltrated or used to probe the internal network topology.

Other concrete attack scenarios include:

  • Cloud metadata exfiltration: http://169.254.169.254/latest/meta-data/iam/security-credentials/ — the AWS instance metadata endpoint, which can expose IAM credentials.
  • Internal service enumeration: Looping through http://192.168.1.1 through http://192.168.1.254 to map internal network services.
  • Data exfiltration to attacker servers: https://evil.example.com/collect?data=... to send internal data outbound.
  • Non-HTTP protocol abuse: file:///etc/passwd or ftp://internal-server/ if the runtime supports those schemes.

Why This Matters for Microworlds

Microworlds projects can be shared between users. A malicious project author could embed a crafted vid_costume call that silently probes the victim's local network or internal services when another user opens the project. The victim would see only a video loading spinner — while their machine makes requests they never intended.


The Fix

What Changed

The fix adds a validation block at the very top of VideoCanvasWrapper.loadVideo(), before fetch() is ever called:

VideoCanvasWrapper.prototype.loadVideo = function(src){
    // NEW: Parse and validate the URL before fetching
    var parsedUrl;
    try {
        parsedUrl = new URL(src);
    } catch(e) {
        throw new Error("Invalid video URL.");
    }
    if(parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
        throw new Error("Video URL must use http or https protocol.");
    }
    // Only reaches fetch() if URL is valid and uses http/https
    fetch(src).then(function(response) {
        return response;
    }).catch(function() {
        // ...
    });
}

Before vs. After

Before (vulnerable):

VideoCanvasWrapper.prototype.loadVideo = function(src){
    fetch(src).then(function(response) {
        return response;
    }).catch(function() { /* ... */ });
}

After (fixed):

VideoCanvasWrapper.prototype.loadVideo = function(src){
    var parsedUrl;
    try {
        parsedUrl = new URL(src);
    } catch(e) {
        throw new Error("Invalid video URL.");
    }
    if(parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
        throw new Error("Video URL must use http or https protocol.");
    }
    fetch(src).then(function(response) {
        return response;
    }).catch(function() { /* ... */ });
}

Why This Fix Works

Step 1 — new URL(src): The built-in URL constructor is the correct tool for parsing URLs. It is not fooled by encoding tricks, null bytes, or malformed strings that might slip past a regex. If src cannot be parsed as a valid URL, it throws a TypeError immediately, which the catch block converts into a clear error message.

Step 2 — Protocol allowlist: After parsing, parsedUrl.protocol is checked against an explicit allowlist of 'https:' and 'http:'. This single check eliminates:
- file:// — local filesystem reads
- ftp:// — FTP requests
- data: — inline data URIs that could bypass other checks
- javascript: — JavaScript execution via URL
- Any other non-standard scheme

The combination of these two steps means fetch() is only ever called with a structurally valid URL that uses a web-standard protocol. Localhost and internal IP addresses are not explicitly blocked by this fix (which would require hostname validation as a further hardening step), but the protocol restriction alone eliminates the most dangerous attack vectors like file:// and custom scheme abuse.


Key Takeaways

  • VideoCanvasWrapper.loadVideo() was the exact sink — any SSRF fix in this file must gate the fetch() call, not just log or catch errors after the fact.
  • The vid_costume primitive is a user-facing entry point — user-controlled data reached fetch() through a code path that appeared to be a simple media feature, not an obvious security boundary.
  • new URL() is the right tool for URL validation in JavaScript — string prefix checks like startsWith('https://') are insufficient and bypassable.
  • Protocol allowlisting eliminates entire classes of attack — blocking everything except http: and https: removes file://, ftp://, data:, and custom scheme abuse in a single check.
  • SSRF can hide in media and asset loading code — not just in explicit API proxy endpoints. Any function that calls fetch() with external input is a potential SSRF vector.

How Orbis AppSec Detected This

  • Source: The src parameter of VideoCanvasWrapper.loadVideo(), supplied by user input via the vid_costume Microworlds primitive.
  • Sink: fetch(src) at libraries/microworlds/video.js:213, called with the unvalidated src value.
  • Missing control: No URL parsing, protocol validation, or hostname restriction was present before the fetch() call.
  • CWE: CWE-918 — Server-Side Request Forgery (SSRF).
  • Fix: Added new URL() parsing with a try/catch block and a protocol allowlist check (http: and https: only) before fetch() is invoked.

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

The SSRF vulnerability in VideoCanvasWrapper.loadVideo() is a reminder that security boundaries exist everywhere user input flows into network calls — not just in obvious places like login forms or API endpoints. A video loading utility in an educational coding environment is just as capable of becoming an SSRF vector as a dedicated HTTP proxy, if the URL it receives is never validated.

The fix is elegant precisely because it is minimal: two validation steps added before the existing fetch() call, with no changes to the happy path for legitimate http:// and https:// video URLs. The URL constructor handles the heavy lifting of parsing, and the protocol check enforces the allowlist. Together, they close the attack surface without breaking any valid use case.

When you write code that calls fetch() — or any other network primitive — with a URL that could be influenced by user input, treat that URL as untrusted data. Parse it. Check its protocol. Consider its hostname. Then fetch.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #3559

Related Articles

high

ip-address 10.2.0 SSRF: Inconsistent Parsing Bypasses IP Checks

The `ip-address` npm package version 10.2.0 contains an inconsistent parsing vulnerability that allows attackers to bypass IP-based access controls. By representing IPv4 addresses in IPv4-mapped IPv6 notation, attackers can trick applications into allowing requests to blocked internal addresses. Upgrading to 10.3.1 resolves this through stricter address normalization.

medium

How gitlab.bandit.B501 happens in Python and how to fix it

The `proverbia-scraper.py` script disabled TLS certificate verification on its `requests.get()` call and silenced the resulting security warnings, exposing the scraper to man-in-the-middle attacks. The fix removes the `verify=False` flag and the warning suppression, restoring proper certificate validation while keeping the existing 30-second timeout intact.

high

How Server-Side Request Forgery (SSRF) happens in Go HTTP handlers and how to fix it

A Server-Side Request Forgery (SSRF) vulnerability was discovered in `internal/web/controller/server.go` where the `applySubTemplate` endpoint accepted arbitrary URLs from user input and passed them directly to `serverService.ApplySubTemplateFromGithub()` without any host validation. An attacker could exploit this to make the server issue HTTP requests to internal network resources, cloud metadata endpoints, or redirect-controlled destinations. The fix introduces a strict allowlist that restrict

critical

How SSRF via Vulnerable Dependency Versions Happens in Node.js and How to Fix It

A permissive semver range in `package.json` allowed npm to install axios versions vulnerable to SSRF (CVE-2024-39338). By bumping the minimum version from `^1.6.0` to `^1.7.4`, all downstream consumers of this SDK are now protected from server-side request forgery attacks. This critical fix required changing just one line in the dependency manifest.

critical

How Server-Side Request Forgery happens in Python FastAPI and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in app.py where the `/parse` and `/parse-video` endpoints accepted user-supplied URLs with only substring validation. The application checked if 'doubao.com' appeared anywhere in the URL string, allowing attackers to bypass this check and access internal services, cloud metadata endpoints, or scan the internal network. The fix implemented proper hostname parsing with an allowlist of legitimate domains.

critical

How Server-Side Request Forgery happens in Node.js maintenance scripts and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `maintenance/getImages.js`, where the `getImage()` function passed database-sourced URLs directly to `axios.get()` without any validation. An attacker who could modify the elements database could redirect these requests to internal network resources — including AWS cloud metadata endpoints — potentially exposing IAM credentials and other sensitive infrastructure data. The fix introduces a strict URL allowlist that limi