# opseclint
[](https://github.com/Gerrrt/opseclint/actions/workflows/ci.yml)
**A detection-coverage analyzer for the command line.** Point it at a command,
a script, or a post-exploitation playbook and it statically resolves each action
to the [MITRE ATT&CK](https://attack.mitre.org/) 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?"**

## Who it's for
- **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 before
and after a test.
- **Red teams** operating under authorization who need to understand and report
the telemetry footprint of a playbook.
### Scope
opseclint describes **detectability** — the defensive signal an action
generates. It is not an evasion tool: it does not recommend "quieter"
alternatives or ways to defeat a detection. Absence of a finding means only that
nothing in the knowledge base matched — it is never a claim that an action is
stealthy.
## Install
```bash
cargo install --path .
# or, from a checkout:
cargo build --release # -> target/release/opseclint
```
## Usage
```bash
opseclint script.sh # analyze a file (Linux/auditd by default)
opseclint -c 'sudo cat /etc/shadow' # analyze a single command
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 --ci --threshold 70 # exit 1 if loudest action >= 70
```
### Real Sigma rules
By default, detection references in the seed KB are *representative*. Point
`--sigma` at a checkout of [SigmaHQ/sigma](https://github.com/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 — Linux-relevant rules only.
```bash
git clone --depth 1 https://github.com/SigmaHQ/sigma
opseclint examples/recon.sh --sigma sigma/rules
# detection Sigma: Access To Sudoers File (2c9d1141-... ) (high confidence)
```
The ruleset is read at runtime and never bundled, so the binary stays
self-contained. The parsed index is cached to disk (keyed by a fingerprint of
the ruleset directory), so repeat runs against a large checkout skip re-parsing
— the report notes `[cached]` on a hit. The cache lives in the system temp dir
(override with `OPSECLINT_CACHE_DIR`) and invalidates automatically when the
ruleset changes; `--no-sigma-cache` bypasses it.
### Platforms
Select the host telemetry model with `--platform` (default `linux-auditd`):
| `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`.
### GitHub code scanning
`--sarif` emits SARIF 2.1.0, so findings can surface in a repo's **Security →
Code scanning** tab. Each finding maps to a rule (tagged with its ATT&CK
technique and a `security-severity` derived from the detectability score) and is
anchored to the line of the analyzed file. See [`.github/workflows/ci.yml`](.github/workflows/ci.yml)
for a job that runs opseclint and uploads the results.
### Use as a GitHub Action
opseclint ships a composite action ([`action.yml`](action.yml)) that downloads
a released binary and analyzes a path in CI (Linux runners):
```yaml
- uses: Gerrrt/opseclint@v0.1.0
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.sarif
```
Inputs: `path` (required), `platform`, `version` (default `latest`),
`sarif-file`, `fail-threshold`, and `args` for anything else.
### CI gating
`--ci` makes opseclint a gate: it exits non-zero when the loudest modeled action
meets or exceeds `--threshold` (default 50), so a team can fail a pipeline on
tradecraft that exceeds an agreed noise budget.
```yaml
# .github/workflows/opsec.yml (example)
- run: opseclint playbooks/ --ci --threshold 75
```
### Example playbooks
The [`examples/`](examples/) directory has illustrative (benign-to-run)
playbooks to try it against:
```bash
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, cred access
opseclint examples/macos-postex.sh --platform macos-es # keychain, Gatekeeper, launchd
```
## Detectability score
A 0–100 estimate of how strongly an action surfaces in defensive telemetry
(higher = louder), bucketed as:
| 0–24 | LOW |
| 25–49 | MEDIUM |
| 50–74 | HIGH |
| 75–100 | CRITICAL |
## How it works
1. **Parser** (`parser.rs`) — quote-aware tokenizer that strips comments and
`VAR=value` assignments, splits a line on control operators (`; | & && ||`),
unwraps `sudo`/`env`/`nohup`/… and resolves each segment to a program +
arguments. The raw line is preserved so substring rules still match. A
preprocessing pass joins line continuations (trailing `\`, `|`, `&&`, `||`),
resolves commands hidden in `$(...)` / backtick substitutions, and handles
here-docs — a here-doc body is skipped as data unless it feeds a shell
interpreter, in which case each body line is analyzed at its real line.
2. **Knowledge base** (`data/knowledge*.json`) — one KB per platform (Linux,
Windows, macOS); 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.
3. **Analyzer** (`analyzer.rs`) — matches every action against the KB,
deduplicates per line, and ranks findings loudest-first.
4. **Report** (`report.rs`) — terminal or JSON output, plus the CI gate.
All KBs are embedded at compile time, so opseclint ships as a single static
binary with no runtime dependencies.
### Knowledge base schema
```json
{
"id": "shadow-read",
"raw_contains": "/etc/shadow",
"description": "Access to /etc/shadow — password hash exposure",
"techniques": [{ "id": "T1003.008", "name": "OS Credential Dumping: /etc/passwd and /etc/shadow" }],
"telemetry": ["openat() of /etc/shadow — high-signal auditd file watch"],
"detections": [{ "source": "Sigma", "rule": "...", "confidence": "high" }],
"noise": 85
}
```
An entry matches either by `command` (with optional `args_contains` /
`raw_contains` refinements) or by `raw_contains` alone. Adding coverage is a
data change, not a code change.
## Status & roadmap
`v0.1` seeds three platforms: **Linux / auditd** (~60 entries), **Windows /
Sysmon** (~60 entries, including Active Directory tradecraft — Kerberoasting,
DCSync, ticket attacks, BloodHound/PowerView recon, and lateral movement), and
**macOS / Endpoint Security** (~28 entries — keychain, Gatekeeper/SIP, TCC,
launchd persistence, AppleScript). Together they cover the most common
post-exploitation actions across discovery, credential access, execution,
persistence, defense evasion, and container escape. On the roadmap:
- Deepen each KB further and add more EDR-specific telemetry mappings.
- A `--format sarif` upload for Windows/macOS example runs in CI.
**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.
## License
MIT