STIX 2.1 mapping best practices
STIX 2.1 is permissive: the same observation can be modeled three different ways, all valid. That flexibility is also why so many STIX bundles are awkward to consume. This guide collects the conventions that hold up across CTI sharing, MISP imports, and ThreatGraph visualizations.
1. Always set spec_version
Top-level bundles can omit it, but every SDO and SRO should declare "spec_version": "2.1". Validators warn when it's missing; downstream tools occasionally refuse the object outright. Set it once, never debate.
2. Indicator patterns: simple, parseable, single-purpose
One indicator, one observable. Combining an IP and a hash in a single pattern with AND looks compact but defeats correlation downstream. Use a relationship instead.
// Good [domain-name:value = 'c2.example.test'] [file:hashes.'SHA-256' = 'aaaa1111…'] // Avoid [file:hashes.MD5 = '...' AND ipv4-addr:value = '...']
3. Use the right relationship type
The OASIS spec defines a fixed list of common values. Sticking to them keeps your bundle interoperable.
| From | relationship_type | To |
|---|---|---|
| indicator | indicates | malware / tool / attack-pattern / threat-actor / intrusion-set / campaign |
| threat-actor / campaign / intrusion-set | uses | malware / tool / attack-pattern / infrastructure |
| campaign / intrusion-set | attributed-to | threat-actor |
| malware | variant-of | malware |
| infrastructure | communicates-with | infrastructure |
| any | related-to | any |
Use related-to only when nothing else fits — it carries no semantics for downstream tools.
4. Kill-chain phases as classification, not narrative
Add kill_chain_phases to indicators and attack-patterns when you know it; leave it out when you're guessing. Lockheed's chain (delivery, exploitation, command-and-control) is widely understood; MITRE ATT&CK tactic IDs are more precise but require the consumer to recognize them.
5. Identifiers are RFC 4122 UUIDv4 — really
The OASIS validator strictly checks that the suffix is a UUIDv4. indicator--11111111-1111-1111-1111-111111111111 looks fine but fails (it's nominal UUID, not version 4). Use a real UUID generator. ThreatGraph generates them automatically when you create new objects in the graph.
6. name + description together
Best-practice rule 303 says SDOs SHOULD have both. name is what the analyst sees on a graph node; description is what survives the round-trip into a report. Skipping either is technically valid and practically painful.
7. Bundles, not loose objects
Always wrap your output in a bundle. Even a single indicator deserves the wrapper — TAXII servers and most importers require it.
{
"type": "bundle",
"id": "bundle--<uuidv4>",
"objects": [ ... ]
}
Try a bundle
Open the workspace, paste a bundle, and watch the validator flag the spots where these conventions slip. Try the STIX 2 visualizer in ThreatGraph.
Open the workspace →