VrittaIn development

Every log source, normalised. Even the ones with no parser.

Vritta identifies each stream, maps it to OCSF, and drafts a parser for anything new. A person approves it.

Interactive demoExplore the console

Demo

Dashboards

Event volume, what it is, and how well it parses.

Events17,100Over 30 days
Average rate0.01/sfrom 4 sources
Parse success100.0%of records with a spec
Unparsed1.2%200 records wait for a source

Events over time

By source, each event once at its latest normalization. Click a source to hide it.
023346770009/0309/1810/02

By source

Select a source to search its events

By OCSF class

What the events are.

Parse outcomes

Every raw record received, by what became of it.
Parsed 17,100 Unparsed 200Failed 0
030060090009/0309/1810/02
See the sample logs used in the walkthrough
Intake, tenant demoSynthetic records
  • Windows event JSON{"@timestamp":"2026-09-24T07:07:00.628Z","winlog":{"channel":"Security","provider_name":"Microsoft-Windows-Security-Auditing","event_id":4624,"computer_name":"WS-FIN-028.corp.example","event_data":{"TargetUserName":"maeve.nolan","IpAddress":"10.20.8.176","LogonType":"3"}},"message":"An account was successfully logged on."}
  • syslog RFC 3164<14>Sep 24 08:07:09 pa-edge-01 1,2026/09/24 08:07:09,013201001234,TRAFFIC,end,2561,2026/09/24 08:07:09,10.20.3.91,198.51.100.36,0.0.0.0,0.0.0.0,allow-web,,,ssl,vsys1,trust,untrust,ethernet1/2,ethernet1/1,log-fwd,2026/09/24 08:07:09,38217,1,54310,443,0,0,0x400000,tcp,allow,18333
  • syslog RFC 5424<38>1 2026-09-24T08:07:01.803+01:00 bastion-01 sshd 18177 - - Failed password for invalid user admin from 203.0.113.131 port 55281 ssh2
  • JSON{"uuid":"bacaa3bc-c19e","published":"2026-09-24T07:07:06.502Z","eventType":"user.session.start","actor":{"type":"User","alternateId":"niamh.kelly@corp.example"},"client":{"ipAddress":"10.20.7.11"},"outcome":{"result":"SUCCESS"}}
  • key=valuets=2026-09-24T08:11:47.717+01:00 app=authgw node=gw-02 evt=login_fail user=niamh.kelly@corp.example src=10.20.2.76 mfa=webauthn reason=bad_password session=cb1a806b1dfaaffc
  • CEFCEF:0|Imperva Inc.|SecureSphere|14.7|Signature violation|Signature violation|High|src=198.51.100.23 dst=10.20.3.14 dpt=443 act=blocked cs1=SQL injection
  • delimited2026-09-24 08:14:07,proxy-2,ALLOW,10.20.4.17,cdn.example.net,443,GET,200,5120
  • free text10.20.6.41 - - [24/Sep/2026:08:14:08 +0100] "GET /healthz HTTP/1.1" 200 2 "-" "kube-probe/1.30"
New stream, key=value, no parser yet

The problem

Parsers are written by hand.
And they break quietly.

Before any detection can happen, every log has to be identified, parsed and mapped to a common schema. That work is manual, and it is never finished.

  • A new source means someone finds or writes a parser, maps its fields and deploys it.
  • A vendor upgrade changes the format, and the source stops parsing without telling anyone.
  • An MSSP repeats all of it for every client.
The parser treadmill, illustratedThree clients, sixteen weeks. Each needs a parser written by hand before its events parse, loses parsing after a vendor upgrade with no alert, and needs another hand-written fix once someone notices.Client AClient A: parser written by hand, weeks 0 to 2Client A: vendor upgrade in week 7; 6% of events parsed and no alert until week 11Client A: fixed by hand, weeks 11 to 12vendor upgradeno alertnoticedClient BClient B: parser written by hand, weeks 0 to 3Client B: vendor upgrade in week 9; 0% of events parsed and no alert until week 14Client B: fixed by hand, weeks 14 to 15Client CClient C: parser written by hand, weeks 0 to 4Client C: vendor upgrade in week 6; 38% of events parsed and no alert until week 12Client C: fixed by hand, weeks 12 to 13week 0481216
Share of each client's events that parse, week by week. An illustration of the pattern, not measured data.

How it is different

Each model does one job.
The one it is good at.

Language models can read an unknown log, but they are too slow and too costly to run on every event, and too risky to emit code for a security pipeline.

