Affected Versions
| Affected | not applicable (first-party code) |
| Fixed in | not applicable (first-party code) — see commit details |
| Ecosystem | N/A |
| CVE / GHSA | not assigned |
| CWE | CWE-843 (Access of Resource Using Incompatible Type) |
Introduction
A type confusion weakness in the requestInput() utility function created a subtle but dangerous path for malformed data to propagate through JavaScript's type system. The function, which handles type conversion for boolean and numeric inputs, assumed its type parameter would always be a valid primitive and that float conversions would succeed—assumptions that fail under adversarial input.
The vulnerability sits at the boundary between dynamic JavaScript and typed expectations. When requestInput(de, du, field, type) receives a type value that is NaN or an unexpected object, or when a float conversion produces NaN, the function's previous implementation would dutifully return these poisoned values to callers. Downstream code expecting clean numbers or booleans would then face arithmetic surprises or type confusion.
The Vulnerability Explained
The vulnerable code in requestInput() performed type conversion without validating either the conversion result or the type parameter itself:
export function requestInput(de, du, field, type) {
// ... earlier code ...
if (typeof type === 'undefined') {
console.error(`requestInput: type is not defined for ${field}`);
return null;
}
// No validation of whether type is NaN or an unexpected object
du[field] = type;
return de[field];
}
The function's contract promises to return converted values, but it had two critical gaps:
NaN type parameter: If type itself was NaN (perhaps from a previous failed calculation), typeof type === 'number' would pass, yet NaN is not a valid type indicator. The function would assign NaN to du[field] and return the raw input.
Unexpected object types: An attacker could pass {} or any object as type. Since typeof {} === 'object', the initial undefined check would pass, and the object would propagate through the system.
Float conversion to NaN: For float types, conversion of non-numeric strings produces NaN. Without explicit handling, this NaN would be returned to callers who might use it in comparisons (NaN === NaN is false) or arithmetic operations that silently poison further calculations.
Consider this attack scenario: A service uses requestInput() to parse a rate-limiting parameter from user input:
const rateLimit = requestInput(dataEnv, dataUsed, 'maxRequests', 'float');
// Attacker sends: { maxRequests: 'unlimited' }
// rateLimit becomes NaN
if (rateLimit > 1000) { // false—NaN comparisons are always false
applyStrictLimits(); // skipped!
}
The Fix
The defense-in-depth fix adds explicit validation at three critical points. First, it rejects NaN as a type parameter:
if (typeof type === 'number' && Number.isNaN(type)) {
console.error(`requestInput: type is NaN for ${field}`);
return null;
}
Second, it whitelists acceptable type values:
if (!['string', 'number', 'boolean'].includes(typeof type)) {
console.error(`requestInput: type has an unexpected type (${typeof type}) for ${field}`);
return null;
}
These changes work together to ensure that requestInput() maintains its security invariant: it returns null, a valid primitive, or a defined value—but never unhandled NaN or unexpected object types that could propagate through calling code.
The fix is intentionally conservative. Rather than attempting to coerce or sanitize unexpected inputs, it fails closed with null, forcing callers to handle the error case explicitly.
Key Takeaways
- Never trust the
typeofcheck alone for numeric parameters—NaNhastypeof 'number'and requiresNumber.isNaN()detection - Whitelist acceptable type values when a parameter must be one of a fixed set of primitives; blacklist approaches miss exotic object types
- Make failure modes explicit and bounded rather than allowing poisoned values to propagate; returning
nullon validation failure is safer than returningNaN - Float conversions require NaN handling—any code path that parses user input to floating-point numbers must explicitly check
Number.isNaN()before using the result - Defense-in-depth at utility boundaries prevents vulnerabilities from becoming exploitable even when the immediate caller appears safe
How Orbis AppSec Detected This
Source: The type parameter and input values passed to requestInput() from upstream request handlers
Sink: The requestInput() function's return of unvalidated NaN values and unexpected object types to calling code
Missing control: No validation that type is a valid primitive (not NaN, not an object) and no check that float conversions produce valid numbers rather than NaN
CWE: CWE-843 — Access of Resource Using Incompatible Type
Fix: Added explicit Number.isNaN(type) check and whitelist validation for typeof type being 'string', 'number', or 'boolean'
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
Type confusion vulnerabilities in JavaScript often hide in the gap between dynamic flexibility and developer assumptions. The requestInput() fix demonstrates that even "internal" utility functions need rigorous validation—especially when they sit at the boundary between untrusted input and application logic. By making the failure mode explicit and returning null rather than propagating NaN or unexpected objects, this defense-in-depth change prevents a class of arithmetic and type-bypass attacks before they can reach exploitable code paths.