Back to Blog
high SEVERITY3 min read

form.js jQuery Selector Construction: CWE-79 Quote-Breaking Fix

A defense-in-depth fix in the form.js initialization routine eliminates a jQuery selector injection vector where user-controlled category data was concatenated directly into a string literal. The vulnerable pattern at lines 47-48 of the form handler could allow malicious content to break out of the selector's quoted context and execute arbitrary jQuery methods.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published October 7, 2026•Reviewed October 7, 2026

Answer Summary

The startForm() function in form.js used unsanitized issue.categorie data to construct jQuery selectors via string concatenation. An attacker controlling the categorie field could inject quote characters to break selector syntax and potentially execute arbitrary jQuery methods or XSS payloads. The fix replaces string concatenation with a filter() callback that uses loose equality comparison, eliminating the injection vector. No CVE or GHSA has been assigned. CWE status is unknown.

Vulnerability at a Glance

cweN/A
fixReplace concatenated selector with filter() callback using loose equality
riskQuote-breaking in jQuery selectors enables method execution or XSS
languageJavaScript
root causeUnsanitized issue.categorie concatenated into quoted selector string
vulnerabilityDOM-based XSS via jQuery selector injection

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see PR description for commit
Ecosystem N/A
CVE / GHSA not assigned
CWE unknown

The Vulnerability Explained

The startForm() function populates a form edit interface using data from an issue object. When setting the selected category option, the original code constructed a jQuery selector by directly concatenating issue.categorie into a single-quoted string:

$("#issue-cat option[value='" + issue.categorie + "']").prop('selected', true);

This pattern is vulnerable to selector injection when issue.categorie contains unescaped single quotes. An attacker who controls the category value could inject:

test']; body *{color:red}; [value='test

Or more maliciously, break out entirely and chain jQuery methods:

test']; option:first).parent().html('<img src=x onerror=alert(1)');//

The resulting selector becomes:

$("#issue-cat option[value='test']; option:first).parent().html('<img src=x onerror=alert(1)');//']")

While modern jQuery versions have hardened against some selector parsing attacks, the fundamental issue remains: user data treated as code. The issue.categorie value originates from stored issue data, potentially populated through user input or imported from external sources, making this a persistent DOM-based XSS vector.

The Fix

The remediation replaces string concatenation with jQuery's filter() method, using a callback function that performs loose equality comparison:

// Before: vulnerable concatenation
$("#issue-cat option[value='" + issue.categorie + "']").prop('selected', true);

// After: safe comparison via filter()
$("#issue-cat option").filter(function () {
  return $(this).val() == issue.categorie;
}).prop('selected', true);

This change eliminates the injection surface entirely:

  1. No string concatenation: The selector "#issue-cat option" is a static, safe string
  2. Value comparison in code: $(this).val() == issue.categorie compares values as JavaScript expressions, not selector syntax
  3. Loose equality intentional: Matches jQuery's attribute selector behavior where "1" and 1 are equivalent

The same pattern applies to the subsequent line setting the category display text, though that line uses i18next.t() rather than direct DOM manipulation.

Key Takeaways

  • Never concatenate user data into jQuery selectors—even inside attribute value brackets, quote-breaking attacks can escape the intended selection context
  • Use filter() or find() with callbacks when matching elements by dynamic values; the performance cost is negligible compared to the security gain
  • Loose equality (==) in filter callbacks can preserve backward compatibility with data types when replacing attribute selectors
  • jQuery's val() getter safely retrieves normalized values without executing injected content, unlike selector parsing
  • Defense-in-depth at the sink remains valuable even when source validation exists—data flows are difficult to track across application layers

How Orbis AppSec Detected This

Source: The issue.categorie property loaded from stored issue data in startForm(token)

Sink: The jQuery selector construction $("#issue-cat option[value='" + issue.categorie + "']") passed to .prop('selected', true)

Missing control: No validation that issue.categorie contains only alphanumeric characters safe for unquoted selector contexts; no use of parameterized element lookup

CWE: Unknown—pattern matches CWE-79 (Improper Neutralization of Input During Web Page Generation) but no official classification assigned

Fix: Replaced concatenated selector string with filter() callback using loose equality comparison on $(this).val()

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 fix demonstrates how a single line of jQuery code can create subtle injection risks. The original pattern—concatenating external data into a selector string—feels natural to many developers but violates the principle that data and code must remain separate. By shifting from declarative selector syntax to imperative JavaScript comparison, the code becomes inherently safer without sacrificing functionality. For teams maintaining legacy jQuery codebases, auditing for similar concatenation patterns in .find(), .filter(), and $() calls should be a priority.

Prevention and further reading

Frequently Asked Questions

Why did the fix use loose equality (==) rather than strict equality (===) in the filter callback?

Loose equality was chosen to match jQuery's default attribute value handling, ensuring the selector behavior remains equivalent to the original while eliminating the concatenation vector.

Does the issue.categorie value still need sanitization before reaching startForm()?

Yes—this fix provides defense-in-depth at the sink, but source validation on the categorie field remains important since other code paths may process the same data.

Which jQuery methods besides prop('selected') could be reached through a quote-breaking attack on this selector?

A successful quote break could chain to any jQuery method on the matched set, including html(), append(), or trigger(), depending on where the injected payload terminates the selector.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #141

Related Articles

high

ItemPicker `_commitTraitInput` XSS via Unescaped Trait Chip Rendering

The `_commitTraitInput` function in ItemPicker accepted arbitrary user input for trait values without sanitization, then rendered those values directly into HTML chip elements through `_updateList`. An attacker could inject malicious JavaScript payloads that executed when trait chips were displayed. The fix applies a strict whitelist filter removing all non-alphanumeric characters except spaces and hyphens.

high

formatObject() Leaves localStorage Keys Unescaped Before innerHTML

The `formatObject()` pretty-printer used to build the key-configuration storage viewer interpolated object keys and array key/value pairs directly into an HTML string that is later assigned with `innerHTML`. Only string *values* were passed through `escapeHtml()`, so a crafted key name persisted in `localStorage` could execute script every time a user opened the storage viewer. The fix wraps the index-pair branch and the object-key branch in `escapeHtml()`, keeping the raw path only for non-colo

high

Voice Assistant Widget XSS: Unsanitized Bot Messages Execute in

The voice assistant widget's `appendMessage` function had a critical cross-site scripting (XSS) vulnerability where bot messages were inserted directly into the DOM without sanitization, while user messages were escaped. An attacker controlling bot responses could inject and execute arbitrary JavaScript in the user's browser context. The fix applies HTML escaping to all message types uniformly.

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

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.

high

CVE-2026-104850: MCP TypeScript SDK OAuth Credential Leak

The MCP TypeScript SDK (`@modelcontextprotocol/sdk`) contained a flaw in its OAuth client flow where the MCP server being connected to could influence which authorization server the client talked to, meaning client credentials and token-exchange traffic could be directed to an endpoint the attacker controls. This project was pinned to the affected `1.30.0`; the dependency has been moved to `^1.31.0`, which carries the upstream fix for CVE-2026-104850. Any MCP client that performs OAuth against t