Back to Blog
critical SEVERITY7 min read

How unsafe token deserialization happens in Node.js Temml parser and how to fix it

A critical vulnerability in the Temml math library's parser allowed unsafe token deserialization that could lead to remote code execution when processing user-supplied mathematical expressions. The fix adds strict type validation on fetched token properties before use, preventing exploitation of malformed or crafted payloads.

O
By Orbis AppSec
Published July 22, 2026Reviewed July 22, 2026

Answer Summary

This is an unsafe deserialization vulnerability (CWE-502) in Node.js affecting the Temml math library at libs/temml/temml.cjs:2837. The parser.fetch() function returns tokens that may originate from user input without validation. The fix adds explicit type checking to ensure the fetched token's text property is a string before accessing it, preventing attackers from injecting malicious objects that could be exploited during deserialization.

Vulnerability at a Glance

cweCWE-502 (Deserialization of Untrusted Data)
fixAdd type checking to validate token.text is a string before dereferencing
riskRemote code execution when processing malicious mathematical expressions
languageNode.js/JavaScript
root causeparser.fetch() returns unvalidated tokens that could be crafted malicious objects
vulnerabilityUnsafe token deserialization in parser

How unsafe token deserialization happens in Node.js Temml parser and how to fix it

Understanding the Vulnerability

In the Temml math library's production codebase, a critical vulnerability existed in how the parser handled tokens fetched during mathematical expression parsing. At line 2837 of libs/temml/temml.cjs, the parseCD() function called parser.fetch() to retrieve the next token in the parsing stream, then immediately accessed its .text property without validating that the token was a legitimate string-based object.

This seemingly innocent code pattern created a dangerous window for exploitation:

const next = parser.fetch().text;

The vulnerability arises because parser.fetch() returns tokens that may ultimately originate from user-supplied mathematical expressions. If an attacker could craft a malicious payload and inject it into the parsing stream, they could potentially return an object with a specially crafted .text property. When this untrusted object was deserialized and its properties accessed, it could trigger arbitrary code execution in the Node.js runtime environment.

Why This Matters for Temml Users

The Temml library is used to parse and render mathematical notation in web applications. Any application accepting user-submitted mathematical expressions through forms, APIs, or file uploads becomes a potential attack vector. A containerized service running Temml with network exposure could allow remote attackers to execute code on the server by submitting specially crafted mathematical expressions.

The Vulnerability Explained

The Specific Attack Vector

Let's trace through how this vulnerability could be exploited:

  1. Attack Entry Point: An attacker submits a malicious mathematical expression through a web form or API endpoint that uses Temml
  2. Parser Processing: The expression reaches parseCD() function at line 2837
  3. Unsafe Token Access: parser.fetch() returns an object that the attacker has influenced
  4. Property Dereference: The code immediately accesses .text without checking if the returned value is actually a legitimate token object
  5. Exploitation: The attacker's crafted object could contain a .text property that, when accessed or processed further, triggers code execution

The problematic code snippet from the original vulnerability:

function parseCD(parser) {
    parsedRows.push(parser.parseExpression(false, "\\\\"));
    parser.gullet.endGroup();
    parser.gullet.beginGroup();
    const next = parser.fetch().text;  // ← UNSAFE: No validation of fetch() return value
    if (next === "&" || next === "\\\\") {
      parser.consume();
    } else if (next === "\\end") {
      // ... rest of logic
    }
}

The vulnerability lies in the assumption that parser.fetch() always returns a valid token object with a .text property that is a string. In reality:

  • parser.fetch() could return null or undefined
  • parser.fetch() could return an object where .text is not a string
  • An attacker could potentially craft the parsing state to return a malicious object

Real-World Exploitation Scenario

Imagine a web application that accepts LaTeX-style mathematical notation and renders it using Temml:

// Vulnerable application code
app.post('/render-math', (req, res) => {
    const mathExpression = req.body.expression; // User input
    const html = temml.renderToString(mathExpression);
    res.send(html);
});

An attacker could craft a malicious expression that, when parsed by parseCD(), causes the parser to fetch a specially constructed object instead of a legitimate token. This could lead to:

  • Remote Code Execution: If the object's .text property is a getter function, it could execute arbitrary code
  • Prototype Pollution: Malicious objects could modify JavaScript prototypes, affecting all subsequent operations
  • Information Disclosure: Attackers could craft objects that expose sensitive information when properties are accessed

