Parse and investigate beside Elasticsearch

Test VRL parsers and run investigations against Elasticsearch or OpenSearch.

Run it with Docker Compose
git clone https://github.com/AmdaaRaijen/weirflow-engine.git
cd weirflow-engine
docker compose -f deploy/docker-compose.yml --profile ui up

Then open localhost:3000. OpenSearch and sample logs come with it.

Source on GitHub

Raw line
<38>1 2026-10-28T09:15:02Z web-1.example.com sshd 4242 - - Failed password for alice from 203.0.113.7 port 51234 ssh2
VRL program
. = parse_syslog!(.message)
f = parse_regex!(.message, r'for (?P<user>\S+) from (?P<ip>\S+)')
.user = f.user
.src_ip = f.ip
del(.message)
Output
appname "sshd"
facility "auth"
hostname "web-1.example.com"
procid 4242
severity "info"
src_ip "203.0.113.7"
timestamp "2026-10-28T09:15:02Z"
user "alice"
version 1

One alarm, the whole attack chain

A workflow enriches the alarm you already have with what your other sources saw. A parsing step can tag each one with its MITRE ATT&CK id.

Correlating one alarm A whoami alarm run by www-data (T1033) is checked against four sources. Before it, the WAF holds the exploit request (T1190) and the web server host holds a web shell (T1505.003). After it, the host shows credential reads (T1552.001) and the auth log an SSH hop to db-1 (T1021.004). Alarm whoami as www-data T1033 WAF, before Exploit request T1190 Host, before Web shell written T1505.003 Host, after Credentials read T1552.001 Auth log, after SSH from web-1 to db-1 T1021.004
  1. Your existing alarm reaches the workflow, for example by webhook: whoami run by www-data (T1033). A decision checks its user and directory.
  2. Before it, the WAF holds the exploit request (T1190) and the host a web shell (T1505.003). A query searches each index in a window around the alarm.
  3. After it, the host shows credential reads (T1552.001) and the auth log an SSH hop to db-1 (T1021.004).
  4. The workflow joins the answers into one investigation tree, or into a verdict written by the AI analysis node, and sends the enriched alarm on.

Watch the attack unfold

A Telemetry node joins what your searches found into a process tree, in time order. It lists the gaps where a search found nothing.

The Telemetry page on a saved case: graph, tree and timeline.

Four screens, one engine

Screen
The workflow canvas showing the Failed SSH logins workflow: webhook, parsing, decision, query, telemetry and HTTP output nodes, with the folder sidebar.
Chain triggers, queries, parsing, lookups, decisions, loops and outputs. The engine checks a workflow before it runs live.
The weirflow Telemetry page on a saved case: the graph view with a time slider, and the details of the whoami command node that raised the alarm.
A Telemetry node joins what earlier nodes found into a graph, a tree and a timeline.
The weirflow parser simulator: a raw Apache log line, a one-line VRL program and the parsed output.
Run VRL on a JSON event or a raw line. Compile errors, runtime errors and timeouts look different.
The weirflow Edit Node dialog for a Query node, with the output preview showing a WAF event as JSON.
A Query node searches an index pattern; every node shows what it returned.

Logs in, searches out

Nothing is installed in Elasticsearch, OpenSearch or Kibana.

How weirflow connects Log sources and weirflow-ui reach weirflow-engine over webhook, TCP, UDP and /api/v1. The engine searches Elasticsearch or OpenSearch over HTTP and sends results to your other tools. Kibana stays as it is. Log sources webhook :8080 TCP UDP You, in the browser weirflow-ui /api/v1 weirflow-engine Parsing (VRL) Query Decision, loop, parallel Telemetry SQLite: folders, workflows, settings search over HTTP HTTP, TCP, UDP outputs Elasticsearch or OpenSearch Kibana stays as it is Your other tools
  1. Log sources reach the engine by webhook :8080, TCP or UDP.
  2. You use weirflow-ui in the browser; it calls /api/v1.
  3. The engine parses, queries, decides and loops, and keeps its data in SQLite.
  4. It searches Elasticsearch or OpenSearch over HTTP. Kibana stays as it is.
  5. Results go to your other tools by HTTP, TCP or UDP.

Every step is a node

A node takes the payload from the node before it and hands one on.

  1. Triggers

    Start a run by hand, or once per event from a webhook, TCP or UDP. A raw syslog or CEF line arrives as a message field.

  2. Query

    Searches an index pattern in a time window you set and passes the raw response on.

  3. Parsing

    Runs a VRL program that replaces the payload. CSV or TSV lookup tables can enrich it.

  4. Decision

    Tests one condition and sends the payload down its true or false path.

  5. Loop

    Runs the parsing and decision nodes after it once for each item of an array.

  6. Parallel and merge

    Run branches at the same time, then join their results.

  7. Threat intel

    Looks an indicator up in a threat intelligence API.

  8. AI analysis

    Has a model read the alarm and the joined logs and write an analysis.

  9. Telemetry

    Assembles fragments into an investigation tree and lists the gaps where a search found nothing.

  10. Outputs

    Send the payload on by HTTP, TCP or UDP, or index it into Elasticsearch or OpenSearch.

Don't know VRL? Let AI handle it

Describe what you need in plain words. It is optional and stays off until you add a provider.

  1. Write VRL parsers

    Describe the log in the Parsing node or the Parser page. The program is tested on your sample first and inserted only if it passes.

  2. Write queries

    Describe the search in the Query node and get Elasticsearch or OpenSearch query DSL.

  3. Answer questions

    The help chat on every page explains the screens and nodes.

  4. Triage alarms

    The AI analysis node reads the alarm and the joined logs. Its model is a setting; its key is an engine environment variable.

  5. Keep your key safe

    The provider key stays on the UI server, never in the browser.

A cluster for every folder

Folders nest. Each can name its own cluster or inherit one.

  1. This folder

    Its own URL, with a user and password or an API key, stored encrypted and never sent back.

  2. The nearest parent

    The first folder above it that names a cluster.

  3. The global setting

    Set once on the Settings page, or by environment variable.

  4. Nothing set

    The Query node stops with no elasticsearch connection configured for this workflow

Yours to keep and change

One HTTP API

curl -s http://127.0.0.1:8081/api/v1/vrl/run \
  -H 'Content-Type: application/json' \
  -d '{"program": ".user = \"alice\"", "event": {"message": "hello"}}'

{"ok":true,"output":{"message":"hello","user":"alice"},"durationMs":1.8}

The UI uses this same API, described by an OpenAPI document.

0BSD

Use it, change it, sell it. No attribution required.

One SQLite file

Folders, workflows, cases and settings live in DATA_DIR/engine.db.

New node types

Adding one to the engine takes three changes.

Run it beside your cluster

git clone https://github.com/AmdaaRaijen/weirflow-engine.git
cd weirflow-engine
docker compose -f deploy/docker-compose.yml --profile ui up
Nested workflow folders in the weirflow workflow page