A fast, dependency-light Python CLI for MITRE ATT&CK. Built for coding agents
to invoke through the Bash tool: clean JSON on stdout, useful errors on stderr,
predictable exit codes. No MCP daemon required.
Coding agents that need MITRE ATT&CK knowledge currently rely on MCP servers
(e.g. stoyky/mitre-attack-mcp,
MHaggis/mitre-attack-mcp). An MCP
brings infrastructure — a long-lived process, a transport, an MCP-aware host —
to what is fundamentally a read-only data lookup. This CLI is just a binary on
$PATH, easier to install (uvx mitre-attack-cli ...), easier to use in CI or
sandboxes, and equally agent-friendly: every command returns parseable JSON.
# One-shot via uvx (no install)
uvx mitre-attack-cli technique get T1059.001
# Or install globally
uv tool install mitre-attack-cli
mitre technique get T1059.001First invocation downloads the ATT&CK Enterprise STIX bundle (~25–45 MB) into
~/.cache/mitre-attack-cli/. Subsequent calls are local.
Default is compact JSON on stdout. Errors are JSON envelopes on stderr.
$ mitre technique get T1059.001
{"attack_id":"T1059.001","name":"PowerShell","tactics":["execution"],"platforms":["Windows"],"is_subtechnique":true}
$ mitre technique get T9999
# stdout: empty
# stderr:
{"error":"Not found: 'T9999' in enterprise@v19.0.","code":"NOT_FOUND","hint":"Try `mitre search T9999` or `mitre update`."}
# exit code: 3Three formats:
| Flag | Behavior |
|---|---|
--format json (default) |
Compact JSON. Single line per object, single array for lists. |
--format jsonl |
Newline-delimited JSON. Best for piping large lists to jq. |
--pretty (or --format pretty) |
Markdown for objects, table for lists. For humans. |
Exit codes: 0 success · 2 invalid usage · 3 not found · 4 missing data · 5 network · 1 other.
mitre technique get | list | subtechniques | parent | groups-using | software-using
| mitigations | detections | procedures | tactics
mitre tactic get | list | techniques
mitre group get | list | techniques | software | campaigns
mitre software get | list | groups-using | techniques | campaigns
mitre mitigation get | list | techniques
mitre campaign get | list | groups | techniques | software
mitre data-source get | list | components | techniques
mitre search <query> [--type … --regex --limit N]
mitre update [--domain … --all-domains --force]
mitre info # diagnostic JSON: cache path, active version, etc.
Global flags must precede the subcommand:
--domain {enterprise|mobile|ics} # default: enterprise
--attack-version v19.0 # default; or MITRE_ATTACK_VERSION env
--data-path PATH # pre-downloaded STIX dir/file (or MITRE_ATTACK_DATA_PATH)
--format {json|jsonl|pretty} # default: json
--pretty # human-readable
--fields a,b,c # whitelisted field projection
--full # all fields
--include-revoked # include revoked/deprecated objects
--offline # fail rather than download
--quiet # suppress stderr progress logs
# Lookup
mitre --pretty technique get T1059.001
mitre group get APT28 # alias resolves to G0007
# Relationships
mitre technique groups-using T1059.001
mitre technique mitigations T1059.001
mitre group techniques G0007
# Listings + filtering + field selection
mitre technique list --tactic lateral-movement --platform Windows
mitre --fields attack_id,name --format jsonl technique list
# Search
mitre search "kerberoasting" --limit 5
mitre search '^Power' --regex --type technique
# Diagnostics
mitre infoThree resolution paths, in order:
--data-path PATH(orMITRE_ATTACK_DATA_PATHenv). Accepts:- A direct file (
/path/to/enterprise-attack.json) - A flat directory containing
enterprise-attack.json - A
{version}/{domain}-attack.jsonlayout (e.g.~/cache/v19.0/enterprise-attack.json). This matches common ETL cache layouts, so pre-downloaded bundles are reused without re-downloading.
- A direct file (
- Local cache at
~/.cache/mitre-attack-cli/{version}/{domain}-attack.json(honorsXDG_CACHE_HOME). - Download from
https://raw.githubusercontent.com/mitre-attack/attack-stix-data/master/{domain}-attack/{domain}-attack-{version}.jsonunless--offline.
Refresh is explicit via mitre update. The pinned default version is v19.0;
override with --attack-version v15.1 or MITRE_ATTACK_VERSION=v15.1. The
version is the reproducibility contract — same CLI version + same --attack-version
returns the same answers.
Only the domain you request is downloaded. --domain mobile will fetch
mobile-attack lazily; enterprise and ics aren't touched.
Coding agents fire many short-lived mitre ... invocations. Loading the full
ATT&CK STIX bundle through stix2 every time would cost ~2.4 s per call. To
avoid that, the CLI maintains a compiled snapshot cache next to each STIX
file:
~/.cache/mitre-attack-cli/v19.0/
├── enterprise-attack.json # raw STIX (downloaded on demand)
└── enterprise-attack.cache.msgpack # compiled snapshot — used by every warm run
| Run | Wall clock | What happens |
|---|---|---|
| First (cold) | ~25–30 s | mitreattack-python loads the STIX bundle once, builds a msgpack snapshot with every relationship pre-resolved. |
| Warm | ~150–300 ms | mitreattack-python is never imported. The hot path is typer + httpx + msgpack + pure-dict lookups. |
| Cache invalid | ~25–30 s | Rebuilt automatically when the STIX file's mtime/size changes, when the CLI version changes, or when you run mitre update --force. |
The snapshot uses msgpack — a binary, data-only format. It lives in your own
writable cache dir even when the STIX bundle itself comes from --data-path,
so a read-only data path remains read-only.
uv tool install mitre-attack-cli # recommended
pipx install mitre-attack-cli # alternative
uvx mitre-attack-cli technique get T1059 # no install, one-shotA drop-in Claude Code skill lives at
skill/ that teaches Claude to invoke this CLI whenever the user
asks anything ATT&CK-shaped — technique/group/mitigation IDs, threat-actor
names, TTPs, log-to-framework questions — instead of recalling stale ATT&CK
content from training data.
In a quick-smoke evaluation across three realistic prompts the skill
version cited only verified IDs (100% accuracy) versus 69% for the
baseline that relied on recall. See skill/SKILL.md
for what it teaches.
To activate it locally, symlink the folder into your ~/.claude/skills/:
ln -s "$(pwd)/skill" ~/.claude/skills/mitre-expertClaude Code will pick it up on the next session.
git clone https://github.com/nitzpo/mitre-attack-cli
cd mitre-attack-cli
uv sync --all-groups
uv run python tests/fixtures/build_tiny_bundle.py # build test fixture
uv run pytest # 114 tests, ~1s
uv run ruff check . && uv run ruff format --check .The project uses uv for dependency management,
ruff for lint+format, ty
for type checking, and pytest for tests. CI runs on Python 3.12 and 3.13 across
Ubuntu and macOS.
Four layers, each independently testable:
cli/ # Typer commands, global flags, CliContext
└─ output/ # JSON/JSONL/markdown formatters, field projection, error envelope
└─ domain/ # ranked search, per-kind list/get/filter logic
└─ repo/ # AttackRepository wrapping mitreattack-python's MitreAttackData
└─ data/ # path resolution, httpx download with atomic-rename
The data layer is intentionally agnostic about ATT&CK schema specifics so future loaders (D3FEND, CAPEC, Atomic Red Team) can plug in without disturbing the CLI surface.
- No daemon, no MCP server, no transport. Just a binary on
$PATH. - JSON-by-default for agents;
--prettyfor humans. - Lazy per-domain download — only fetches what you ask for, not all three.
- Pre-downloaded path interop — points at any existing STIX bundle including
versioned
{version}/{domain}-attack.jsonlayouts. - Stable error envelope with documented codes (NOT_FOUND, MISSING_DATA, …) and useful hints in every error.
MIT. See LICENSE.