0
# Object Versioning and Evolution
1
2
Version management system for creating new versions of STIX objects, tracking changes over time, and handling object revocation with proper timestamp and identifier management.
3
4
## Capabilities
5
6
### Versioning Functions
7
8
Core functions for creating new versions and revoking STIX objects.
9
10
```python { .api }
11
def new_version(stix_obj, **kwargs):
12
"""
13
Create a new version of an existing STIX object.
14
15
Parameters:
16
- stix_obj: Existing STIX object to create new version of
17
- **kwargs: Properties to update in the new version
18
19
Returns:
20
New STIX object with updated properties and incremented modified timestamp
21
22
Raises:
23
UnmodifiablePropertyError: If attempting to modify immutable properties
24
TypeNotVersionableError: If object type doesn't support versioning
25
ObjectNotVersionableError: If object lacks required versioning properties
26
RevokeError: If attempting to version a revoked object
27
"""
28
29
def revoke(stix_obj):
30
"""
31
Create a revoked version of a STIX object.
32
33
Parameters:
34
- stix_obj: STIX object to revoke
35
36
Returns:
37
New STIX object marked as revoked
38
39
Raises:
40
RevokeError: If object is already revoked
41
TypeNotVersionableError: If object type doesn't support revocation
42
"""
43
```
44
45
### Basic Versioning
46
47
Create new versions of STIX objects with updated properties.
48
49
Usage examples:
50
51
```python
52
from stix2 import new_version, Indicator, Malware
53
54
# Create initial version
55
indicator_v1 = Indicator(
56
name="Suspicious Domain",
57
indicator_types=["malicious-activity"],
58
pattern_type="stix",
59
pattern="[domain-name:value = 'suspicious.com']",
60
confidence=50
61
)
62
63
print(f"V1 ID: {indicator_v1.id}")
64
print(f"V1 Created: {indicator_v1.created}")
65
print(f"V1 Modified: {indicator_v1.modified}")
66
print(f"V1 Confidence: {indicator_v1.confidence}")
67
68
# Create new version with updated confidence
69
indicator_v2 = new_version(indicator_v1, confidence=85)
70
71
print(f"V2 ID: {indicator_v2.id}") # Same ID
72
print(f"V2 Created: {indicator_v2.created}") # Same creation time
73
print(f"V2 Modified: {indicator_v2.modified}") # New modification time
74
print(f"V2 Confidence: {indicator_v2.confidence}") # Updated confidence
75
76
# Create another version with additional changes
77
indicator_v3 = new_version(indicator_v2,
78
confidence=95,
79
description="Confirmed malicious domain used in phishing campaign",
80
labels=["phishing", "credential-theft"]
81
)
82
83
# Versioning preserves object identity
84
assert indicator_v1.id == indicator_v2.id == indicator_v3.id
85
assert indicator_v1.created == indicator_v2.created == indicator_v3.created
86
assert indicator_v1.modified < indicator_v2.modified < indicator_v3.modified
87
```
88
89
### Immutable Properties
90
91
Certain properties cannot be changed when creating new versions.
92
93
```python
94
from stix2 import new_version, UnmodifiablePropertyError
95
96
try:
97
# Attempting to change immutable properties raises error
98
invalid_version = new_version(indicator_v1,
99
id="indicator--new-id-12345678-1234-1234-1234-123456789012", # Cannot change ID
100
type="malware", # Cannot change type
101
created="2022-01-01T00:00:00.000Z" # Cannot change creation time
102
)
103
except UnmodifiablePropertyError as e:
104
print(f"Cannot modify: {e.unchangable_properties}")
105
106
# Valid versioning - only modifiable properties
107
valid_version = new_version(indicator_v1,
108
name="Updated Suspicious Domain", # Can change name
109
confidence=75, # Can change confidence
110
description="Additional context added", # Can add description
111
external_references=[ # Can add external references
112
{
113
"source_name": "internal-analysis",
114
"description": "Internal security team analysis"
115
}
116
]
117
)
118
```
119
120
### Object Revocation
121
122
Mark objects as revoked to indicate they should no longer be used.
123
124
```python
125
from stix2 import revoke, RevokeError
126
127
# Create malware object
128
malware = Malware(
129
name="Old Malware Name",
130
malware_types=["trojan"]
131
)
132
133
# Revoke the object
134
revoked_malware = revoke(malware)
135
136
print(f"Original revoked: {malware.revoked}") # False (or not present)
137
print(f"Revoked version: {revoked_malware.revoked}") # True
138
print(f"Same ID: {malware.id == revoked_malware.id}") # True
139
print(f"Updated modified: {revoked_malware.modified > malware.modified}") # True
140
141
# Cannot revoke already-revoked objects
142
try:
143
double_revoked = revoke(revoked_malware)
144
except RevokeError as e:
145
print(f"Revocation error: {e}")
146
147
# Cannot create new versions of revoked objects
148
try:
149
new_version_of_revoked = new_version(revoked_malware, name="Updated Name")
150
except RevokeError as e:
151
print(f"Versioning error: {e}")
152
```
153
154
### Version Tracking
155
156
Track and manage multiple versions of objects over time.
157
158
```python
159
from stix2 import MemoryStore, Filter
160
161
# Create store for version tracking
162
store = MemoryStore()
163
164
# Add all versions of an object
165
threat_actor_v1 = ThreatActor(
166
name="APT Example",
167
threat_actor_types=["nation-state"],
168
first_seen="2020-01-01T00:00:00.000Z"
169
)
170
171
threat_actor_v2 = new_version(threat_actor_v1,
172
aliases=["APT-EX", "Example Group"],
173
sophistication="advanced"
174
)
175
176
threat_actor_v3 = new_version(threat_actor_v2,
177
last_seen="2021-12-31T23:59:59.000Z",
178
goals=["espionage", "data-theft"]
179
)
180
181
# Store all versions
182
store.add([threat_actor_v1, threat_actor_v2, threat_actor_v3])
183
184
# Get latest version (most recent modified timestamp)
185
latest_version = store.get(threat_actor_v1.id) # Returns most recent
186
print(f"Latest version modified: {latest_version.modified}")
187
188
# Get all versions of an object
189
all_versions = store.all_versions(threat_actor_v1.id)
190
print(f"Total versions: {len(all_versions)}")
191
192
for version in all_versions:
193
print(f"Version modified: {version.modified}")
194
print(f"Properties: {len(version._inner)}")
195
196
# Get specific version by timestamp
197
specific_version = store.get(threat_actor_v1.id, version=threat_actor_v2.modified)
198
print(f"Specific version: {specific_version.modified}")
199
```
200
201
### Version Comparison
202
203
Compare different versions to track changes.
204
205
```python
206
def compare_versions(obj_v1, obj_v2):
207
"""Compare two versions of the same STIX object."""
208
if obj_v1.id != obj_v2.id:
209
raise ValueError("Objects must have the same ID")
210
211
changes = {}
212
213
# Get all properties from both versions
214
all_props = set(obj_v1._inner.keys()) | set(obj_v2._inner.keys())
215
216
for prop in all_props:
217
v1_val = getattr(obj_v1, prop, None)
218
v2_val = getattr(obj_v2, prop, None)
219
220
if v1_val != v2_val:
221
changes[prop] = {
222
'old': v1_val,
223
'new': v2_val
224
}
225
226
return changes
227
228
# Compare versions
229
changes = compare_versions(threat_actor_v1, threat_actor_v3)
230
for prop, change in changes.items():
231
print(f"{prop}: {change['old']} -> {change['new']}")
232
233
# Output:
234
# modified: 2021-04-23T10:30:00.000Z -> 2021-04-23T11:45:00.000Z
235
# aliases: None -> ['APT-EX', 'Example Group']
236
# sophistication: None -> advanced
237
# last_seen: None -> 2021-12-31T23:59:59.000Z
238
# goals: None -> ['espionage', 'data-theft']
239
```
240
241
### Version Lifecycle Management
242
243
Manage the complete lifecycle of versioned objects.
244
245
```python
246
from stix2 import Bundle
247
248
class VersionManager:
249
"""Utility class for managing object versions."""
250
251
def __init__(self, store):
252
self.store = store
253
254
def create_object(self, cls, **kwargs):
255
"""Create new object and store it."""
256
obj = cls(**kwargs)
257
self.store.add(obj)
258
return obj
259
260
def update_object(self, obj_id, **updates):
261
"""Create new version of object with updates."""
262
current = self.store.get(obj_id)
263
if not current:
264
raise ValueError(f"Object {obj_id} not found")
265
266
if getattr(current, 'revoked', False):
267
raise RevokeError("Cannot update revoked object")
268
269
new_obj = new_version(current, **updates)
270
self.store.add(new_obj)
271
return new_obj
272
273
def revoke_object(self, obj_id, reason=None):
274
"""Revoke object and optionally add reason."""
275
current = self.store.get(obj_id)
276
if not current:
277
raise ValueError(f"Object {obj_id} not found")
278
279
revoked_obj = revoke(current)
280
if reason:
281
revoked_obj = new_version(revoked_obj,
282
external_references=[{
283
"source_name": "revocation-reason",
284
"description": reason
285
}]
286
)
287
288
self.store.add(revoked_obj)
289
return revoked_obj
290
291
def get_version_history(self, obj_id):
292
"""Get chronological version history."""
293
versions = self.store.all_versions(obj_id)
294
return sorted(versions, key=lambda x: x.modified)
295
296
def is_current_version(self, obj):
297
"""Check if object is the most current version."""
298
latest = self.store.get(obj.id)
299
return obj.modified == latest.modified
300
301
# Usage example
302
store = MemoryStore()
303
manager = VersionManager(store)
304
305
# Create initial object
306
indicator = manager.create_object(Indicator,
307
name="Initial Indicator",
308
indicator_types=["malicious-activity"],
309
pattern_type="stix",
310
pattern="[ip-addr:value = '192.168.1.1']",
311
confidence=30
312
)
313
314
# Update object multiple times
315
indicator_v2 = manager.update_object(indicator.id, confidence=60)
316
indicator_v3 = manager.update_object(indicator.id,
317
confidence=85,
318
description="Confirmed C2 server"
319
)
320
321
# Get version history
322
history = manager.get_version_history(indicator.id)
323
print(f"Version history: {len(history)} versions")
324
325
for i, version in enumerate(history, 1):
326
print(f"V{i}: {version.modified} - Confidence: {version.confidence}")
327
328
# Revoke object
329
revoked = manager.revoke_object(indicator.id,
330
reason="Indicator found to be false positive")
331
332
# Check current status
333
is_current = manager.is_current_version(indicator_v3)
334
print(f"V3 is current: {is_current}") # False, revoked version is current
335
```
336
337
### Versioning in Relationships
338
339
Handle versioning when objects are referenced in relationships.
340
341
```python
342
from stix2 import Relationship, ThreatActor, Malware
343
344
# Create related objects
345
actor = ThreatActor(
346
name="Initial Actor Name",
347
threat_actor_types=["nation-state"]
348
)
349
350
malware = Malware(
351
name="Banking Trojan",
352
malware_types=["trojan"]
353
)
354
355
# Create relationship
356
relationship = Relationship(
357
relationship_type="uses",
358
source_ref=actor.id,
359
target_ref=malware.id
360
)
361
362
# Store objects
363
store.add([actor, malware, relationship])
364
365
# Update referenced object
366
actor_v2 = new_version(actor,
367
name="Updated Actor Name",
368
aliases=["Actor Alias"]
369
)
370
store.add(actor_v2)
371
372
# Relationship still references same ID
373
retrieved_relationship = store.get(relationship.id)
374
referenced_actor = store.get(retrieved_relationship.source_ref)
375
376
print(f"Relationship references: {referenced_actor.name}") # "Updated Actor Name"
377
print(f"Actor version: {referenced_actor.modified}") # Latest version
378
379
# Create new relationship version if needed
380
relationship_v2 = new_version(relationship,
381
description="Updated relationship description"
382
)
383
store.add(relationship_v2)
384
```
385
386
### Bundle Versioning
387
388
Version entire bundles of related objects.
389
390
```python
391
from stix2 import Bundle
392
393
# Create bundle with multiple objects
394
bundle_v1 = Bundle(
395
indicator_v1,
396
malware,
397
relationship
398
)
399
400
# Create new bundle version with updated objects
401
bundle_v2 = new_version(bundle_v1,
402
objects=[indicator_v3, malware, relationship_v2] # Updated objects
403
)
404
405
print(f"Bundle V1: {len(bundle_v1.objects)} objects")
406
print(f"Bundle V2: {len(bundle_v2.objects)} objects")
407
print(f"Same bundle ID: {bundle_v1.id == bundle_v2.id}")
408
```
409
410
### Error Handling
411
412
Handle versioning errors appropriately.
413
414
```python
415
from stix2.exceptions import (
416
TypeNotVersionableError,
417
ObjectNotVersionableError,
418
UnmodifiablePropertyError,
419
RevokeError
420
)
421
422
def safe_version_update(obj, **updates):
423
"""Safely attempt to create new version with error handling."""
424
try:
425
return new_version(obj, **updates)
426
427
except TypeNotVersionableError:
428
print(f"Object type {obj.type} does not support versioning")
429
return None
430
431
except ObjectNotVersionableError:
432
print(f"Object lacks required properties for versioning")
433
return None
434
435
except UnmodifiablePropertyError as e:
436
print(f"Cannot modify properties: {e.unchangable_properties}")
437
# Filter out immutable properties and retry
438
valid_updates = {k: v for k, v in updates.items()
439
if k not in e.unchangable_properties}
440
if valid_updates:
441
return new_version(obj, **valid_updates)
442
return None
443
444
except RevokeError:
445
print("Cannot version revoked object")
446
return None
447
448
# Safe versioning attempt
449
updated_obj = safe_version_update(some_object,
450
name="New Name",
451
id="invalid-id-change", # This will be filtered out
452
confidence=75
453
)
454
```