STIX 2.1 mapping best practices

Updated 2026-05-04 · 7 min read

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.

Fromrelationship_typeTo
indicatorindicatesmalware / tool / attack-pattern / threat-actor / intrusion-set / campaign
threat-actor / campaign / intrusion-setusesmalware / tool / attack-pattern / infrastructure
campaign / intrusion-setattributed-tothreat-actor
malwarevariant-ofmalware
infrastructurecommunicates-withinfrastructure
anyrelated-toany

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 →