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:
- No string concatenation: The selector
"#issue-cat option"is a static, safe string - Value comparison in code:
$(this).val() == issue.categoriecompares values as JavaScript expressions, not selector syntax - Loose equality intentional: Matches jQuery's attribute selector behavior where
"1"and1are 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()orfind()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.