A first look at Zeek’s new JavaScript support

The Zeek 6 release includes a very powerful new feature: the ability to script Zeek in JavaScript. In this post we’ll explain what this capability brings to Zeek, how it works internally, and where we see it going in the future.

Meet JavaScript, Zeek-style

Since its inception, Zeek has been designed around the extensibility and openness enabled by scripting. Until now, the only scripting language supported by Zeek has been its own domain-specific language: event-driven, strongly typed, and procedural in style. Out of the box, Zeek ships with a large set of scripts that drive the log production that brings most users to Zeek. Power users go deeper and develop their own scripts on top of the hundreds of event types that the Zeek core sends into its scripting layer, in response to the network traffic it observes.

With Zeek 6, many of Zeek’s scripting concepts become accessible from JavaScript. Zeek can now seamlessly load JavaScript code that handles Zeek events, adds hook handlers, and calls Zeek’s own script-layer functions. Consider the “hello world” example from the documentation:

// hello.js
zeek.on('zeek_init', () => {
let version = zeek.invoke('zeek_version');
console.log(`Hello, Zeek ${version}!`);

This is the JavaScript equivalent of the following Zeek script:

# hello.zeek
event zeek_init() {
    print fmt(“Hello, Zeek %s”, zeek_version());

To run it, simply mention JavaScript files where you’d otherwise use Zeek scripts:

$ zeek hello.js
Hello, Zeek 6.0.1

Zeek’s @load statements can likewise refer to JavaScript: just include the .js suffix:

@load hello.js

This means you can immediately use JavaScript in Zeek packages. All of this functionality exists alongside the existing Zeek scripting language and interpreter, and all of your existing code continues to function as before.

The JavaScript runtime is powered by Node.js, so you can run any JavaScript code built for its API, including NPM packages. This makes Zeek’s JavaScript support a natural vehicle for rapid prototyping and integrations with other infrastructure, such as web APIs, web servers, or storage backends like Redis. Needless to say, this is powerful stuff!

The Implementation

Behind the scenes, the JavaScript integration is just another Zeek plugin that ships with Zeek by default. Zeek plugins consist of compiled, native code that provides additional functionality to the Zeek core.

We did not change anything in Zeek’s core to enable JavaScript support. This is actually remarkable — how can an entire new scripting runtime ship in a plugin? A few things had to align for this to work:

  • Zeek’s plugin API lets developers hook into Zeek’s execution at a sufficient level of detail, including script loads, function calls, event loop processing, etc.
  • Zeek’s IOSource abstraction allows essentially anything to get hooked into Zeek that can be read from and written to. Normally, this means things like packet sources, log writer backends, and messaging I/O in Zeek clusters. Here, it means interaction with another event loop, because …
  • Node.js operates its own event loop, powered by libuv, which supports a file descriptor interface that we can hook up to Zeek as an IOSource. In essence, whenever Zeek asks the Node event loop to invoke something, or the Node event loop calls into Zeek, the plugin’s IOSource handles the exchange.

Given these, the building blocks for the new runtime are in place, and the plugin can focus on the glue code to make the integration work in practice: relaying function calls, data type translations, implementing attribute equivalents, etc.

The plugin exposes this glue to the JavaScript runtime via a zeek global object that provides the required API. You already saw this in action via the zeek.on() example earlier, enabling event handling. This is also where you go to invoke Zeek’s existing script-layer functions and to access its globals. For details regarding the type mapping between Zeek and JavaScript equivalents, please again refer to our documentation.

Next Steps

We stress that JavaScript support is currently experimental, and an extension — not a language substitute. There are aspects of the Zeek language that currently lack a JavaScript equivalent — for example, you cannot currently redefine types, and it’s tricky to build a Zeek package containing only JavaScript. Also, you saw that the implementation requires a translation step between the two runtimes, which implies overhead. Whether this matters in practice depends entirely on your use case.

We’re excited to enable the vast JavaScript developer community to engage directly with Zeek. At this point, we encourage everyone to explore JavaScript, ask questions, and share feedback in our community channels. Much of where we’ll take JavaScript in the upcoming release cycles depends on what you will try to do with it.

If you’re ready to dive in, please take a look at additional code examples, an example of a web API integration with the MISP platform, and an article by Simeon Miteff over on the Corelight blog, where he dives into the details of a Telegram integration and how to leverage TypeScript.

Closing Thoughts

We have hypothesized bringing other scripting languages to Zeek for a long time, with Seth setting eyes on JavaScript early on. However, Zeek’s JavaScript support would not have come together without a Corelight experiment that cracked the riddle around the plugin and IOSource APIs, as well as Node’s event loop. We’re thrilled to include the result of this work in the Zeek distribution.