-
Notifications
You must be signed in to change notification settings - Fork 0
Importing into OpenCTI
A bundle exported from Draw Me A STIX is plain STIX 2.1 and any platform can read it. OpenCTI is the one we actually tested against, and testing turned up things that neither the specification nor the code can tell you. This page collects them: which ingestion path to prefer, what the deterministic identifiers do once the bundle is on the other side, and what to look at once the import is finished.
For what is in a bundle and how to produce one, see Exporting. For the identifier algorithm itself, see docs/identifiers.md.
This one is an observation, not documented OpenCTI behaviour, and it is the only thing on this page that is not backed by the code or the specification. Take it as such.
Importing real bundles into one OpenCTI instance, the workbench path mishandled them: a bug in the connector that handles them meant the objects did not land the way the file described. The draft path ingested the same file correctly. That was a single instance at a single point in time, nothing in the repository records it, no version is pinned to it, and it may well have been fixed upstream since.
What it is worth to you: if a workbench import produces something that does not match the file you sent, send the same file to a Draft before you spend an hour debugging a bundle that is fine.
The export dialog reports a count: <n> STIX objects in the bundle. That number is not the number of entities you drew, and knowing the difference saves an argument with yourself later.
| Part of the bundle | How many | Where it comes from |
|---|---|---|
Tool identity + layout extension-definition
|
always 2, first in the file | plumbing, so the extensions key carried by the entities resolves |
| Author identity | 0 or 1 | the Author field in the export dialog |
marking-definition |
one per TLP actually used | the export TLP, plus any per-entity TLP |
| Entities, relationships, notes, opinions | your investigation | confirmed entities only; the notes and opinions only if Include notes and opinions was left on |
Container (report or grouping) |
1, last | the investigation itself |
Two consequences worth holding on to:
- Only confirmed entities are exported. Anything sitting in the triage tray is not in the file, and a relationship with one end in the tray is dropped with a warning. See Triage-walkthrough.
- The container's
object_refslists the entities, relationships and notes, and nothing else. The plumbing, the author and the markings are not referenced by it.
If the export warned that an object "collapses onto an object already exported", the bundle genuinely holds one object where your canvas held two. That is the deterministic identifier doing its job, and it is the correct count to expect downstream.
Identifiers are UUIDv5 values derived from each object's own identifying properties, using the same scheme OpenCTI uses on its side. The consequence at import time is the whole point:
Re-importing the same bundle updates the objects instead of creating a second set. Two analysts on two machines, with no shared server, produce the same identifier for the same malware, and their bundles merge rather than pile up. You can import, keep working, and import again.
The cost is the mirror image of that:
Renaming an object creates a second one downstream. The identifier follows the properties, so fixing a typo in a malware name, or correcting a hash, produces a different identifier. On the next import the platform sees a new object beside the old one, not a rename. Cheap on the canvas, not cheap on the platform, where you now have to merge them by hand. Correct names before the first import, not after the third.
The same applies to the container. A report is identified by its name and its published date, a grouping by its name and its context, so renaming the investigation gives you a second container holding the same objects.
The algorithm, property by property and type by type, is in docs/identifiers.md. It is not repeated here.
Everything the builder emits is valid STIX 2.1. A few choices exist because OpenCTI is on the other end.
Deterministic identifiers on objects (SDOs). The specification says an SDO identifier should be a random UUIDv4. We depart from it deliberately and reproduce OpenCTI's scheme, because a random identifier makes every re-import a duplication. Tools that do not deduplicate on identifiers take these like any others; the bundle stays valid either way.
x_opencti_location_type on locations. STIX 2.1 has no way to say "this is a country, this is a city, this is a region". OpenCTI does, through this custom property, and it also enters the location's identifier. A consumer that ignores it is left with a perfectly valid location.
The threat actor's kind lives only in the identifier. Choosing group or individual changes the deterministic identifier (Threat-Actor-Group versus Threat-Actor-Individual), so a group and a person of the same name stay distinct. The builder does not re-emit that choice as a property. Set it before the first export: changing it later is a rename, with the consequence described above.
Confidence on every object that does not carry its own. The export dialog offers none, 15, 50, 75 (the default) and 100. Confidence is what arbitrates a merge on the platform side: a platform that honours it will not let a lower-confidence assertion overwrite a higher-confidence one. 75 is there because analyst curation should win against most automated feeds. A confidence set on a single entity in the inspector takes precedence over the export value.
Markings ride on the observables themselves, not just on the container. Observables used to carry no marking at all, on the assumption that the container covered them. Checked against OpenCTI: a platform that ingests objects one at a time propagates nothing from the container, and an IP address exported as TLP:RED arrived unmarked. Declassified, in other words. Every observable now carries object_marking_refs of its own, whenever a marking applies: pick "no marking" in the dialog and set no per-entity TLP, and there is nothing to carry.
Observables carry a marking and nothing else. STIX 2.1 allows object_marking_refs on an observable but not created_by_ref, so your author identity is on the objects and the relationships, never on an IP address or a file. This is the specification's rule, not ours, and it is worth knowing before you go looking for the author on an observable and conclude the import failed.
Empty fields are never written. A field with no value is left out of the bundle entirely, so an import cannot blank out a value that already exists on the platform. That is also why the export lets you choose a confidence and an author rather than always emitting one.
TLP:CLEAR is written as TLP:WHITE. STIX 2.1 defines the white marking definition, not clear. Pick TLP:CLEAR in the dialog and the bundle carries marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9, named TLP:WHITE. That is what you will see downstream. It is the same marking under its older name, not a mistake.
The layout extension is inert. Node positions travel in a property-extension, with its definition shipped in the bundle so nothing points at an unresolvable identifier. Nothing downstream needs to understand it, and it carries no intelligence. It exists so that re-importing the file into Draw Me A STIX gives you your canvas back.
Import once, then look at four things. All of them are cheap, and each one catches a different failure.
1. Object counts. Compare against the bundle, not against your canvas. Take the count from the export dialog and subtract the plumbing: two tool objects, the author identity if you set one, one marking-definition per TLP used, and the container. What remains is your entities, your relationships, and the notes and opinions if you left the checkbox on. Relationships count as objects too, and they are usually the ones missing when a count is short: a relationship with an end that was not exported is dropped, and the export says so in a warning.
2. The markings arrived. Do not check this on an object; check it on an observable. Objects get their marking from the export-wide setting and were never the problem. Observables are the case that once failed silently, and an unmarked TLP:RED address is a disclosure, not a cosmetic defect. Open one IP address or one file and confirm the marking is on it.
3. The author is the identity you set. Look at an object or a relationship, not an observable, since the specification forbids created_by_ref there. Confirm the name matches what you typed in the Author field. The author identity is created deterministically from its name and its class, and the class is always organization: the dialog offers no way to change it. The name is lowercased and trimmed before it enters the identifier, so My CERT and my cert land on the same identity, and an author already on the platform under that name is reused rather than duplicated. A genuinely different spelling gives you a second author identity.
4. A second import changes nothing. This is the real test of the whole scheme. Import the same file again and the counts should not move. If they do, something in the bundle is not identified the way you think it is, and that is worth understanding before you build on it.
Worth checking once, on the first bundle you send to a given instance, then never again: that the confidence on an imported object is the value you chose, and that a re-import with a lower confidence does not overwrite what is already there.
If the import came out of a workbench, resending the file to a draft is the cheapest thing to try first, for the reason at the top of this page. If the bundle itself is the suspect rather than the ingestion, Troubleshooting covers the exports the tool refuses to produce and the warnings that come with a successful one. What a platform does with a bundle after it has ingested it is not covered there.
One thing to keep in mind while reading any of it: merge behaviour is the platform's business, not the format's. Whether an existing field is updated, overwritten or merged comes down to how the instance is configured. The bundle can only avoid making that worse, which is what the never-write-an-empty-field rule and the confidence setting are for. The version this was verified against, and what exactly was verified, is stated in the README.
Using it
Running it
Changing it
When it breaks
In the application:
In the repository: