Threat Model — GAUNTLET
Scope
1.x: a local Python CLI that (a) downloads pinned public datasets (ATT&CK STIX, SigmaHQ rules, OTRF recorded Windows logs, Splunk attack_data Windows/Linux logs, Atomic Red Team index) with SHA-256 verification, (b) parses JSON, XML-event and raw auditd logs and replays them through Sigma rules, and (c) keeps the offline synthetic-telemetry mode. The package never executes techniques; the only network I/O is scripts/download_data.py. Separately, the live-telemetry CI workflow runs an allowlist of benign discovery commands on an ephemeral GitHub runner (ADR 0005).
Assets
- Integrity of coverage results. A falsely green report creates false confidence.
- Detection rule set and baselines, which are the regression-gate inputs.
- In future: the isolated range and any host that runs it.
Trust boundaries
- Rule files (
gauntlet/data/rules/*.json,--rules, SigmaHQ YAML) are semi-trusted input. YAML is parsed withyaml.safe_loadonly. - Downloaded datasets are third-party input: pinned refs + SHA-256 manifest (
scripts/checksums.sha256). - Baseline JSON (
--baseline) is semi-trusted input. - Live CI emulation:
scripts/live_emulate.pyon an ephemeral GitHub-hosted runner, allowlist only. - Parsed telemetry (Splunk XML/auditd, live audit logs): untrusted text, parsed with bounded regexes and a 1 MiB line cap.
- Operator-run emulation (outside GAUNTLET):
gauntlet manifestoutput executed in an isolated range. This is the critical boundary.
Threats and mitigations
| # | Threat | Mitigation | Residual / TODO |
|---|---|---|---|
| T1 | Project misused as attack tooling | The package only replays recorded logs or simulates. Steps are inert templates with <SIMULATED> placeholders, .invalid domains and lab IPs, enforced by a test |
Any future executor must be range-bound. Code review required |
| T2 | Emulation escapes the lab (future) | N/A, nothing executes | Internal-only network, no egress, cap_drop ALL, read-only containers, a kill-switch, and egress verification tests (Grade D) |
| T3 | ReDoS: malicious rule |re, or hostile log lines |
Rules come from a pinned SigmaHQ release; log parsers use bounded, non-backtracking patterns, a 1 MiB line cap and an argc cap, with a timing regression test | Rule regexes run on Python re without a timeout |
| T4 | Tampered baseline hides a regression | Baselines are version-controlled and diffs are reviewed in PRs | TODO: sign or hash baselines |
| T5 | Over-trust in coverage numbers | v0.2 measures coverage on real recorded attack telemetry (OTRF), reports family and exact-ID coverage, off-target alert burden, and limitations; simulation output stays labelled SIMULATED | Recordings are one lab, mostly 2019-2020 tradecraft; not a production FP rate |
| T6 | Rule that fires on everything inflates coverage | False positives on benign noise are counted and reported | TODO: fail CI above an FP threshold |
| T7 | Unknown rule syntax silently ignored | Unsupported conditions, modifiers and logsources raise UnsupportedRule and are counted in the report |
Aggregation/correlation rules not evaluated |
| T8 | Supply chain | One runtime dependency (PyYAML, safe loader); matplotlib optional. Actions pinned by commit SHA, Dependabot (actions, pip, docker), base images pinned by digest, pip-audit and gitleaks in CI, release provenance attestation | Python dependencies are not hash-locked |
| T9 | Poisoned / swapped dataset or rule release | Immutable refs (commit SHA / release tag / pinned ATT&CK commit); downloads are hashed before install and discarded on mismatch, existing files with a wrong digest are re-fetched, paths outside the data dir are refused, Splunk files are checked against git-LFS oids | Checksums are TOFU for OTRF/ATT&CK; bench does not re-verify files at run time |
| T10 | Recorded attack logs mistaken for malware by AV | Logs are JSON text, not binaries; unreadable (quarantined) files are skipped and reported | Users may need an AV exclusion for the data dir |
| T11 | Allowlist tampering turns the live CI job into real attack execution | Allowlist is a fixed tuple in scripts/live_emulate.py, a unit test pins it to approved read-only binaries and rejects forbidden ones, CODEOWNERS review, job runs unprivileged on an ephemeral runner with contents: read and no secrets |
A maintainer could still change both test and list |