zen-internals-node: The Firewall Hidden Inside V8

zen-internals-node turns eval() and new Function() into a policy check, so Node.js code can be stopped at the instant it tries to become executable.

11 min read • View on GitHub • More from AikidoSec

A vast engine room built from gears and rails has a narrow gate installed exactly where a strip of source text enters the machinery. The image explains how the addon intercepts code generation before execution, instead of inspecting it after the fact.
This repo does not watch JavaScript after it runs. It stops it at the boundary where text becomes code.
Key Takeaways

The novelty is not another denylist. It is the location of the veto. Most tools see requests, payloads, or stack traces. zen-internals-node sees the moment a string is about to become code, which makes eval() and new Function() enforceable in a way normal middleware never can.

A security hook at the exact line between text and execution

Aikido Security frames Zen as an embedded firewall inside the app, not a box at the edge. That matters because application context changes the security question. Instead of asking what a request looks like, the runtime can ask what the code is about to do, and who it is allowed to do it for.

Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks.

Aikido Security, Organization · AikidoSec/firewall-node

How the addon blocks code generation

Inside src/binding.cc, a V8 callback intercepts code generation from strings. The native layer forwards the source text into a JavaScript policy function, then interprets the return value. If the policy returns a string, the addon treats that as a block signal, sets a custom error message, and tells V8 to abort execution.

v8::Local<v8::Value> v8_value;
std::memcpy(&v8_value, &argv[0], sizeof(v8_value));

That memcpy bridge is the story in miniature. N-API is the friendly face, but the enforcement path cuts past the abstraction boundary into V8's internals. It works because the addon needs control at a layer ordinary addons do not reach, and it is fragile for exactly the same reason.

The decision happens before code exists, not after it runs.

That synchrony is the point. The policy cannot drift into a queue or a background task, because the JavaScript engine is waiting. The flip side is obvious: if the callback is slow or recursive, it can stall the isolate and take the event loop down with it.

Two massive machine housings are joined by a thin bridge pin under tension, with one side built for stability and the other side built for raw access. The image explains the brittle jump from N-API convenience to V8 internals.
The surface stays stable, but the enforcement path cuts through the engine room.

Why the repo is mostly delivery plumbing

The source looks minimal, but index.js and binding.gyp do a lot of hidden work. The loader skips Node 17, picks a prebuilt binary by platform and architecture, and keeps production users away from local compiler setup. binding.gyp locks the build to C++20, which is another sign that the real product is not just the hook, it is the ability to ship that hook reliably across environments.

LayerWhat it can seeStrengthTrade-off
Edge WAFRequests on the wireEasy to deploy broadlySees patterns, not application intent
App middlewareInputs after routingLives close to your codeUsually too late to stop code generation
zen-internals-nodeStrings at the V8 compile boundaryCan veto eval and new Function before executionNative, version-sensitive, and operationally heavier

What this beats, and what it does not

Against an edge WAF, this is deeper and sharper. Against ordinary Express-style middleware, it is earlier and more decisive. But the cost is real: native maintenance, version skew, and a very small margin for implementation mistakes. The repo is a reminder that the most powerful security controls often live at the highest operational risk.

Runtime Application Self Protection (RASP) takes a different approach which doesn’t rely on written rules but actively monitors application behaviour through native integrations, leading to an increased protection for 0-Days.

Timo Kössler, Author · Zero day attack prevention