Small classifiers are fast, but they do not say when they are guessing.

Vritta uses each where it is good, and puts plain code everywhere else.

  1. Every event

    Deterministic code

    The hot path, in Go. It decodes, parses with an approved spec, maps to OCSF and stores.

    840records

    Trusted with
    Every record, at full volume, the same way every time.
    Never asked to
    Guess. Patterns are RE2 only, with capped steps and a time budget per record, and a spec cannot reach the network or storage.
  2. Once per new stream

    Laya, a small calibrated model

    421 million parameters, on CPU. It answers one bounded question for each new stream signature, and the answer is cached.

    5streams classified

    Trusted with
    Which catalog source this is, with a probability that means what it says.
    Never asked to
    Force the closest parser. Below the threshold, or on “none of these”, the stream is marked unknown.
  3. Rarely

    A language model

    The bundled local model, or an endpoint you configure. Called only for a stream that nothing in the catalog explains.

    2drafts requested

    Trusted with
    Reading redacted samples and drafting a declarative spec, constrained to a JSON Schema.
    Never asked to
    Run code, see raw values, or activate anything. Every draft is replayed against real records, and a person approves it.

Counts are for the synthetic tenant in the walkthrough below: one cell per record, per stream classified, and per draft requested.

One record, followed through Vritta

A synthetic tenant with five streams. Every panel is computed from its records.

Intake

A collector sends a batch. Each record gets a tenant, a receive time and a stable ID, and is written raw before the batch is acknowledged.

The ID hashes the tenant, the file offset and the bytes, so a collector's retry is stored once.

  1. CollectorVector tails the log file
  2. Intaketenant demo, evt_0f9ec7592d208b33
  3. ClickHouseraw table, written first
  4. Acknowledgedonly after the write returns
ts=2026-09-24T08:11:47.717+01:00 app=authgw node=gw-02 evt=login_fail user=niamh.kelly@corp.example src=10.20.2.76 mfa=webauthn reason=bad_password session=cb1a806b1dfaaffc
received
2026-09-24T07:11:48.129Z
source
collector-01 /var/log/authgw/auth.log
state
stored raw, unparsed

Format sniffing

Nobody declares a format. The sniffer reads the bytes, and a collector's hint wins when there is one. Highlighted: what it keyed on.

  • Windows event JSON{"@timestamp":"2026-09-24T07:07:00.628Z","winlog":{"channel":"Security","provider_name":"Microsoft-Windows-Security-Auditing","event_id":4624,"computer_name":"WS-FIN-028.corp.example","event_data":{"TargetUserName":"maeve.nolan","IpAddress":"10.20.8.176","LogonType":"3"}},"message":"An account was successfully logged on."}JSON with a winlog object
  • syslog RFC 3164<14>Sep 24 08:07:09 pa-edge-01 1,2026/09/24 08:07:09,013201001234,TRAFFIC,end,2561,2026/09/24 08:07:09,10.20.3.91,198.51.100.36,0.0.0.0,0.0.0.0,allow-web,,,ssl,vsys1,trust,untrust,ethernet1/2,ethernet1/1,log-fwd,2026/09/24 08:07:09,38217,1,54310,443,0,0,0x400000,tcp,allow,18333priority, then a BSD timestamp
  • syslog RFC 5424<38>1 2026-09-24T08:07:01.803+01:00 bastion-01 sshd 18177 - - Failed password for invalid user admin from 203.0.113.131 port 55281 ssh2priority, then version 1
  • JSON{"uuid":"bacaa3bc-c19e","published":"2026-09-24T07:07:06.502Z","eventType":"user.session.start","actor":{"type":"User","alternateId":"niamh.kelly@corp.example"},"client":{"ipAddress":"10.20.7.11"},"outcome":{"result":"SUCCESS"}}parses as a JSON object
  • key=valuets=2026-09-24T08:11:47.717+01:00 app=authgw node=gw-02 evt=login_fail user=niamh.kelly@corp.example src=10.20.2.76 mfa=webauthn reason=bad_password session=cb1a806b1dfaaffckey= before most values
  • CEFCEF:0|Imperva Inc.|SecureSphere|14.7|Signature violation|Signature violation|High|src=198.51.100.23 dst=10.20.3.14 dpt=443 act=blocked cs1=SQL injectionstarts with the CEF header
  • delimited2026-09-24 08:14:07,proxy-2,ALLOW,10.20.4.17,cdn.example.net,443,GET,200,5120a steady comma count
  • free text10.20.6.41 - - [24/Sep/2026:08:14:08 +0100] "GET /healthz HTTP/1.1" 200 2 "-" "kube-probe/1.30"no structure found

