Skip to content
suncalPublic

About

Entity-resolved security intelligence console: 5 log formats → one graph → ATT&CK kill chains. Pure Python stdlib, zero dependencies.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

ARGUS

Live demo License: MIT Stars

screenshot

Entity-resolved security intelligence console. Ingests raw telemetry from five different log formats, resolves the entities hiding inside them into one graph, detects with rules and behavioural analytics, reconstructs incidents as readable ATT&CK kill chains, and keeps every claim one click from the raw log line that produced it.

Pure Python 3.9+ standard library. No pip install, no services, no network calls.

python3 -m argus demo --serve       # generate a sample intrusion, analyse it, open the console

Then open http://127.0.0.1:8799.


The problem it addresses

A SIEM shows you alerts. Each alert knows almost nothing:

  • a Suricata alert knows 10.20.14.42 → 185.243.115.84:443
  • a Sysmon event knows WS-0142 / svchost.exe / alice
  • an sshd line knows deploy from 45.132.192.77
  • a CloudTrail record knows arn:aws:iam::…:user/alice / StopLogging

Nothing in that pile says these are the same intrusion. Assembling them is the job analysts spend their evenings on. ARGUS does the assembly.

On the shipped demo dataset: 8,672 raw records → 65 findings → 1 incident spanning 10 ATT&CK tactics, with a written reconstruction of what happened.


How it works

 logs ──▶ ingest ──▶ resolve ──▶ detect ──▶ correlate ──▶ score ──▶ console
          adapters   entities    rules +    incidents    risk      graph /
                     + graph     analytics  + kill       propaga-  timeline /
                                            chains       tion      hunt / paths

1. Ingest — five real formats, auto-detected

Adapter Handles
zeek conn / dns / http / ssl / notice, TSV and JSON-lines
suricata EVE JSON — alert, flow, dns, http, tls
sysmon Windows JSON — Sysmon 1/3/10/11/12-13/22 and Security 4624/4625/4672/4688/4698/7045/1102
authlog Linux auth.log / secure — sshd, sudo, PAM, useradd, groupadd
cloudtrail AWS CloudTrail, both the {"Records":[…]} envelope and NDJSON

Point it at a directory of mixed logs; each file is sniffed and routed. .gz is handled transparently. Everything normalises into one wide ECS-shaped event table, which is what makes cross-source correlation possible at all.

2. Resolve — the part that does the real work

Three moves turn disconnected records into one graph:

  • IP ↔ host binding. A Sysmon network event is emitted by the host that owns the source IP, so that pairing is authoritative. Internal IPs then collapse into their host entity — without this you get two disconnected populations (network nodes and endpoint nodes) that never touch. Hosts with no endpoint agent are covered from the asset inventory (assets.json).
  • Principal normalisation. CORP\Alice, alice@corp.local and alice become one user entity; the domain is kept as an attribute, not part of the key.
  • Resolution chain. DNS answers link domains to the external IPs they resolve to, so a flow to a bare address can be attributed back to the name.

Every entity/event association is written to an observations table. That table is the provenance guarantee.

3. Detect — two engines

41 declarative rules across Windows, Linux, network and cloud, each mapped to an ATT&CK technique. Rules are plain data (Python or JSON), with a Sigma-like condition grammar: all/any/none combinators over leaf predicates (eq, in, contains, regex, gt, startswith, …), reaching into the JSON sidecar with extra.granted_access. Every rule aggregates — one burst of 46 brute-force packets is one finding that says "46 times", not 46 findings.

10 behavioural analytics for what no signature describes:

Analytic What it measures
beaconing Inter-arrival regularity via MAD, not stdev — a single missed check-in destroys a stdev score but leaves MAD intact. Tolerates ±35% implant jitter. Collapses duplicate observations when two sensors see the same connection.
dns_tunnel Subdomain cardinality × Shannon entropy per label × unusual qtype ratio
rare_process Fleet prevalence, with system-directory binaries explicitly down-weighted (low prevalence there says more about the estate than the binary)
credential_attacks Failure clustering by source, spray vs guessing, and — the part that matters — whether anything succeeded afterwards
lateral_movement One account, several hosts, remote logon types, inside a window
exfil_volume Outbound volume against the estate's own p99, not a fixed threshold, weighted by how rare the destination is
staging_then_egress Archive written, then a large upload from the same host within hours — either half is weak, the sequence is the signal
first_time_access Account/host pairs with no precedent in the baseline half of the data
impossible_travel One credential in two countries too close in time (only runs when geo data is actually supplied)
offhours_privileged Low-severity supporting context that correlation pulls into real incidents

4. Correlate — findings into stories

Findings that share identity-bearing entities inside a 48-hour window are clustered, then ordered along the ATT&CK kill chain into a written narrative.

Two design decisions carry this stage:

  • Only substantive findings merge clusters. A weak observation cannot be the sole bridge welding two unrelated intrusions together; it attaches to the cluster it best matches instead.
  • Breadth is scored, not just severity. Six alerts of one detection firing repeatedly is a noisy rule. Six findings spanning initial-access → execution → credential-access → lateral-movement → collection → exfiltration is a live intrusion, and it ranks above the former even when every individual severity is lower.

5. Score — risk propagation and attack paths

Compromise is contagious, so risk is seeded from findings and diffused across the typed edge graph with per-edge-type conductance (authenticated_to ≈ 0.95, resolved_to ≈ 0.15) and damping.

Attack paths are modelled the way an operator moves, not the way packets flow: from a compromised host you harvest the credentials of accounts that authenticated there, and with those accounts you reach every host they can authenticate to. Alternating host → user → host hops is what surfaces the non-obvious two-step route from a phished laptop to a domain controller.


The console