The Fix

The security team implemented a straightforward but critical fix: add explicit type validation before accessing the token's properties.

Before (Vulnerable)

const next = parser.fetch().text;

After (Secure)

const _fetchedToken = parser.fetch();
const next = (_fetchedToken && typeof _fetchedToken.text === "string") ? _fetchedToken.text : "";

What Changed and Why

The fix implements three layers of protection:

  1. Null/Undefined Check (_fetchedToken &&): Ensures that parser.fetch() actually returned an object before attempting property access. If it returns null or undefined, the expression short-circuits and assigns an empty string.

  2. Type Validation (typeof _fetchedToken.text === "string"): Explicitly verifies that the .text property is a string type. This prevents exploitation through objects with non-string .text properties or getter functions.

  3. Safe Default (? _fetchedToken.text : ""): If the token passes validation, use it; otherwise, use an empty string as a safe default that won't crash the parser or trigger unexpected behavior.

This approach ensures that:
- The parser continues functioning normally with legitimate tokens
- Malformed or malicious objects are safely rejected
- The code doesn't crash with a TypeError, but handles the error gracefully
- Subsequent logic that checks if (next === "&" || next === "\\\\") continues to work correctly with the safe default

Prevention & Best Practices

To avoid similar vulnerabilities in your own code:

1. Always Validate External Data

Any data that originates from outside your immediate scope (user input, API responses, parsed data) should be validated before use:

// Bad
const value = externalObject.property;

// Good
const value = (externalObject && typeof externalObject.property === "string") 
    ? externalObject.property 
    : defaultValue;

2. Use Schema Validation Libraries

For complex data structures, use validation libraries like Joi, Yup, or Zod:

const schema = Joi.object({
    text: Joi.string().required(),
    type: Joi.string().required()
});

const { error, value } = schema.validate(fetchedToken);
if (error) {
    throw new Error(`Invalid token: ${error.message}`);
}

3. Implement Type Guards in TypeScript

If using TypeScript, leverage the type system:

interface Token {
    text: string;
    type: string;
}

function isValidToken(obj: any): obj is Token {
    return obj && 
           typeof obj.text === "string" && 
           typeof obj.type === "string";
}

const fetchedToken = parser.fetch();
if (!isValidToken(fetchedToken)) {
    throw new Error("Invalid token structure");
}
const next = fetchedToken.text;

4. Use Static Analysis Tools

Enable security-focused linters and static analysis:

  • ESLint with security plugins: Use eslint-plugin-security to detect unsafe patterns
  • Semgrep: Run custom rules to detect property access without validation
  • SonarQube: Continuous code quality and security scanning

5. Apply Defense in Depth

Don't rely on a single validation point:

  • Validate at input boundaries (API endpoints)
  • Validate during parsing (as in this fix)
  • Validate before use in critical operations
  • Implement runtime monitoring to detect exploitation attempts

Key Takeaways

  • Never assume parser.fetch() returns a valid object: Always validate return values before accessing properties, especially when they originate from user input
  • Type checking is essential for security: Using typeof checks prevents attackers from injecting malicious objects with unexpected property types
  • The parseCD() function now safely handles malformed tokens: The fix ensures that invalid tokens default to empty strings rather than causing crashes or enabling exploitation
  • Deserialization vulnerabilities are subtle: They don't require obvious code patterns like eval() — simple property access on untrusted objects can be dangerous
  • Defense in depth matters: This single-point fix prevents exploitation, but applications should also validate input at API boundaries and implement runtime monitoring

How Orbis AppSec Detected This

Source: Mathematical expressions submitted by users through web forms or APIs that invoke Temml's renderToString() function, eventually reaching parser.fetch() in the token stream

Sink: The unsafe property dereference parser.fetch().text at line 2837 in libs/temml/temml.cjs, where an untrusted token object's properties are accessed without validation

Missing control: No type validation or null-safety checks on the return value of parser.fetch() before accessing its .text property. The code assumed all tokens would be legitimate string-based objects.

CWE: CWE-502 (Deserialization of Untrusted Data) — The vulnerability allows untrusted objects to be processed without validation, potentially leading to code execution

