Skip to content

M7: CLI, config, starter rule pack, demo, README - #7

Merged
dylanpatriarchi merged 1 commit into
feat/m6-correlationfrom
feat/m7-cli-rules-demo
Aug 1, 2026
Merged

dylanpatriarchi merged 1 commit into
feat/m6-correlationfrom
feat/m7-cli-rules-demo

Conversation

@dylanpatriarchi

Copy link
Copy Markdown
Owner

Milestone 7 — CLI, config, starter rule pack, demo, README

Stack: 6 of 6 · base feat/m6-correlation (PR #6) · final PR

The pieces that turn a library into something someone can run.

config.nim

YAML parsed into plain data, so it can be validated and tested without starting anything. Every setting has a defensible default — argus run works with no config at all.

A misspelled key is rejected, never ignored: a silently-dropped setting is the worst kind of config bug, because everything looks configured and nothing is. The default watch list is four explicit paths rather than the whole filesystem — cheaper, and less of a privacy problem than collecting everything and filtering later.

runner.nim

The only module that knows all five pipeline stages at once, and it does nothing but assembly.

  • A bad rule pack refuses to start rather than running with silently fewer detections.
  • A failing sink is counted and reported but never stops detection — losing the ability to write an alert file is not a reason to stop noticing things.
  • Shutdown stops collectors first, then drains, so a clean exit reports what it collected instead of discarding it.

CLI

doctor, config, run, alongside the existing rules / replay / check-trace / demo. run takes --config, --rules, --duration, --json; handles Ctrl-C by flushing rather than dropping; and prints what is running before the first event, so a user learns a collector failed while there's still time to act.

rules/ — 22 starter rules

Process, file, network and correlation, every one with a MITRE ATT&CK technique. Deliberately simple, and the comments say where they're weak: several exclude package managers, which is also the obvious way to evade them; correlation joins on pid, so a chain crossing a process boundary won't link.

scripts/demo.sh

Writes a script into its own temp directory, runs it (prints one line), removes the directory. Nothing outside it is touched, no privilege needed. On a host without /proc it says so and stops at the replay step rather than pretending. CI runs it end to end on Linux.

End to end

Replaying the sample trace against the shipped pack:

[HIGH]     ARG-PROC-001  Interactive shell spawned by a web server
[HIGH]     ARG-FILE-001  Cron entry created or modified
[HIGH]     ARG-NET-001   Outbound connection from a shell
[MEDIUM]   ARG-NET-002   Outbound connection to an uncommon port
[MEDIUM]   ARG-NET-003   Connection to a documentation address range
[CRITICAL] ARG-CORR-001  Web server spawned a shell, which then connected out

That last line is the whole project: the correlation rule links the shell spawn to the outbound connection from the same pid, and neither half alone would have earned critical.

The same trace contains three benign apt writes to /etc. Nothing fires on them, and a test asserts it — noise is how a real deployment gets muted.

Verification

136 new tests, 721 total, all green. nimble lint clean. Every commit in this stack was verified independently in a worktree (477 → 594 → 639 → 721).

Two of those tests are worth calling out:

  • The shipped rule pack is held to the same standard as the engine — every rule loads, is reachable, carries a resolvable ATT&CK URL, and detects what it claims.
  • argus.example.yaml must parse back to exactly defaultConfig(), which already caught it drifting on one line.

The pieces that turn a library into something someone can actually run.

config.nim — YAML configuration, parsed into plain data so it can be
validated and tested without starting anything. Every setting has a
defensible default, so 'argus run' works with no config at all. A misspelled
key is REJECTED, never ignored: a silently-dropped setting is the worst kind
of config bug, because everything looks configured and nothing is. The
default watch list is four explicit paths rather than the whole filesystem —
cheaper, and less of a privacy problem than collecting everything and
filtering later.

runner.nim — the only module that knows all five pipeline stages at once,
and it does nothing but assembly. A bad rule pack refuses to start rather
than running with silently fewer detections. A failing sink is counted and
reported but never stops detection: losing the ability to write an alert
file is not a reason to stop noticing things. Shutdown stops collectors
first and then drains, so a clean exit reports what it collected instead of
discarding it.

CLI — doctor, config, run, plus the existing rules/replay/check-trace/demo.
'run' takes --config, --rules, --duration and --json, handles Ctrl-C by
flushing rather than dropping, and prints what is running BEFORE the first
event, so a user learns a collector failed while there is still time to act.

rules/ — 22 starter rules across process, file, network and correlation,
every one with a MITRE ATT&CK technique. They are deliberately simple and
the comments say where they are weak: several exclude package managers,
which is also the obvious way to evade them; correlation joins on pid, so a
chain that crosses a process boundary will not link.

scripts/demo.sh — writes a script into its own temp directory, runs it (it
prints one line), removes the directory. Nothing outside it is touched, no
privilege is needed, and on a host without /proc it says so and stops at the
replay step rather than pretending. CI runs it end to end on Linux.

Verified end to end: replaying the sample trace against the shipped pack
fires five single-event rules plus ARG-CORR-001, which links the shell spawn
to the outbound connection from the same pid — the whole pipeline in one
line of output. The three benign apt writes to /etc in that trace fire
nothing, and a test asserts it, because noise is how a real deployment gets
muted.

136 new tests, 721 total. Among them: the shipped rule pack is held to the
same standard as the engine (every rule loads, is reachable, carries a
resolvable ATT&CK URL, and detects what it claims), and argus.example.yaml
must parse back to exactly defaultConfig() — which already caught it
drifting on one line.
@dylanpatriarchi
dylanpatriarchi force-pushed the feat/m7-cli-rules-demo branch from eb28382 to 52a5da6 Compare August 1, 2026 14:36
@dylanpatriarchi
dylanpatriarchi merged commit d0c5c73 into feat/m6-correlation Aug 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant