Back to Blog
critical SEVERITY3 min read

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 11, 2026•Reviewed September 11, 2026

Answer Summary

The `deleteNestedProperty` function in propertyUtils.ts accepted user-controlled dot-notation paths without validating against dangerous property names. An attacker could pass paths like `__proto__.polluted` or `constructor.prototype.polluted` to modify Object.prototype, affecting all objects in the application and potentially leading to remote code execution or authentication bypass. The fix introduces a `dangerousKeys` array containing `__proto__`, `constructor`, and `prototype`, rejecting any path containing these keys before traversal. This is first-party code with no assigned CVE, GHSA, or CWE identifier.

Vulnerability at a Glance

cweunknown
fixBlock `__proto__`, `constructor`, and `prototype` keys before object traversal
riskAttacker can modify Object.prototype, affecting all objects and potentially enabling RCE or auth bypass
languageTypeScript/JavaScript
root causeMissing validation of dangerous property names in dot-notation path parsing
vulnerabilityPrototype Pollution

Affected Versions

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

The Vulnerability Explained

The deleteNestedProperty function accepts a FrontMatterCache object and a dot-notation path string, splitting the path on periods to traverse nested properties. Before the fix, this code path lacked any validation against JavaScript's dangerous property names:

export const deleteNestedProperty = (obj: FrontMatterCache, path: string): boolean => {
    if (!obj || !path) {
        return false;
    }
    const keys = path.split('.');
    let current = obj;
    // Navigate to the parent of the target property

The vulnerability lies in the unconditional path.split('.') followed by property access using those keys. When path contains __proto__ or constructor, the traversal reaches Object.prototype instead of an ordinary object property. The delete operation—or any subsequent property assignment—then modifies the shared prototype, polluting every object in the JavaScript runtime.

Consider a note-taking application using this utility to process user-authored frontmatter. An attacker crafts metadata with a property path of __proto__.isAdmin. When the application attempts to delete this "property," it actually sets isAdmin: true on Object.prototype. Every subsequent object creation—including internal session objects—inherits this polluted property, potentially bypassing authentication checks that test user.isAdmin.

The constructor.prototype variant achieves the same effect through a longer traversal: constructor.prototype.polluted reaches Object.prototype via obj.constructor.prototype, which is identical to __proto__ in most object hierarchies.

The Fix

The remediation adds explicit validation before any object traversal:

const dangerousKeys = ["__proto__", "constructor", "prototype"];

export const deleteNestedProperty = (obj: FrontMatterCache, path: string): boolean => {
    if (!obj || !path) {
        return false;
    }
    const keys = path.split('.');
    if (keys.some(key => dangerousKeys.includes(key))) {
        return false;
    }
    let current = obj;

This change introduces three specific protections:

  1. Early rejection: The some() check runs immediately after splitting the path, before any object property access occurs
  2. Exact matching: The dangerousKeys array uses exact string matches, avoiding false positives on legitimate property names like reconstruct or prototypical
  3. Fail-closed: The function returns false (deletion not performed) rather than throwing, maintaining API stability while preventing pollution

The fix is minimal and surgical—only the vulnerable path is modified, with no changes to setNestedProperty or other related functions. This suggests the maintainers audited similar code paths separately or determined they weren't exploitable in this context.

Key Takeaways

  • Dot-notation parsers are prototype pollution hotspots: Any code splitting strings on . and using those segments as property keys must validate against __proto__, constructor, and prototype before the first bracket or dot access
  • The FrontMatterCache type offers no protection: Type safety in TypeScript doesn't prevent runtime prototype manipulation; the vulnerability exists at the JavaScript level regardless of static types
  • Deletion operations pollute too: While prototype pollution is often associated with assignment (obj[key] = value), the delete operator on prototype properties can also corrupt object behavior or enable further exploitation
  • Exact matching prevents collateral damage: Using includes() with an explicit array avoids regex pitfalls and ensures myConstructor remains valid while constructor is blocked

How Orbis AppSec Detected This

  • Source: The path parameter of deleteNestedProperty, accepting user-controlled dot-notation strings from frontmatter processing
  • Sink: Dynamic property access via let current = obj followed by traversal using unsanitized keys array elements
  • Missing control: No validation that path segments avoid __proto__, constructor, or prototype before object traversal begins
  • CWE: unknown (no identifier assigned)
  • Fix: Added dangerousKeys array with exact-match filtering using keys.some(key => dangerousKeys.includes(key)) before any property access

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 prototype pollution vulnerability in deleteNestedProperty demonstrates how ubiquitous utility functions—simple property deleters—become security-critical when they process untrusted input. The dot-notation convenience that makes frontmatter handling ergonomic also creates an attack surface reaching the root of JavaScript's object model. The fix's explicit dangerousKeys whitelist provides a pattern applicable to any property-path parser: validate before traversing, and treat prototype-accessing keys as inherently suspicious.

Prevention and further reading

Frequently Asked Questions

Does the fix change the return value when `deleteNestedProperty` receives a path with `__proto__`?

Yes. Previously, such paths would attempt deletion and potentially pollute prototypes; now the function returns `false` immediately without modifying any object.

Is `constructor.prototype.polluted` blocked by the same check as `__proto__.polluted`?

Yes. The `dangerousKeys` array includes both `__proto__` and `constructor`, so any path containing either key is rejected, regardless of position in the dot-notation string.

What happens to legitimate property names that happen to contain "constructor" as a substring?

The check uses exact string matching against `dangerousKeys`, so a property named `myConstructor` or `constructors` would not be blocked—only the exact key `constructor` triggers the guard.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #156

Related Articles

high

CVE-2026-4800: lodash Template Imports Allow Code Execution

CVE-2026-4800 affects lodash's `_.template()` templating API, where untrusted input reaching the `imports` option can lead to arbitrary code execution. The fix was shipped as a dependency upgrade from lodash 4.17.21 to 4.18.1 in the project's lockfile, though the PR itself notes it was never verified against the actual code paths in this repository.

critical

proxy-addr 2.0.7 IP Spoofing: CVE-2026-90711 Trust Bypass

A critical vulnerability in proxy-addr 2.0.7 allowed attackers to spoof client IP addresses by manipulating X-Forwarded-For headers when the trust chain evaluation contained specific misconfigurations. The fix in version 2.0.8 hardens the trust evaluation logic to prevent IP address falsification in Express.js applications relying on this common middleware dependency.

high

TweenMax `_applyCycle` Prototype Pollution via vars.cycle Keys

A bundled copy of the TweenMax animation library copied attacker-influenceable `vars.cycle` property names straight onto a tween configuration object using an unguarded `for...in` loop, so a key named `__proto__`, `constructor`, or `prototype` was written through to the object's prototype chain. The fix adds an explicit key denylist to both copies of the `_applyCycle` helper so those three names are skipped during the merge. No CVE or GHSA is assigned; the issue is tracked as CWE-1321 (Improperl

high

picomatch 2.3.1 ReDoS: Extglob Pattern Catastrophic Backtracking

picomatch versions below 2.3.2, 3.0.2, or 4.0.4 contain a Regular Expression Denial of Service vulnerability in extglob pattern parsing. An attacker can cause catastrophic backtracking with patterns containing nested alternations and quantifiers, freezing any Node.js process that evaluates untrusted glob expressions.

critical

pet-window.js Dynamic Code Evaluation: CWE-94 Hardening via Number

The pet-window module constructed dynamic JavaScript by embedding raw configuration values into code strings. An attacker with local access could inject arbitrary JavaScript by modifying stored configuration. The fix replaces string interpolation with explicit Number() coercion and NaN validation for all numeric configuration parameters.

high

sanitizeUnicodeInput(): Fullwidth U+ Bypasses Codepoint Validation

The `sanitizeUnicodeInput()` helper used by the project character-range settings screen rewrote `U+` prefixes to `0x` and called `parseInt()`, but never normalized its argument first. Compatibility-equivalent forms such as fullwidth `U+`, superscript digits, or mathematical alphanumerics never matched the `/U\+/gi` regex, fell through to the `else return inputString` branch, and were handed back to callers verbatim as "sanitized" values. The fix inserts a `String.prototype.normalize('NFKC')` pas