Why this layer is needed
Module interception (Layer 1) gatesrequire() calls for dangerous modules. But several attack surfaces are not modules at all:
- The Express
httpAdminandhttpNodeinstances are plain objects available as properties of theREDAPI. process.envis a global object — there is norequire('process')call to intercept.- The Express router’s internal
stackarray is a plainArraythat can be mutated in-place after route registration guards pass.
Express routing method guards
Sentinel wraps the routing methods (use, get, post, put, delete, patch, options, head, all) on both the httpAdmin and httpNode Express instances. After Node-RED initialises, any call to these methods from a user package triggers a capability check:
http:adminis required to register routes or middleware onhttpAdmin.http:nodeis required to register routes or middleware onhttpNode.
process.env Proxy
After settings are loaded and the allowMap is populated, process.env is replaced with a Proxy:
process is a global object, not a module — there is no require() call to intercept. A Proxy on process.env is the only way to gate reads and writes at the language level.
The
process.env Proxy is installed after settings load, not at preload startup. Node-RED reads many process.env variables during its own boot sequence before any user package runs. Installing the Proxy at preload time would trigger capability checks against an empty allowMap, blocking Node-RED’s own initialisation.Router-stack Proxy
Express stores its middleware chain inapp._router.stack — a plain array. A malicious package with http:admin access could still inject middleware by mutating this array in-place after the route registration guard passes — for example by calling app._router.stack.splice(0, 0, rogueHandler) directly.
Sentinel wraps the array in a Proxy and intercepts the _router property assignment with a configurable: false setter:
set trap intercepts:
- Direct index writes (
stack[5] = handler) - Length truncation (
stack.length = 0) - Methods that call
[[Set]]internally:push,splice,sort,reverse,fill
deleteProperty trap intercepts delete stack[i] and remove-only splice calls.
In-place mutations are the only route that bypasses the use()/get()/etc. method wrappers, making this Proxy the necessary last layer for Express security.
configurable: false property locks
After installing a guarded accessor, Sentinel calls Object.defineProperty with configurable: false to prevent the descriptor from being replaced:
Module._load— prevents stripping the module interception hook.app._routeron Express instances — prevents an attacker from replacing the guarded router with an unguarded one.rt.stackon the router object — prevents the guarded stack accessor from being overridden.
Object.defineProperty call with a new descriptor can silently replace a previously installed getter or setter, undoing a guard. configurable: false makes such a call throw a TypeError, closing this bypass.