Skip to main content

JavaScript

Set lang: javascript (or js) on the Script processor to run a JavaScript body against each event.

- script:
lang: js
source: |
__e.user = __e.user.toLowerCase();

The engine is a sandboxed interpreter embedded in the Director. It is not Node.js: there is no module system, no file system, no network, and no timers.

Two Forms

The same engine compiles two different things, and the difference decides how the code is written.

FormUsed byGrammar
ScriptThe Script processor's sourceFull statements, loops and function declarations. Runs in strict mode. Fields are reached through __e explicitly. Nothing is returned.
ExpressionThe Script processor's filter, and Eval field valuesA single expression. Bare field names resolve against the event. The value is returned automatically.

A script body is wrapped in a function and invoked once per event, so a return statement is legal but its value is discarded. All output is written to the event.

A script reaches fields explicitly...

- script:
lang: js
source: |
if (__e.bytes > 0) {
__e.kilobytes = __e.bytes / 1024;
}

An expression does not, and needs no return...

- script:
lang: js
filter: severity > 3 && host.startsWith('web-')
source: |
__e.escalated = true;

The Event

__e is the event. It is a live handle, not a copy — an assignment reaches the event immediately, and there is no commit step.

OperationSyntax
Read a field__e.status
Read a field whose name contains a dot__e['source.ip']
Write a field__e.status = 'ok'
Delete a fielddelete __e.status, or __e.status = undefined
Test for a field__e.status !== undefined

Assigning undefined deletes the field rather than storing the value. Assigning null stores a null.

__e exposes the event's top-level keys. A field holding an object behaves like an ordinary JavaScript object, so __e.user.name = 'j' works and modifies the event, but the enclosing object must already exist — create it first where it might not.

A field that is not present reads as undefined, so a missing field never throws on read.

note

__e is the only name bound to the event. Scripts written for other platforms may use event or __pack; neither exists here.

Parameters

Values under params are available to the script as a params object:

- script:
lang: js
params:
threshold: 70
allowed: ["web-01", "web-02"]
source: |
__e.high = __e.score > params.threshold;
__e.known = params.allowed.includes(__e.host);

params is a fresh deep copy on every event, nested values included. A script may write to it, but those writes are discarded when the event finishes — params cannot carry a value from one event to the next. With no params block it is an empty object, never undefined, so reading a key that was never configured yields undefined rather than throwing.

Function Library

The engine ships a library of helper functions under a C namespace — network tests, text parsing, time formatting, masking, hashing, encoding and CSV lookups. Each group has its own page:

GroupCovers
NetworkC.Net — CIDR matching, address classification, Community ID
TextC.Text — entropy, hashing, XML and Windows Event parsing
TimeC.Time — parsing, formatting, time zones, partitioning
MaskingC.Mask — redaction, Luhn, digests, and C.Crypto.createHmac
EncodingC.Encode, C.Decode — Base64, gzip, deflate, hex, MIME, URI
LookupsC.Lookup and its CIDR, regex and case-insensitive variants
ContextC.Misc, C.vars, C.env, C.os, version values and logging

The C namespace is frozen. Assigning to it, or to any function inside it, has no effect.

Restrictions

Script bodies run in strict mode. The practical consequences:

  • Assigning to a name that was never declared throws. Declare locals with let or const.
  • The value of this at the top level is undefined, not a global object.

Beyond strict mode, the runtime is hardened so that one event's script cannot affect another's:

  • New global variables cannot be created. An attempt is silently ignored.
  • globalThis is undefined.
  • The prototypes of the built-in types — Object, Array, String, Number, Function, RegExp, Date, Error and the rest — are frozen, so they cannot be extended or patched.
  • There is no require, no import, and no console. Use debug() or C.log() to emit a message.

Math, JSON, Date, RegExp, Map, Set, Promise, Proxy and BigInt are all available and behave normally. Regular expressions use JavaScript syntax and semantics.

Limits

A single script gets one second of wall-clock time per event. Exceeding it fails the processor with a timeout error. The budget is fixed and cannot be raised from the pipeline configuration.

There is no cap on allocation or iteration count — the time budget is the only bound, so a script that builds an unbounded structure runs until the second expires.

Scripts hold no state between events. Each event gets a fresh invocation, and because new globals cannot be created there is no place to keep a running total. Use the Cache Set and Cache Get processors where a value has to survive across events.

Failure Behavior

A script that throws stops at the point of the throw. Fields it already modified stay modified — writes reach the event as they happen, so there is no rollback.

The event itself continues down the pipeline. The processor records the failure in the _ingest.on_failure_* fields and runs its on_failure chain; setting ignore_failure: true suppresses both and lets the partially modified event pass silently.

Where a script performs several related writes, guard the whole body so a failure cannot leave the event half-updated:

Validate before writing anything...

- script:
lang: js
source: |
const raw = __e.payload;
if (typeof raw !== 'string') { return; }
let parsed;
try {
parsed = JSON.parse(raw);
} catch (err) {
__e.parse_error = String(err);
return;
}
__e.user = parsed.user;
__e.action = parsed.action;

Compilation

Each distinct source string is compiled once per pipeline and reused for every event that reaches it. Two processors in the same pipeline with byte-identical source share the compiled result.

The cache is per-pipeline, not process-wide, so the same script used in two pipelines is compiled twice. This is where the two engines differ: the Go engine's compiled scripts are shared across every pipeline in the process.