What prototype pollution is
Prototype pollution is a class of attack where a malicious package injects a property directly ontoObject.prototype or another shared prototype. Because every plain object in a JavaScript process inherits from Object.prototype, a property added there becomes visible on every object — including Sentinel’s internal permission maps.
The attack is straightforward:
Object.prototype.admin = true before Sentinel evaluates a capability check, a simple if (grants.admin) test will pass for any caller, regardless of what is actually in grants.
How Sentinel blocks it
Object.preventExtensions() is called on every built-in prototype before any third-party module loads:
preventExtensions blocks the addition of new properties to an object. After this call, any attempt to inject a new property onto a protected prototype will throw a TypeError in strict mode or silently fail in sloppy mode — either way, the injection does not take effect.
The preload runs as
"use strict", so a pollution attempt by a third-party package will throw a TypeError rather than silently failing.Why preventExtensions rather than freeze
Sentinel deliberately does not use Object.freeze(), which would also make existing properties non-writable. That would break legitimate libraries:
- Express assigns
router['BIND'] = fnon startup. - moment.js sets
proto.toString. - Many other packages extend prototypes at initialisation time.
preventExtensions is the correct surgical choice — it blocks new property additions while leaving existing writable properties unchanged.
What a malicious package sees
After Layer 0 runs, any third-party package that attempts prototype pollution receives a TypeError:Why this must run before any third-party module loads
Layer 0 is the very first thing in the preload IIFE. The guard is meaningful only if it installs before any third-party code has had a chance to run:- Node.js loads modules synchronously when
require()is called. The preload is injected via--requirebefore Node-RED starts, which means it runs before any user package’srequire()fires. - If any third-party package loaded first, it could call
Object.prototype.pollute = valuebeforepreventExtensionsran, and the guard would have no effect on that property.
Relationship to intrinsic capture
Prototype hardening and intrinsic capture are complementary defenses:preventExtensionsblocks adding new properties to a prototype.- Intrinsic capture defends against overwriting existing writable properties — it pins the original method before any third-party code can replace it.