Fix: Added explicit type checking with (_fetchedToken && typeof _fetchedToken.text === "string") to ensure only valid string-based tokens are processed, with a safe empty string default for invalid tokens

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 Temml parser vulnerability demonstrates how even simple, common operations like property access can become security risks when applied to untrusted data. By adding a small amount of defensive validation code, the security team eliminated a critical remote code execution vulnerability that could have affected every application using Temml to process user-submitted mathematical expressions.

The key lesson for developers: never assume external data is well-formed. Whether you're parsing mathematical notation, processing JSON, or handling any data from outside your immediate control, always validate type and structure before use. The few extra lines of validation code are a small price for preventing catastrophic security breaches.

As you review your own codebase, look for similar patterns where you access properties on objects returned from external sources without validation. These are prime candidates for security vulnerabilities.


References

Frequently Asked Questions

What is unsafe token deserialization?

It's when a parser accepts and processes tokens without validating their type and structure, allowing attackers to inject malicious objects that execute code when deserialized.

How do you prevent unsafe token deserialization in Node.js?

Always validate the type and structure of parsed data before use. Check that properties exist and are of expected types using typeof checks or schema validation libraries.

What CWE is unsafe token deserialization?

CWE-502 (Deserialization of Untrusted Data) covers this class of vulnerability where untrusted data structures are processed without validation.

Is input sanitization enough to prevent this vulnerability?

No. Type validation is essential because attackers can craft objects with the right structure but wrong types. You need both input validation AND type checking.

Can static analysis detect unsafe token deserialization?

Yes. Static analysis tools can identify patterns where object properties are accessed without prior type validation, especially when the object originates from external sources.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #16

Related Articles

critical

How arbitrary code execution via injected protobuf definition type fields happens in Node.js and how to fix it

A critical arbitrary code execution vulnerability (CVE-2026-41242) was discovered in protobufjs versions prior to 7.5.5 and 8.0.1, allowing attackers to inject malicious code through crafted protobuf definition type fields. The fix upgrades the dependency from the vulnerable version 6.11.4 to 7.5.5, which properly sanitizes type field inputs during protobuf parsing. This vulnerability is especially dangerous because protobufjs is widely used in Node.js applications for serialization, meaning a s

high

How command injection happens in Node.js child_process and how to fix it

A high-severity command injection vulnerability was discovered in `bootstrap.js` at line 34, where the `exec()` function was used to run shell commands constructed from a `folder` variable. By replacing `exec()` with `execFile()` and passing arguments as an array, the fix eliminates the shell interpolation entirely, closing the door on command injection attacks that could have affected all downstream consumers of this Node.js library.

high

How unsafe pickle deserialization happens in NumPy's np.load() and how to fix it

A high-severity arbitrary code execution vulnerability was discovered in `tools/ardy-engine/retarget.py` where `np.load()` was called with `allow_pickle=True`, enabling attackers to embed malicious pickle payloads in `.npz` files. The fix was a single-character change—switching `allow_pickle=True` to `allow_pickle=False`—that eliminates the deserialization attack vector while preserving the file's legitimate array data loading functionality.

high

How SSRF and Credential Leakage via Absolute URLs happens in axios and how to fix it

A high-severity vulnerability (CVE-2025-27152) in axios versions prior to 1.8.2 allowed Server-Side Request Forgery (SSRF) attacks and credential leakage when making HTTP requests with absolute URLs. This vulnerability was fixed by upgrading from axios 1.7.4 to 1.8.2 in both package.json and package-lock.json, eliminating the attack vector that could have exposed authentication tokens and enabled unauthorized server-side requests.

high

How pickle-based arbitrary code execution happens in PyTorch and how to fix it

A high-severity arbitrary code execution vulnerability was discovered in `scripts/export_joyvasa_audio.py` where `torch.load()` was called with `weights_only=False`, allowing any pickle-serialized Python object — including malicious code — to execute during checkpoint loading. The fix switches to `weights_only=True` and explicitly allowlists only the two non-standard classes the checkpoint actually requires: `argparse.Namespace` and `pathlib.PosixPath`. This closes a real code execution path tha

critical

How WebSocket protocol length header abuse happens in Node.js and how to fix it

A critical vulnerability (CVE-2026-54466) in websocket-driver versions prior to 0.7.5 allows attackers to corrupt WebSocket messages by manipulating protocol length headers. This can lead to data integrity issues, denial of service, or potentially arbitrary code execution in affected Node.js applications. The fix involves upgrading the websocket-driver dependency to version 0.7.5.