Streams

Records group into streams by transport, format and a cheap discriminator. New message types become shape variants inside a stream instead of splitting it.

Each bar is one stream; its segments are shape variants. The unknown stream is buffering a sample: 200 records, stored raw and tagged unparsed meanwhile.

  1. Windows SecurityWindows event JSON, collector-01 beats:50442943 shapesknown
  2. Unknownkey=value, collector-01 /var/log/authgw/auth.log2003 shapessampling
  3. PAN-OS trafficsyslog RFC 3164, edge-vector syslog:udp/5141911 shapeknown
  4. Linux sshdsyslog RFC 5424, edge-vector syslog:tcp/65141072 shapesknown
  5. Okta System LogJSON, connector okta:system-log481 shapeknown

Classification

Once per stream, not once per event, a small calibrated model picks a catalog source or answers “none of these”.

Unknown. The top answer is “none of these” and no source clears the threshold, so Vritta does not force the closest parser onto it.

Sample model output, not computed on this page. For contrast, the Windows stream scored 0.99 and parsed at once.

  1. None of these0.64
  2. Okta System Log0.14
  3. Linux auth and sshd0.11
  4. Entra ID sign-in0.07

the 0.80 threshold

Drafting and validation

A language model drafts a declarative spec from redacted samples. Each draft is replayed against the real records, and failures go back, redacted, for another round.

Returned to the model, redacted

  • 14 records: timestamp matched no declared format, e.g. ts="2026-09-24 08:09:07"
  • 14 records: class lookup has no case for evt=mfa_timeout
  • device.hostname is the constant "host-fe47", a value seen in 92 samples; map it from a field

Draft 0.1 replayed, one cell per record

parsed (172)no timestamp format (14)no class for mfa_timeout (14)

Draft 0.2 replayed

parsed (186)parsed, time zone flagged (14)

Validation of two drafted specs against the 200 buffered records
CheckBarDraft 0.1Draft 0.2
Parsed≥ 95%86%100%
Timestamps read≥ 99%93%100%
Required OCSF attributesall100%100%
OCSF violations000
Sample values in the spec010
Meets the barNoYes

Approval

The draft meets the bar. It still does nothing until a person approves it. You are the analyst.

Proposal authgw spec 0.2

Pending. Nothing activates without a person.

Samples
200, redacted before drafting
Parsed
100%
Flagged
14 with no time zone, read as UTC
Left unmapped
session, kept in unmapped
{
  "source": "authgw",
  "version": "0.2-draft",
  "decoder": "kv",
  "timestamp": {
    "field": "ts",
    "formats": [
      "rfc3339",
      "local-datetime"
    ],
    "missing_zone": "flag"
  },
  "class_lookup": {
    "field": "evt",
    "cases": {
      "login_ok": {
        "class_uid": 3002,
        "activity_id": 1,
        "status_id": 1,
        "severity_id": 1
      },
      "login_fail": {
        "class_uid": 3002,
        "activity_id": 1,
        "status_id": 2,
        "severity_id": 3
      },
      "mfa_timeout": {
        "class_uid": 3002,
        "activity_id": 1,
        "status_id": 2,
        "severity_id": 2
      },
      "logout": {
        "class_uid": 3002,
        "activity_id": 2,
        "status_id": 1,
        "severity_id": 1
      }
    }
  },
  "map": {
    "metadata.product.name": "app",
    "device.hostname": "node",
    "user.email_addr": "user",
    "src_endpoint.ip": "src",
    "status_detail": "reason",
    "auth_factors": "mfa"
  }
}

What the model sees.
Placeholders, not your values.

Samples are redacted before they leave the pipeline. Keys, delimiters and timestamps survive, so the model can still read the structure. People, hosts, addresses and secrets do not.

Stored in your ClickHouse

ts
2026-09-24T08:11:47.717+01:00
app
authgw
node
gw-02
evt
login_fail
user
niamh.kelly@corp.example
src
10.20.2.76
mfa
webauthn
reason
bad_password
session
cb1a806b1dfaaffc
The record as stored, and as sent to the model: ts=2026-09-24T08:11:47.717+01:00 app=authgw node=host-fe47 evt=login_fail user=<email:12b0> src=88.226.114.173 mfa=webauthn reason=bad_password session=<redacted>
  • Placeholders come from a keyed hash with a secret generated at install, so they cannot be reversed by trying every address.
  • Drafts are validated against the unredacted samples, locally. Feedback to the model is redacted the same way.
  • Every payload sent to the model is logged, and you can read exactly what left.
  • The model is the bundled local one or an endpoint you configure. With none, Vritta still classifies, parses and stores; only drafting is off.

