Object.hasOwn vs Lodash has for Prototype Pollution
This article examines the core differences between ES2022's native
Object.hasOwn() method and Lodash’s _.has()
function in the context of prototype pollution defense. It covers how
each handles inherited properties, property path traversal,
null-prototype objects, and the security implications of using native
shallow property checks compared to library-based deep path
evaluation.
Shallow Key Checking vs. Path Resolution
The most fundamental architectural difference between
Object.hasOwn() and _.has() lies in how keys
are parsed and traversed:
Object.hasOwn(obj, prop)performs a strictly flat, single-level lookup. It takes the property argument directly as a string or symbol without parsing delimiters. A property named"a.b"or"__proto__"is treated purely as a literal key on the target object. It never traverses nested structures or navigates the prototype chain._.has(object, path)supports path navigation via strings (e.g.,'user.role') or arrays (e.g.,['user', 'role']). Because Lodash must traverse intermediate objects to check deep keys, it introduces internal path-resolution logic. While modern versions of Lodash actively defend against accessing'__proto__','constructor', and'prototype'during path traversal, path-resolving logic inherently exposes a larger attack surface than a non-traversing native check.
Defense Against Polluted Inherited Properties
Both methods are designed to verify whether an object holds an "own" property rather than an inherited one. This distinction is critical when defending against prototype pollution:
- If an attacker pollutes
Object.prototypewith a property such asisAdmin = true, readinguser.isAdmindirectly will evaluate totrueon an unprivileged object. - Both
Object.hasOwn(user, 'isAdmin')and_.has(user, 'isAdmin')will returnfalsebecause the property resides on the prototype chain, not directly on theuserinstance.
Using either method prevents code from mistakenly treating an injected prototype property as a valid, intentional property of the instance.
Handling Null-Prototype Objects and Overrides
Before ES2022, the common idiom
obj.hasOwnProperty('prop') suffered from two notable
vulnerabilities:
- Objects created via
Object.create(null)do not inherit fromObject.prototype, causingobj.hasOwnPropertyto throw aTypeError. - Malicious inputs or polluted prototypes could override the
hasOwnPropertymethod itself with an arbitrary function or property.
Both Object.hasOwn() and _.has() mitigate
these failure modes:
Object.hasOwn()is a static method on theObjectconstructor. It executes safely on objects created without prototypes and cannot be hijacked by modifying the target object's own properties._.has()abstracts this check by internally callingObject.prototype.hasOwnProperty.call(object, key)(or equivalent safe lookups), ensuring compatibility across null-prototype objects and preventing execution of overridden methods on the instance.
Security Guarantees in Input Sanitization
When building defenses to sanitize input keys against prototype pollution attacks:
Object.hasOwn()is ideal for validating keys during object merging or cloning operations. Because it operates natively at the runtime level, it introduces no JavaScript-layer parsing overhead or recursion bugs. It guarantees that properties like__proto__are evaluated as literal own keys on the target rather than triggering prototype resolution._.has()is designed for querying nested data structures. If you need to confirm that a nested path exists without triggering inherited prototype values,_.has()provides deep traversal safety. However, rely on modern, fully patched versions of Lodash, as legacy versions contained path-traversal flaws that allowed prototype pollution bypasses.
For validating property ownership at a single layer, native
Object.hasOwn() provides a deterministic, zero-dependency,
and tamper-resistant security boundary.