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:
- Early rejection: The
some()check runs immediately after splitting the path, before any object property access occurs - Exact matching: The
dangerousKeysarray uses exact string matches, avoiding false positives on legitimate property names likereconstructorprototypical - 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, andprototypebefore the first bracket or dot access - The
FrontMatterCachetype 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), thedeleteoperator 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 ensuresmyConstructorremains valid whileconstructoris blocked
How Orbis AppSec Detected This
- Source: The
pathparameter ofdeleteNestedProperty, accepting user-controlled dot-notation strings from frontmatter processing - Sink: Dynamic property access via
let current = objfollowed by traversal using unsanitizedkeysarray elements - Missing control: No validation that path segments avoid
__proto__,constructor, orprototypebefore object traversal begins - CWE: unknown (no identifier assigned)
- Fix: Added
dangerousKeysarray with exact-match filtering usingkeys.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.