Five views over the same graph:

  • Link graph — force-directed canvas, colour by entity type, radius by risk, amber ring for crown jewels. Click to open, double-click to expand, drag, zoom.
  • Timeline — stacked event volume by kind, with findings plotted on their own severity lane below. Click a marker to open the finding.
  • Hunt — the query language, with stats bar charts.
  • ATT&CK — technique coverage matrix; lit cells are what this dataset evidenced, dim cells are what ARGUS can detect but did not see.
  • Attack paths — routes to crown jewels, ranked by where the attacker already is.

The detail pane always ends in raw log lines with their file:line reference.


Hunt query language (AQL)

Compiles to SQL, so it stays fast on millions of rows.

kind=process.create cmdline~"-enc "
kind=auth.login outcome=failure | stats count by src_ip
dst_port in [3389,22,445] src_ip:external
kind=network.flow bytes_out>50mb dst_ip:external | sort bytes_out desc | limit 50
process=powershell.exe AND NOT parent_process in [explorer.exe,cmd.exe]
domain~"\.(top|xyz|ru)$" | stats count by domain
"vssadmin"
Syntax Meaning
field=value exact, case-insensitive; * and ? wildcards
field~"text" substring, auto-upgraded to regex if it has metacharacters
field>50mb numeric with k/mb/gb/tb suffixes
field in [a,b] set membership (also not in)
src_ip:external type predicate — external, internal, exists, empty
AND OR NOT ( ) boolean logic; adjacency means AND
"bare text" free-text across message, cmdline, url, host, raw
| stats count by host aggregate
| sort f desc | limit n | fields a,b order, cap, project

CLI

python3 -m argus demo [--serve]           # synthetic intrusion, full pipeline
python3 -m argus ingest /var/log/zeek/    # load real logs (auto-detects format)
python3 -m argus analyze                  # re-run resolution/detection/correlation
python3 -m argus serve --port 8799        # web console
python3 -m argus hunt 'kind=auth.login outcome=failure | stats count by src_ip'
python3 -m argus incidents -v             # narratives on the terminal
python3 -m argus report --out brief.html  # standalone HTML briefing
python3 -m argus stats

Global flags: --db <path> (default argus.db, or $ARGUS_DB), --assets <file>.

Bring your own logs

python3 -m argus --db corp.db --assets assets.json ingest /path/to/logs/
python3 -m argus --db corp.db serve

assets.json

Describes your estate. Everything is optional; sensible defaults apply.

{
  "crown_jewels": ["*dc*", "*sql*", "*vault*", "prod-*"],
  "admin_users":  ["*admin*", "root", "svc-*"],
  "allow_domains": ["microsoft.com", "github.com"],
  "hosts":  { "lnx-app02": "10.20.2.12" },
  "geoip":  { "45.132.192.77": "RU", "203.0.113.0/24": "US" }
}

hosts supplies IP↔host bindings for machines with no endpoint agent. geoip is how impossible-travel gets geography — ARGUS ships no GeoIP database, and the analytic simply does not run rather than guessing.

Custom detection rules

python3 -m argus analyze --rules my_rules.json
[{
  "id": "custom.suspicious_svc",
  "title": "Service installed pointing at a UNC path",
  "severity": 80, "confidence": 0.8, "technique": "T1543.003",
  "kinds": ["service.install"],
  "where": { "all": [ { "field": "file_path", "startswith": "\\\\" } ] }
}]

Tests

python3 -m unittest discover -s tests -v

47 tests. The analytics tests assert on behaviour rather than implementation — a jittered beacon is caught, a wildly-jittered legitimate update checker is not, a beacon survives being observed twice by two sensors, and an end-to-end test drives the whole pipeline over the generated intrusion and asserts that all ten kill-chain detections fire and that every finding traces back to raw events.


What this is and is not

It is a working analysis engine with real detection math, honest provenance and a dense operator UI, that runs on real Zeek/Suricata/Sysmon/syslog/CloudTrail output today.

It is not a production SIEM. There is no streaming ingest, no authentication, no multi-tenancy, no retention policy, and it is single-node SQLite — comfortable into the low millions of events, not billions. Bind it to localhost; it has no access control of any kind.

Detection content is a curated ATT&CK subset covering what the shipped rules and analytics emit, not the full 600+ technique corpus. Findings are investigative leads, not verdicts — which is exactly why every one of them is traceable to the raw record.

MITRE ATT&CK® is a registered trademark of The MITRE Corporation.


Layout

argus/
  model.py       unified event schema, entities, edges, findings, incidents
  store.py       SQLite store (events, entities, observations, edges, …)
  util.py        IP/name normalisation, robust statistics, timestamp parsing
  attck.py       curated ATT&CK catalog + coverage matrix
  ingest/        network.py (zeek, suricata) · host.py (sysmon, authlog) · cloud.py
  resolve.py     entity resolution + graph construction
  enrich.py      optional GeoIP / ASN enrichment from the assets file
  detect/        rules.py (engine + 41 rules) · analytics.py (10 analytics)
  correlate.py   incident clustering, kill-chain narrative, ATT&CK coverage
  graph.py       risk propagation, attack paths, blast radius, subgraphs
  hunt.py        AQL parser and compiler
  pipeline.py    ingest → resolve → detect → correlate → score
  server.py      stdlib HTTP API + static console
  report.py      standalone HTML briefing
  cli.py         command line
  scenarios/     synthetic multi-stage intrusion generator
web/             index.html · style.css · app.js · graph.js
tests/           test_argus.py

If this is useful to you, a ⭐ on the repo helps other people find it. Issues and pull requests are welcome.

Built by Priyankar "Sunny" Chakraborty · everbuiltstudio.com

About

Entity-resolved security intelligence console: 5 log formats → one graph → ATT&CK kill chains. Pure Python stdlib, zero dependencies.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages