A community extension for STIX 2.1 that adds temporal uncertainty support to objects and relationships.
Status: v1.0 — Community proposal. Implemented via the official STIX 2.1 Extension Definition mechanism.
STIX 2.1 handles absolute timestamps well, but provides no native mechanism for expressing temporal uncertainty. In practice, CTI analysts often face situations where:
- An event occurred within a time range, not at a precise moment (
"between Jan 1 and Jan 5") - Multiple sources report different timestamps for the same event
- A
first_seenorstart_timevalue is not available with certainty
This extension complements STIX native temporal fields without replacing them:
- Native fields (
first_seen,last_seen,start_time,stop_time) express certain information. - This extension is used when the information is uncertain.
The two never coexist on the same object for the same information.
Applicable to STIX objects that carry native temporal semantics: campaign, malware, threat-actor...
Extension Definition ID: extension-definition--97e3afda-ebea-400a-9cce-10fa1fb98b77
{
"type": "malware",
"extensions": {
"extension-definition--97e3afda-ebea-400a-9cce-10fa1fb98b77": {
"extension_type": "property-extension",
"uncertainty_range": {
"earliest": "2024-01-01T00:00:00Z",
"latest": "2024-01-10T00:00:00Z",
"most_probable": "2024-01-05T00:00:00Z"
},
"observations": [
{ "source": "firewall_log", "timestamp": "2024-01-03T00:00:00Z" },
{ "source": "analyst_report", "timestamp": "2024-01-08T00:00:00Z" }
]
}
}
}| Field | Type | Required | Description |
|---|---|---|---|
uncertainty_range |
object | No* | The synthesized uncertainty interval |
uncertainty_range.earliest |
timestamp | Yes (if present) | Lower bound — RFC 3339 / UTC |
uncertainty_range.latest |
timestamp | Yes (if present) | Upper bound — RFC 3339 / UTC |
uncertainty_range.most_probable |
timestamp | No | Best estimate, set manually by the analyst |
observations |
array | No* | Raw timestamps collected from sources |
observations[].source |
string | Yes | Identifier of the source |
observations[].timestamp |
timestamp | Yes | Timestamp reported by the source — RFC 3339 / UTC |
*At least one of uncertainty_range or observations must be present.
- If
most_probableis absent, no automatic inference is made. The object is treated as temporally unresolved. - No confidence scores — CTI reports do not provide formally defined confidence values on timestamps.
- No propagation of uncertainty to relationships — consistency is the implementer's responsibility.
Applicable to STIX relationships with temporal duration: uses, targets, attributed-to...
Extension Definition ID: extension-definition--b19394bc-0293-4432-bf86-5338c02d227f
{
"type": "relationship",
"relationship_type": "uses",
"extensions": {
"extension-definition--b19394bc-0293-4432-bf86-5338c02d227f": {
"extension_type": "property-extension",
"start": {
"uncertainty_range": {
"earliest": "2024-01-01T00:00:00Z",
"latest": "2024-01-10T00:00:00Z"
}
},
"stop": null
}
}
}| Field | Type | Required | Description |
|---|---|---|---|
start |
object | Yes | Uncertainty block for the start of the relationship |
stop |
object or null | Yes | Uncertainty block for the end — null if ongoing |
Each start / stop block follows the same structure as the object extension (uncertainty_range + observations).
stopis always present, set tonullwhen the relationship is ongoing or has no known end.start_time/stop_timenative fields and this extension never coexist on the same relationship.
When merging two STIX bundles containing the same object (same id):
observations— union of both lists, deduplicated on(source, normalized timestamp)earliest/latest— widened tomin(earliest)/max(latest)across both bundlesmost_probable— taken from the most recently modified bundle (modifiedfield); inherited from the older bundle if the newer one does not have it
pip install stix2 python-dateutilimport stix2
from temporal_uncertainty.extension import TemporalObjectUncertainty
from temporal_uncertainty.validator import validate_object, validate_relation
from temporal_uncertainty.merge import merge_objects, merge_relations
# Create a STIX object with temporal uncertainty
malware = stix2.Malware(
name="ExampleMalware",
malware_types=["trojan"],
is_family=False,
extensions={
"extension-definition--97e3afda-ebea-400a-9cce-10fa1fb98b77": {
"extension_type": "property-extension",
"uncertainty_range": {
"earliest": "2024-01-01T00:00:00Z",
"latest": "2024-01-10T00:00:00Z"
}
}
}
)
# Validate
validate_object(malware)stix2-temporal-uncertainty/
├── schemas/
│ ├── object_uncertainty.json # JSON Schema for the object extension
│ ├── relation_uncertainty.json # JSON Schema for the relation extension
│ └── common/
│ └── timestamp.json # Shared RFC 3339 timestamp schema
├── extension-definition/
│ ├── extension-definition-objects.json
│ └── extension-definition-relations.json
├── examples/
│ ├── bundle_example.json # Full CTI scenario
│ ├── bundle_simple.json # Minimal bundle
│ └── minimal_example.json # Single object and relation
├── python/
│ └── temporal_uncertainty/
│ ├── extension.py # Extension registration (stix2)
│ ├── validator.py # Business rule validation
│ └── merge.py # Bundle merge logic
└── spec/
└── temporal-uncertainty.md # Full specification
- Temporal uncertainty on punctual relationships (
drops,exploits,downloads) — out of scope for v1.
iliamsou — Community proposal, v1.0.
Licensed under Apache 2.0.