Skip to content

Exporting

pak edited this page Aug 12, 2026 · 2 revisions

Exporting

The STIX bundle is not a report you produce at the end. It is the save file. Investigations live in the IndexedDB of one browser profile on one machine, so until you have downloaded the .json, nothing on disk holds your work. The status bar says so: an investigation that has never been exported shows never exported, and one changed since its last download shows unexported changes.

Everything below happens in the browser. Nothing is uploaded. For where the data lives and what leaves, see /about.

Opening the dialog

Two ways in, and they open the same panel:

  • the Export STIX button, top right of the workspace;
  • the command palette (Ctrl/Cmd + K), entry Export STIX bundle….

There is no dedicated keyboard shortcut for it.

The panel has two steps that are deliberately separate: Generate the bundle, then Download the .json. Only the download counts as an export. Opening the dialog, generating, looking at the preview and closing leaves the status bar untouched, because no file exists yet. Changing any option after generating clears the generated bundle and you have to generate again, so that you can never download a file that no longer matches the options on screen.

The file is named after the investigation, stripped of anything outside letters, digits, accented letters, space, _ and -, with .stix.json appended.

The options

Option Default What it does
Container report Wraps every exported object in a report (finished product) or a grouping (investigation in progress).
TLP TLP:AMBER Marking applied to the whole bundle. no marking emits none.
Confidence 75 - high (analyst curation) Integer 0-100 written on every object that does not carry its own. none writes nothing and lets the receiving platform decide.
Author empty Creates an identity (class organization) and points created_by_ref at it on every object that can carry one.
Include notes and opinions on Whether your note and opinion objects travel with the bundle.

TLP, author and confidence are remembered between sessions in localStorage under dmas.export-prefs. They are analyst settings, not investigation settings: you set your CERT name and your house TLP once. The container choice and the notes checkbox are not remembered and go back to report and on every time.

Container: report or grouping

