Last updated: October 29, 2026.
A STIX 2.1 example is easiest to read from the inside out: a pattern states what to match, an Indicator gives that pattern context, and a Bundle carries the object. The JSON below contains one complete Indicator. We use a made-up domain so you can test the file without turning a real site into a threat label.
This is a format lesson, not a threat report. The name malware.example is a placeholder. The reserved .example domain is meant for documentation. Do not import this teaching object into a live blocklist or treat it as evidence of an attack.
You will save and validate a small file, then add a producer, a fictional Malware object, and a relationship. The final checks cover updates, expiry, and the receiving system. Both complete examples passed the stated validator; no platform import or live detection was tested.
Choose The Object Before Writing The JSON
An indicator of compromise in everyday security language can be a suspicious domain or hash. A STIX Indicator has a narrower structure: it holds a pattern and its context. A bare domain value does not explain why it matters or whether anyone has seen it.
- Cyber-observable object: describes a fact such as a domain-name value. It does not itself assert that the domain is malicious.
- Observed Data: records when facts were observed and how often. It can reference cyber-observable objects through
object_refs. - Indicator: expresses a pattern used to detect potentially suspicious or malicious activity. Its validity dates describe the Indicator, not an observed event.
- Sighting: records that something represented by STIX was believed to have been seen. It can reference Observed Data that supports the sighting.
- Relationship: states a link between objects, such as an Indicator that
indicatesMalware. Sharing a Bundle does not create that link.
Use these distinctions when reading a feed. A pattern is not a sighting, and a sighting is not automatic proof of attribution. Our STIX and TAXII glossary explains the wider split between the data model and its transport. Here, the focus is the saved STIX file.
A Complete STIX 2.1 Example
Save the following JSON as sample.json. Copy the whole object, including the outer braces. The dates and identifiers are fixed so your test uses the same content as ours. The timestamps describe this teaching object; they are not dates of a real observation.
{
"type": "bundle",
"id": "bundle--bb7b3a4e-806b-4680-8429-bf1617e4e5fd",
"objects": [
{
"type": "indicator",
"spec_version": "2.1",
"id": "indicator--d6e74bc9-a791-4fb6-badf-83b8b21b6e72",
"created": "2026-10-29T10:00:00.000Z",
"modified": "2026-10-29T10:00:00.000Z",
"name": "Documentation-only domain pattern",
"description": "Fictitious tutorial indicator. Do not use as threat intelligence or deploy to production.",
"indicator_types": [
"anomalous-activity"
],
"pattern": "[domain-name:value = 'malware.example']",
"pattern_type": "stix",
"pattern_version": "2.1",
"valid_from": "2026-10-29T10:00:00.000Z"
}
]
}
The outer object has three properties: type, id, and objects. Its objects array contains the Indicator. Notice that spec_version is inside the Indicator, not at the Bundle level. The OASIS STIX 2.1 specification defines these as different structures.
- bundle.objects1 ContainerCarries one Indicator in this example.
- indicator.pattern2 Test (deceptive part)States the domain value to match.
- indicator.valid_from3 TimeStates when the Indicator starts to apply.
Which Fields Identify The Indicator?
type selects the Indicator object definition. spec_version says it uses STIX 2.1. The id starts with the object type, then two hyphens and a UUID. We chose version 4 UUIDs for both identifiers, in line with the specification’s recommendation for these object types.
created records when the object was created. modified records its latest change; both are equal in this first version. These are UTC timestamps, shown by the final Z. If you later revise a real Indicator, retain its identity and follow STIX object versioning rules rather than changing dates at random.
name gives a short label. description explains the purpose and limits. Both help a reader judge the object, though they are optional in the standard.
Our indicator_types list uses the defined value anomalous-activity for the lesson. That value is a category, not proof that the placeholder domain is harmful.
Read The Pattern Without Guessing
The expression [domain-name:value = 'malware.example'] compares the value of a STIX domain-name object with the stated string. Square brackets bound the observation expression. The colon separates the object type from its property. Single quotes enclose the domain string within the JSON string.
pattern_type names the language used, here stix. pattern_version makes the chosen language version explicit. These fields should agree with the pattern itself. A domain pattern belongs in the pattern field; putting a domain in the name alone would give a reader a label but no test to apply.
valid_from sets the start of the Indicator’s validity window. This example has no valid_until, so it places no upper bound on that window. That omission does not mean real evidence remains useful forever. Set a review and expiry policy in the system that receives your intelligence.
Validate The File You Actually Saved
We ran the full file through the OASIS STIX validator on 29 October 2026. Our test used Python and stix2-validator 3.2.0 in an isolated environment. The command below selects STIX 2.1 and treats warnings as errors. Save the file first, then run the command from the same folder.
python3 -m venv .venv
.venv/bin/pip install stix2-validator==3.2.0
.venv/bin/stix2_validator --version 2.1 --strict sample.json
The successful result for our saved file was STIX JSON: Valid. JSON parsing alone would not cover every STIX rule. OASIS explains that the validator also checks requirements that JSON schemas cannot express. Keep the validator’s full output when a file fails, fix the named fields, and validate the saved file again.
Keep the version pin when reproducing this run. Our initial 3.3.1 attempt stopped with a schema-loading error before it could assess the sample. The tested 3.2.0 environment completed strict validation. That is a record of this environment, not a claim that every 3.3.1 installation fails.
If your output says it cannot locate a schema, first check the installed package and its schema resources. Do not edit valid intelligence fields merely to silence an environment failure. The validator’s installation and command options are documented in its linked repository.
Add A Producer And An Explicit Relationship
The first example answers what to match. It does not say who created it or what it claims to indicate. Save this second file as context-bundle.json. Every identity, malware label, relationship, confidence value, and date is fictional teaching data.
The Indicator keeps the same ID and creation time. Its later modified time marks a new version with added context. The other objects have their own IDs. These choices show versioning and references; they do not describe a real intelligence update.
{
"type": "bundle",
"id": "bundle--d24c1ecf-12a9-4dd3-a07f-1c6fcaec7d75",
"objects": [
{
"type": "identity",
"spec_version": "2.1",
"id": "identity--120e7127-6a62-4d2c-98bf-686c70fb1578",
"created": "2026-10-29T10:00:00.000Z",
"modified": "2026-10-29T10:00:00.000Z",
"name": "Fictitious tutorial producer",
"description": "Documentation-only identity. It is not a real source organization.",
"identity_class": "organization"
},
{
"type": "indicator",
"spec_version": "2.1",
"id": "indicator--d6e74bc9-a791-4fb6-badf-83b8b21b6e72",
"created": "2026-10-29T10:00:00.000Z",
"modified": "2026-10-29T11:00:00.000Z",
"name": "Documentation-only domain pattern",
"description": "Fictitious tutorial indicator. Do not use as threat intelligence or deploy to production.",
"indicator_types": [
"anomalous-activity"
],
"pattern": "[domain-name:value = 'malware.example']",
"pattern_type": "stix",
"pattern_version": "2.1",
"valid_from": "2026-10-29T10:00:00.000Z",
"created_by_ref": "identity--120e7127-6a62-4d2c-98bf-686c70fb1578",
"confidence": 25,
"valid_until": "2026-11-05T10:00:00.000Z"
},
{
"type": "malware",
"spec_version": "2.1",
"id": "malware--4224e91d-6093-4013-9e34-34e6c1743430",
"created": "2026-10-29T10:00:00.000Z",
"modified": "2026-10-29T10:00:00.000Z",
"created_by_ref": "identity--120e7127-6a62-4d2c-98bf-686c70fb1578",
"name": "Fictitious tutorial malware",
"description": "Documentation-only malware object. This is not a real threat or attribution.",
"malware_types": [
"remote-access-trojan"
],
"is_family": false
},
{
"type": "relationship",
"spec_version": "2.1",
"id": "relationship--02ae0831-c37b-4cc4-a529-2374c9b9c592",
"created": "2026-10-29T11:00:00.000Z",
"modified": "2026-10-29T11:00:00.000Z",
"created_by_ref": "identity--120e7127-6a62-4d2c-98bf-686c70fb1578",
"relationship_type": "indicates",
"source_ref": "indicator--d6e74bc9-a791-4fb6-badf-83b8b21b6e72",
"target_ref": "malware--4224e91d-6093-4013-9e34-34e6c1743430",
"description": "Fictitious tutorial link. No real-world detection or association is claimed."
}
]
}
created_by_ref points to the producer’s Identity. It identifies the claimed creator; it does not authenticate that creator. The Relationship points from the Indicator to Malware using source_ref and target_ref. Its indicates value makes the intended connection explicit.
The value confidence: 25 demonstrates the optional 0–100 field. We chose it only for the lesson. It is not a measured probability that this invented domain is harmful. When confidence is absent, its value is unspecified; do not silently turn that into zero or full trust.
For real intelligence, record the supporting evidence and the producer’s assessment method before adding an association. STIX can represent an assertion faithfully even when the assertion is wrong. Validation checks the representation; an analyst must still assess the claim.
.venv/bin/stix2_validator --version 2.1 --strict context-bundle.json
Our saved second file returned STIX JSON: Valid with the same pinned validator. All referenced objects are included here for a self-contained lesson. A real receiver may already hold a referenced object, so check its rules for resolving references across imports.
Manage Versions, Expiry, And Revocation Separately
The STIX common properties and versioning rules distinguish an object’s identity from a particular version. Keep that distinction when storing updates. A Bundle ID identifies the wrapper; it is not a reliable deduplication key for the Indicators inside.
Only the object creator may issue a new version or revoke that object. If you are a different producer, create your own object with a new ID. Use a derived-from relationship when appropriate to show its origin. Do not revise another producer’s object as though you created it.
- Revise your own object: retain
idandcreated; advancemodified. The second example illustrates this with the Indicator’s later modification time. - Recognize a repeated version: compare the object ID and version timestamp. Reimporting the same version should not create a new analyst finding merely because its Bundle changed.
- Bound applicability: set
valid_untilwhen a known end is appropriate. It must be later thanvalid_from. The example’s end date is arbitrary. - Withdraw your own object: a version with
revoked: truemarks it permanently revoked. STIX forbids later versions of that object. Do not use revocation for a temporary pause.
Expiry and revocation answer different questions. An expired Indicator is outside its stated validity window. A revoked object has been withdrawn by its creator. Your receiving system needs a policy for both, including what happens to copies already sent to detection tools.
Test those transitions in staging before importing a real feed. Send the same version twice, then a later version, and inspect the stored result. Check how expired and revoked objects affect searches and downstream exports. These are suggested acceptance tests; we did not run them against a platform for this article.
Three Failures Worth Testing Yourself
We changed one field at a time in copies of the validated files and reran the same strict command. Two changes failed as expected. One typo still passed, which shows why a green validation result cannot replace a receiving-system test.
- Missing pattern: deleting
patternreturned'pattern' is a required property. Restore the actual expression; a descriptive name cannot replace it. - Expiry before the start: setting
valid_untilbeforevalid_fromreturned a validity-window error. Correct the dates from evidence; do not choose a later date just to pass. - Wrong property name: changing
domain-name:valuetodomain-name:valustill returnedSTIX JSON: Validin our 3.2.0 run. Restore the defined propertyvalueand test the receiver’s pattern handling.
The third result matters: the tool accepted this particular typo, but STIX defines the domain-name property as value. Passing this validator does not establish that a pattern will match telemetry. Keep the exact tool version and output with your test record so others can reproduce the boundary.
Also check the file before chasing STIX errors. Smart quotes, missing commas, and unescaped quotation marks can break JSON itself. Preserve the outer Bundle braces and the inner pattern quotes when copying. Rerun validation after each change so the result belongs to the file you actually intend to share.
Check The Receiving Platform’s Import Contract
A valid Bundle is a starting point for an import test. A product may accept only certain STIX versions, object types, or pattern features. It may store an object without making it available to a detector. Read the product’s documented import behavior and inspect what survives the import.
- Confirm scope: check STIX 2.1, Indicator patterns, Relationship support, and reference resolution. Test in an isolated workspace with response actions disabled.
- Inspect the stored fields: compare IDs, timestamps, pattern, producer, confidence, and validity dates with the input. Look for dropped context or renamed fields.
- Exercise updates: check repeated versions, newer versions, expiry, and revocation. Decide who reviews conflicts between producers.
- Test interpretation: use permitted synthetic telemetry to check expected matching and non-matching cases. A successful upload is not a successful detection.
- Approve downstream use: review source quality, sharing restrictions, freshness, and false-positive risk before enabling any response action.
Our OpenCTI and MISP hands-on comparison gives you a practical next environment to explore. Use its setup guidance, then verify each platform’s current import contract separately. This article does not claim that either platform imported these fixtures.
If you are choosing where this data should live, Threat Intelligence Platforms 101 connects ingestion, enrichment, and operational use. Those stages explain why preserving source and lifecycle fields matters beyond a successful JSON upload.
Turn The Example Into A Reusable Intelligence Workflow
Start with a source you are allowed to use, and record each claim with its supporting passage, dates, and limits. Choose the STIX objects that fit the evidence. Validate the saved Bundle, review the claims, and test the receiver before distribution. Keep the source and review record alongside the exported file.
For an AI-assisted workflow, begin with read-only retrieval. Our Python threat intelligence MCP server tutorial shows how to expose local intelligence to an agent. Keep these fictional examples clearly labeled if you use them as fixtures. An agent should not turn a teaching record into a real threat claim.
Control what the agent can read and do as well as what it can output. The guide to securing MCP servers and tool access covers that next boundary. Schema validation alone does not grant an agent permission to publish intelligence or change a blocklist.
If your goal is a detector rather than a shared intelligence object, continue with turning a threat report into ATT&CK mappings and Sigma rules. A STIX domain pattern is not a ready-made Sigma rule. Detection engineering still needs the right log source, field mapping, test events, and false-positive review.
For the wider analyst workflow, Agentic Threat Intelligence 101 connects retrieval and analysis with human review. Keep the same rule here: each added relationship needs support, each update needs lifecycle handling, and each operational action needs a separate decision.
Your next step is small and testable: validate both files, reproduce the three mutations, and inspect their output. Then choose one isolated receiver and write down its import behavior. Replace the fictional objects with sourced, reviewed intelligence only when that full path is understood.