Catalog

Starts with the sources
most teams send first.

The planned v1 catalog. Each spec ships with fixture samples and expected output.

Windows

Sources
Windows Security, Sysmon
Formats
Windows event JSON
Arrives via
Winlogbeat through the edge Vector, or Fluent Bit
OCSF classes
Authentication, Account Change, Process Activity, Registry Key Activity

Linux

Sources
auth and sshd, auditd
Formats
syslog RFC 3164 and 5424, key=value
Arrives via
Vector or Fluent Bit, or syslog to the edge Vector
OCSF classes
Authentication, SSH Activity, Process Activity, File System Activity

Web servers

Sources
Apache, Nginx
Formats
free text access logs
Arrives via
Vector or Fluent Bit tailing files
OCSF classes
HTTP Activity

Firewalls

Sources
PAN-OS, FortiGate, Cisco ASA, pfSense
Formats
syslog with delimited or key=value payloads
Arrives via
syslog to the edge Vector
OCSF classes
Network Activity

Cloud and identity

Sources
AWS CloudTrail, Okta, Entra ID and Microsoft 365, Kubernetes audit
Formats
JSON
Arrives via
Built-in pull connectors; CloudTrail through Vector from S3 and SQS
OCSF classes
Authentication, API Activity

Network sensors

Sources
Zeek, Suricata
Formats
JSON, delimited
Arrives via
Vector or Fluent Bit tailing files
OCSF classes
Network Activity, DNS Activity, Detection Finding

Anything else goes through the proposal loop above. A spec approved for one client can be promoted to every client, with its fixtures redacted first.

Built for one node.
Held to these numbers.

v1 is measured against these targets before a pilot starts. None is measured yet: the load test and the first pilot confirm or correct them.

One Compose file on one VM, air-gapped if you need it. It sits between the collectors you run and the SIEM you keep.

Throughput
10,000events/ssustained for 24 hours on one node, no acknowledged event lost
Ingest to searchable
≤60sat p99
Search
≤5sa filtered search over 7 days of one tenant, at p95
Day-one coverage
≥80%of a pilot's volume normalised through catalog specs
Parse success
≥99%on catalog sources
Install
≤1hourfrom a fresh install to the first normalised event
Throughput on one nodeevents per second
10,000 for 24 h

The shaded band is the v1 envelope, 5,000 to 10,000 per second. At 10,000 a second that is 864,000,000 events a day.

Storage per dayGB, at 100 GB of raw input
Raw input100
On disk20–30

Raw and normalised events in ClickHouse, an estimate the load test confirms. Thirty days of retention is roughly 1 TB of NVMe.

New source to active parserminutes, median targets
With a GPU≤ 25
CPU onlytens

drafting and validation, the reviewer's decision, open-ended on CPU. Recommended: one 16 GB GPU on 8 vCPU and 32 GB of RAM.

Do our logs leave our network?

No. Vritta runs on your infrastructure and works with no outbound access. The only thing that can leave is a redacted sample sent to a model endpoint you configure, and every payload sent is logged.

Does it replace our SIEM?

No. It normalises events and forwards them to your SIEM through a webhook or S3-compatible storage. Named Splunk, Elastic and Security Lake forwarders are planned for the release after v1.

What is not in v1?

Detection rules and alerting on log content, case management and response, endpoint agents, and automatic activation of any parser.

What does it cost?

Pricing is not set yet. Tell us about your volume and tenants when you book a demo.

How far along is it?

Vritta is in development. The demo walks through the design and whatever is running at the time, and is where we talk about a pilot on your data.

Book a demo

Walk through it with us.
Then decide about a pilot.

Tell us about your team and the sources that give you the most trouble. We reply from info@artisai.ie to find a suitable time.

Prefer email? Write to info@artisai.ie with your name, company and team.

We reply to this address and use it for nothing else.

Your team

Optional. The sources you struggle with, the SIEM you forward to, how many tenants.

We use these details to reply and to arrange the demo. Privacy notice: who holds the data, why, for how long, and how to have it deleted.