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
Published September 11, 2026Reviewed 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

critical

eval() in Async Function Constructor Enables Runtime Escape

The eval.mjs command handler used raw `eval()` to execute JavaScript expressions, creating a critical code injection path if owner credentials are compromised. The fix replaces `eval()` with the `AsyncFunction` constructor and explicitly shadows `process`, `require`, and other runtime globals as parameters, preventing evaluated code from reaching the Node.js runtime even when authentication boundaries fail.

high

How Regular Expression Denial of Service (ReDoS) Happens in Node.js trim-newlines and How to Fix It

CVE-2021-33623 exposed a Regular Expression Denial of Service (ReDoS) vulnerability in the npm package `trim-newlines` versions 1.0.0 and earlier. The vulnerable `.end()` method used an inefficient regex pattern that could cause severe performance degradation when processing malicious input. Upgrading to version 4.0.1 patches the regex implementation and eliminates the attack surface.

critical

How CSS Injection via Weak Pattern Validation happens in Vue.js and how to fix it

A critical CSS injection vulnerability in `testpage/App.vue` allowed attackers to bypass weak HTML5 pattern validation and load malicious stylesheets. The fix replaces direct variable assignment with a hardened `setCustomStylesheetHref()` method using strict regex validation.

critical

How Unvalidated Dynamic Component Loading happens in TypeScript/Viewi and how to fix it

A critical vulnerability in Viewi's component loader allowed attackers to inject malicious JavaScript through compromised or MITM-attacked external component servers. The fix adds proper HTTP response validation before parsing dynamically fetched JSON components.

high

How Denial of Service via Crafted ZIP File happens in Node.js and how to fix it

CVE-2026-39244 is a high-severity denial of service vulnerability in the adm-zip npm package that allows attackers to crash Node.js applications by uploading maliciously crafted ZIP files. The fix upgrades adm-zip from version 0.5.16 to 0.6.0, which adds proper memory bounds checking to prevent excessive allocation during archive extraction.

critical

How stored XSS happens in TinyMCE plugins and how to fix it

The snippets plugin's `Main.ts` inserted raw, unsanitized snippet content directly into the TinyMCE editor via `editor.insertContent(snippet.content)`, allowing stored JavaScript payloads saved by any snippet editor to execute in every user's browser. The fix routes snippet content through TinyMCE's own parser and serializer before insertion, stripping dangerous markup while preserving legitimate formatting.