A report carries published (the investigation's creation date) and report_types: ["threat-report"]. A grouping carries context: "suspicious-activity" instead. The distinction is about what you are claiming: a report asserts a finished analysis, a grouping asserts "these things belong together, I am still working". Platforms treat them differently in their UI, and analysts reading your bundle will read the container type as a claim about maturity.

Both identifiers are deterministic and derived from the investigation name, so re-exporting the same investigation updates the same container rather than piling up new ones. Renaming the investigation produces a different container.

Confidence, and why 75 is the default

Confidence is not a decoration. On a platform that honours it, it arbitrates updates: incoming data with a lower confidence than what is already stored does not overwrite it. 75 is high enough that hand-curated work wins against most automated feeds, and low enough that it does not pretend to certainty.

none is the honest choice when you do not know the receiving platform's policy: writing nothing lets it apply its own default rather than having you guess one.

Observables never carry a confidence. STIX 2.1 has no confidence property on an SCO, so the value is dropped for those objects rather than smuggled in.

Author

Filling the author field adds one identity object to the bundle and sets created_by_ref on the SDOs, the relationships, the container and the notes. Observables get no created_by_ref, because the spec does not allow one on an SCO.

Leaving it empty is legitimate: the bundle then attributes nothing, and the receiving platform will attribute it to whatever connector ingested it.

Notes and opinions

With the checkbox on, every note becomes a note object and every opinion an opinion object. A note attached to an entity references that entity; a note attached to nothing references the container. A note whose entity was not exported (a candidate still in the triage tray) is skipped, with a warning.

Turn it off when you are shipping to a partner and the notes are your working thinking rather than intel.

Per-entity overrides win

TLP and confidence set on an individual object, in the inspector or through bulk edit, take precedence over the export values for that object alone. That is how you ship a mostly TLP:GREEN bundle with two TLP:RED observables in it. Every marking definition actually used is embedded in the bundle, in a fixed order, so the file does not change shape depending on the order you created things in.

The inspector only offers TLP and confidence on SDOs. To set a TLP on an observable, select it together with at least one other object: the bulk edit panel replaces the inspector as soon as two things are selected, and it applies to everything in the selection whatever the type. The builder then reads that TLP on the observable and honours it.

The lint that runs before export

The lint runs on the local state when you open the dialog and prints a list titled what would make the bundle cleaner. Nothing in it stops the export by itself. Three of its findings are advance warning that the builder will refuse anyway, and two of them say so in their own text.

The status bar also shows a lint counter, which counts only the warn level findings.

Finding Level What it means
N candidate(s) still in the triage tray info Only confirmed entities are exported. The tray is not part of the bundle.
N relationship(s) touch a candidate entity warn Those relationships are dropped: an edge cannot point at an object that is not in the file.
N imported entity/entities already confirmed without triage warn They arrived from a bundle carrying our layout extension, so they bypassed the tray. Check their provenance before pushing them to a platform: a third-party bundle can forge that extension.
likely duplicate: two X named "Y" warn Same type, same name (case and edge whitespace ignored). They will very likely collapse onto one identifier at build time.
X "Y": isolated, no relationship info The container will still reference it, but nothing in the graph says why it is there.
location: no country, region or coordinates warn The export will be blocked.
indicator: missing pattern warn The export will be blocked.
indicator: no based-on towards the source observable info The pattern exists but nothing links it to the observable it came from, so the provenance chain is broken on the consumer side.
file: no hash warn A file identified by name alone deduplicates badly everywhere downstream.
attack-pattern: no MITRE ID info Without x_mitre_id, the receiving platform can only deduplicate on the name.
observable: no indicator (based-on) covers it info A fact wired into the graph with no detection built on it. Fine, if that is what you meant. It only fires on an observable that already has a relationship, an isolated one gets the isolated finding instead, and a consists-of from an infrastructure counts as covering it.
relationship: activity end before its start warn stop_time earlier than start_time. Nothing downstream will catch this, which is why it is caught here.
last_seen earlier than first_seen warn Same reasoning.
valid_until earlier than valid_from warn On an indicator this blocks the export. Note that an empty valid_from is not absent: the builder falls back on the object's creation date, and the check compares against that effective value. The message says so when it applies.

What blocks an export

These come back as Export impossible with a list, and no bundle is produced.

  • A location with no country, no region and not both coordinates. The spec requires one of the three. The tool used to fall back on copying the name into region, which is a normalised vocabulary: "Brive-la-Gaillarde" came out as a region of the world and polluted the consumer's filters. Refusing is the lesser harm.
  • An indicator with no pattern. There is nothing to detect with.
  • An indicator whose valid_until is not strictly after valid_from. The object would be invalid and the detection would never fire on the consumer side.
  • An autonomous-system whose name contains no digits, so no AS number can be read from it.
  • An empty investigation, meaning nothing confirmed to reference.
  • A failure against the OASIS STIX 2.1 JSON schemas. Every generated bundle is validated against the official schemas before you are offered the download, including a check that a schema exists for each type present. This is the belt-and-braces pass: if it fires, the message is the schema's own and can be terse.

Warnings after generation

Distinct from the lint: these describe what the builder actually did, and they appear next to the fingerprint once the bundle exists. The download is still offered.

  • An object collapses onto one already exported. Identifiers are computed from properties, so two canvas nodes can legitimately land on the same identifier (two techniques with the same MITRE ID, two relationships of the same type between the same ends and the same time window). Only the first is written, so only its description travels. Merge the two nodes on the canvas if you want both descriptions kept.
  • A relationship skipped because one end is not exported.
  • A note skipped because its entity is not exported.
  • Properties of an imported observable not re-exported. Only custom x_… properties survive a round trip on an observable. A modelled-elsewhere STIX property is often a reference (resolves_to_refs, belongs_to_refs) pointing at identifiers that are not in your bundle, and re-emitting it would manufacture dangling references. The loss is announced rather than silent, because an observable enriched elsewhere used to grow quietly poorer at every round trip.

The version fingerprint

Under the generated bundle sits a line like sha256:…. It is the SHA-256 of the canonical (JCS) serialisation of the bundle's objects, sorted by identifier, with two things removed first: the canvas layout extension on each object, and the tooling objects (the extension definition and the tool identity).

What that buys you:

  • Two analysts on the same state get the same fingerprint. It is a content identity, quotable in a ticket or a chat message to establish that you are both looking at the same thing.
  • Moving nodes around does not change it. A visual rearrangement is not a new version of the intelligence.
  • Two exports of the same state produce the same file, byte for byte. Even the bundle identifier is derived from the fingerprint rather than being a fresh random UUID. That is what makes the unexported changes indicator trustworthy rather than decorative.

The status bar compares the investigation's updated_at against the state the downloaded file actually contains, not against the moment you clicked download. A canvas changed between generating and clicking would otherwise have shown green over a stale file.

One deliberate looseness: dragging a node changes the bundle bytes (positions are embedded) but does not change the fingerprint and does not mark the investigation as modified. The indicator stays green while the file on disk no longer quite matches. Rearranging a layout has no business demanding a new export.

What the bundle carries, and what it leaves behind

Carried Left behind
Confirmed entities and their properties Candidates in the triage tray
Relationships between confirmed entities, with descriptions and time windows Relationships touching a candidate
Notes and opinions, if the checkbox is on The scratchpad (work notes), which is why editing it never moves the fingerprint
The container (report or grouping) with its object_refs Screenshots pasted on the canvas
Marking definitions actually used, and the author identity if you set one Pinned-note positions on the canvas
Node positions, in a per-object extension Undo history

The layout extension is worth understanding, because it decides what happens on the way back in. It records position_x, position_y, the local id and the source, and it travels with its own extension-definition and a tool identity so a consumer is never left holding an unresolvable reference. Its schema is plain text rather than a URL, on purpose: a URL would bake a hosting address into every bundle an analyst exports, and that address may stop answering long after the bundle is filed.

Its practical consequence: a bundle exported from Draw Me A STIX and imported back comes in already confirmed, with its layout; a third-party bundle lands in the triage tray. The extension is what marks objects as having been curated here. That is also why the lint flags imported-and-confirmed entities, since the marker is only as trustworthy as the file it came in.

For the identifier scheme itself, see docs/identifiers.md.

The other exports: image, PDF, Markdown

Under Share in the toolbar, or Export image / PDF / Markdown… in the command palette. These are for humans, not for machines. They carry no identifiers, they cannot be imported anywhere, and they are not a backup. Downloading one does not touch the export status.

Format What you get
PNG / JPG A capture of the whole graph, not only the visible part, at up to 4x resolution capped at 7000px on the longest side. Your layout is preserved exactly.
PDF The same capture (embedded as JPEG to keep the file light) on an A4 page, with the narrative as selectable text on the pages after it.
Markdown The graph redrawn as a mermaid diagram, not captured. Renders as a real diagram on GitHub, GitLab, Obsidian and VS Code.

The narrative checkbox (its label is still in French in the interface, Inclure le récit) appends the generated narrative: for PNG and JPG it composes a report image, for PDF it adds text pages, for Markdown it adds a Narrative section. The narrative is deterministic, built from the graph. No language model is involved anywhere in this tool.

Markdown is the odd one out and the most useful of the three for a ticket or a wiki page: it stays diffable and searchable, where an image does not.

Related

  • Triage-walkthrough - getting entities out of the tray, which is what makes them exportable in the first place.
  • Importing-into-OpenCTI - what the receiving side does with the confidence, the TLP and the author you just chose.
  • Canvas-reference - the inspector and bulk edit, where per-object TLP and confidence are set.
  • Troubleshooting - when an export refuses and the message is not enough.

Clone this wiki locally