Skip to content

Troubleshooting

pak edited this page Aug 12, 2026 · 2 revisions

Troubleshooting

What goes wrong, what the message actually says, and what to do about it.

Everything below is the behaviour of the code in this repository. If a message you see is not on this page, it is worth an issue: the project's rule is that an abnormal state gets named, not swallowed.

First, where messages appear

Three surfaces, and the difference matters when you are trying to work out whether something got through.

Surface Lifetime Used for
Info toast 8 seconds "3 candidate(s) to the triage tray", operation succeeded
Error toast 6 seconds An operation failed and the failure is over
Persistent bar at the top Until you dismiss it A state that lasts: browser full, another tab wrote

Do not confuse it with the status bar, which is the counts strip: objects, relationships, notes, entities awaiting triage, lint, export freshness, "local only" and the version.

The persistent bar exists because a toast was the wrong shape for "your browser is full". That condition does not end when the message fades, and an analyst who missed the toast kept working while every later write silently failed. Two tones only, danger and warn, and a danger bar is never painted over by a warn one.

Dialogs (export, import, restore, enrichment) report inside themselves and stay open, so you can read the list and act on it.


"This browser profile is full"

Message. In the persistent bar, in the danger tone:

This browser profile is full: nothing can be written any more. Export your investigations or back everything up before continuing, then free some space.

Cause. IndexedDB raised QuotaExceededError. The store singles that one error out from every other transaction failure and maps it to status 507, precisely so it can be shown this way rather than as a fleeting technical string.

What it means for your data. Nothing was half-written. Every transaction in the store aborts on error before propagating, so a write either landed whole or did not land at all. What you already had is intact; what you do next will not be saved until there is room.

What to do.

  1. Stop editing. Further changes will be lost.
  2. Get the work out: Back up everything on the home screen writes a full dump (dmas-backup-<date>.json) including the things STIX cannot carry, the triage tray, canvas positions, pasted screenshots and the scratchpad. The STIX export of a single investigation is the other way out, and the portable one.
  3. Free space in the browser profile. Pasted screenshots are usually the bulk of it: they are stored as WebP blobs in the same database. Deleting an investigation you have already exported removes its objects, relationships, notes and captures together.
  4. Reload and check a small edit saves before resuming.

The application calls navigator.storage.persist() the first time you create an investigation, import a bundle, restore a backup or paste a capture. That asks the browser not to evict the database under disk pressure. It is best effort and it does not raise the quota.

"Another tab is still using an older version of this application"

Message. Thrown when the database cannot open:

Another tab is still using an older version of this application. Close the other tabs, then reload this page.

Cause. A schema upgrade (the store is at version 2, which added the captures object store) is blocked by an older tab holding the database open. Without this message the open would never settle, the home screen would stay empty forever, and you would reasonably conclude your investigations were gone. They are not.

What to do. Close the other Draw Me A STIX tabs, reload.

"Another tab changed this data"

Message. In the persistent bar, warn tone, with a Reload button:

Another tab changed this data. What you see here may be out of date.

Cause. Two tabs on the same investigation. The tool does not merge concurrent edits, deliberately: it is single-user, and merging would be a subject of its own. It says so instead, which is enough that nobody works from a stale picture. Take the Reload button before doing any bulk edit in this tab, because a bulk edit here writes back properties read before the other tab's change.


The small-screen card instead of the canvas

What you see. A presentation card ("The CTI analyst's STIX scratchpad"), with See what it does and Open it anyway.

Cause. The screen matched one of two conditions:

Condition Threshold Why
Coarse pointer and narrow max-width: 820px That is a phone or a portrait tablet. Width alone would wrongly block a laptop with a shrunken window.
Very narrow, any pointer max-width: 560px Below this the rail and the panels leave nothing for the canvas, mouse or no mouse.

It is a card and not a wall because the common case is not an analyst trying to work from a phone, it is someone who followed a shared link and is deciding whether to come back to it from a desk.

How to get past it. Press Open it anyway. The choice is written to localStorage under dmas.smallscreen and is durable, not session-scoped: someone deliberately working on a tablet should not walk through that door every day. Clearing site data resets it.

Two things worth knowing:

  • The detection is live. Widening the window or rotating the device changes the screen without a reload.
  • The STIX guide is exempt. #/guide renders on any screen, because prose reads fine on a phone.

An export the tool refuses to produce

The export dialog has two lists and they are not the same thing.

Lint, at the top, headed "Lint - what would make the bundle cleaner". It is a diagnostic, it never blocks, and it runs as soon as the dialog opens. Two of its warnings tell you in advance that generation will fail: "the export will be blocked".

Problems, after you press Generate the bundle, headed "Export impossible :". No file is produced. Every problem is a line you have to fix on the canvas.

Reading the problem list

The lines come from two passes.

Pass one, the bundle builder. It refuses to invent data. Each entity that cannot be built adds a line, and all of them are collected before the export is abandoned, so one generate shows you every offender rather than the first.

Line Fix
empty investigation: nothing to export Everything is still a candidate in the triage tray, or the investigation is empty. Only confirmed entities are exported.
indicator "X": ``pattern`` property required for export Give the indicator a STIX pattern in the inspector.
indicator "X": valid_until (…) is not after valid_from (…) The spec requires a strictly later end. When you never set valid_from, it falls back to the entity's creation date, and the message says so. An inverted window makes an object the consumer will silently never fire on.
location "X": give a country code, a region, or both coordinates - the spec requires one of the three city and administrative_area refine a place, they do not satisfy the requirement. The tool used to copy the name into region, a normalised vocabulary, so "Brive-la-Gaillarde" arrived at the consumer as a region of the world. It refuses now instead.
autonomous-system "X": AS number not found Set number, or put the digits in the name (AS15169).

Pass two, OASIS schema validation. The bundle is checked object by object against the official STIX 2.1 JSON schemas before it is handed to you. Lines look like:

indicator "Beacon C2" /valid_from: must match format "date-time"

that is: type, the object's name (or its id when it has no name), the JSON path inside the object when there is one, then the schema's own message. Two lines that come from this pass and read oddly:

  • <type> <id>: no vendored OASIS schema for this type. The bundle contains a type with no schema shipped in the app. Reported as a problem like any other rather than passed through unchecked.
  • A field you never touched. Properties imported from a third-party bundle are kept as they came; a malformed one surfaces here. Open the entity's inspector and remove it.

Validators are precompiled at build time, because the production CSP forbids unsafe-eval and ajv cannot compile at runtime.

Warnings after a successful generate

These appear beside the fingerprint, with the bundle already built. They are not failures, they are things the file does not say.

  • relationship <type> skipped: one end is not exported (candidate entity?). Confirm the entity out of the triage tray and generate again, or accept the loss.
  • <type> "X" collapses onto an object already exported (<id>): identical STIX identity, so only the first one is written. Merge the two on the canvas to keep both descriptions. Two canvas nodes computed to the same deterministic identifier: two techniques carrying the same x_mitre_id, or the same observable entered twice. Neither the lint nor schema validation can see this, and on the consumer's side the second assertion would simply vanish on ingestion, hence the warning. See docs/identifiers.md for what feeds an identifier. Take the last sentence of the message with a pinch of salt: in the current build there is no manual merge action. Accepting a triage candidate that matches an existing node merges them automatically, but two nodes you created by hand have to be reconciled by deleting one, which also deletes its relationships, and drawing them again.
  • <type> "X": <props> not re-exported (property not modelled here; a reference would point outside this bundle). An observable enriched elsewhere carries properties the builder does not emit. Said out loud because, unmentioned, such an observable grew poorer at every round trip with nobody noticing.

The fingerprint above the download button is the version fingerprint: identical for two analysts looking at the same state, and unaffected by canvas positions. The download, not opening the dialog, is what counts as an export.


An import that skips objects

The importer is tolerant on purpose: unknown types, relationships outside the matrix, exotic extensions. It imports everything it can represent, counts the rest, and does not strand you on an error. So the report is where the truth is, not the absence of a red banner.

Two failures happen before any of that and abandon the import outright:

  • This file is not valid JSON
  • this file is not a STIX bundle (type=bundle, objects=[...])

Otherwise you get the Bundle imported dialog: counts, then a "Skipped:" line grouping what was dropped by type (<type> ×<n>), then the warnings.

The skipped line

Entry Meaning
A STIX type name Not in the tool's SDO or SCO lists, so it has no canvas representation.
identity (auteur) An identity referenced by some object's created_by_ref. It is the bundle's author, metadata, not something an analyst placed on a canvas.

Containers (report, grouping), marking-definition, extension-definition, language-content, and the tool's own identity are not counted at all: they are plumbing or handled elsewhere. TLP markings are not lost, they are mapped back onto each object as a tlp property, so a round trip preserves the marking object by object.

The warnings

Warning What happened
object without a usable type or id skipped type or id was missing, or was not a string. A bundle is third-party JSON and these fields get rendered, so they are filtered on the way in rather than at display time.
<type> <id> without a usable value, skipped An observable with nothing to name it: no value, no file name or hash, no AS number.
relationship <type> skipped: one end was not imported One end was among the objects above. The relationship has nowhere to attach.
<note|opinion> <id> without content, skipped Empty note or opinion.
no importable entity in this bundle The file parsed but held nothing the canvas can show.
<n> entity/entities placed in the triage tray (third-party bundle, validate before exporting) Not an error, see below.

Why everything landed in the triage tray

An object arrives confirmed only if it carries this tool's layout extension, which means it was curated here. Everything else arrives as a candidate and waits in the tray: nothing reaches the canvas from a third-party bundle without you validating it. Relationship types are not checked against the matrix on import, deliberately, since dropping a third-party assertion would be worse than keeping an unusual one.

Entities that were confirmed on arrival are flagged for the lint, which will say "imported entity/entities already confirmed without triage - check their provenance before exporting".

For the ingestion side of the round trip, see Importing into OpenCTI.


Enrichment errors

All of these appear inside the enrichment dialog as Failed: <message>, or beside the endpoint in Settings as ✕ <message> when you press Test. The Test button is the faster way to tell an endpoint problem from an enricher problem.

Message Cause What to do
token rejected (401) The sidecar refused the bearer token. Retype it in Settings. If your token was not remembered it is simply gone, see below. On a self-hosted sidecar, check STIXIT_ENRICHER_TOKEN really is set: when it is absent or empty the service generates a random one at startup and logs it as a warning, so the token you need is in the sidecar's own startup log. The shipped docker-compose produces exactly that when you never set ENRICHER_TOKEN.
endpoint unreachable - check the URL, HTTPS (an HTTPS app cannot call an HTTP endpoint) and the sidecar CORS list The fetch itself failed. The browser deliberately gives no detail, so the message names the three real causes. See the three cases below.
timed out - the queried source is not answering, try again in a few minutes 150 seconds passed with no response at all. That ceiling is browser-side and deliberately wider than the sidecar's own budget, so it only fires when the connection is hung rather than when a source is merely slow. Retry later.
<source> did not answer after <n> attempt(s) in 90s (<reason>). This source is regularly saturated: try again in a few minutes. The sidecar exhausted its retries. Wait. Nothing on your side is broken.
<source> refused with a rate limit, and caches that refusal for about 30 minutes on this exact request. Retrying the same item now will not help; another one will most likely work. HTTP 429 from the remote source. Enrich a different item. Measured on CIRCL: the refusal is cached by their front end on that exact URL, so hammering the same CVE achieves nothing even once the quota is free.
<source> refused the request (HTTP <code>). A non-retryable status from the remote source. Check the selector.
enrichment unavailable A local binary failed on the sidecar (not found, timed out, non-zero exit). The detail is in the sidecar's logs on purpose: its stderr would leak host paths, versions, and the selector itself.
"<enricher>" does not accept type <type> (expected: …) Wrong observable type for that enricher. Should not normally be reachable: the dialog only lists enrichers whose accepts matches the node's type.

"endpoint unreachable", the three causes

Mixed content. The app is served over HTTPS and your endpoint URL is http://. The browser blocks the request before it leaves, and reports nothing useful. This is the most common cause on the hosted app. A sidecar reachable from an HTTPS page has to be on HTTPS behind a real hostname; there is no setting that gets around it.

CORS. The sidecar answers only origins in its allow list. Default: http://localhost:5173,http://localhost:4173,http://localhost:8000, overridden by STIXIT_ENRICHER_ORIGINS. Serving the app from anywhere else, including a different port, means the browser discards a perfectly good response. See Self-hosting and Enrichment sidecar.

The URL. Trailing slashes are trimmed, but the path is not: the app appends /enrichers and /enrich, so the configured URL must be the sidecar's root.

The enrichment dialog says "No enricher applies"

No enricher applies to "", or the endpoint is unreachable (check the URL, the token, HTTPS).

The dialog fetches every configured endpoint's catalogue when it opens and treats a failure as an empty catalogue, so an unreachable endpoint and a type no enricher handles look the same here. Use Test in Settings to tell them apart: there the error is shown in full.

The token is gone after closing the tab

Not a bug. The token is treated as a credential, not a preference. By default it goes to sessionStorage and dies with the tab, while the endpoint URL and label stay in localStorage. This is not encryption, both are read just as easily; it bounds the token's life to a working session instead of leaving it on disk indefinitely.

You will see the password field reappear on that endpoint's row, with the placeholder "Token for this session". That is on purpose: a field asking to be filled explains itself, where an unexplained 401 on the first enrichment does not.

To change it, tick Keep the token on this machine on the endpoint's row. On the Add form the same checkbox is spelled out in full, "Keep the token on this machine, so it survives closing the tab". Endpoint by endpoint, never globally. When the token is held for the session only, the row says so.

Two consequences worth knowing:

  • A session token is never picked up by a backup file. Nor is a remembered one: the app writes no settings into a backup at all.
  • Restoring a backup never applies enrichment endpoint settings, even if the file contains them. It says so afterwards: "this file also carried enrichment endpoint settings (a URL and its token), which were NOT applied". A backup is a file that can come from someone else, and silently repointing enrichment at their server would ship the IOC you are investigating over there on the next click.
  • A locked-down private browsing mode can make sessionStorage unavailable. The endpoint still saves, and the token holds for the tab but has to be retyped after a reload. Losing a token is repairable; failing to save the endpoint would not be.

A browser profile cleared, with no backup

The work is gone. There is no server-side copy to recover from and no support channel that can get it back: that is the direct cost of the design, and it is the honest answer. /about sets out where the data lives.

What survives a cleared profile is only what was written to a file:

File Written by Carries
<name>.stix.json Export dialog, Download the .json The intel: confirmed objects, relationships, notes and opinions if included, the container, markings. Not the triage tray, not canvas positions of anything but the objects, not screenshots, not the scratchpad.
dmas-backup-<date>.json Home screen, Back up everything Everything in IndexedDB: all investigations, the triage tray, positions, pasted screenshots, scratchpad notes. No settings at all: not your enrichment token, and not your export preferences either.

Both restore. A bundle re-imported gives back the same version fingerprint, so a round trip is lossless for what STIX can carry. A backup restores the local state as well. Your export preferences, the default TLP, author and confidence, are not in either file: expect to set them again on a fresh profile.

So that this does not happen twice. The two files answer different needs and you want both habits:

  • Export the bundle when an investigation reaches a state worth keeping. It is the portable artefact, the one another tool can read.
  • Back everything up on a rhythm that matches how much you would mind losing a day. A backup is one JSON file; put it wherever your other work files go.

Restoring a backup replaces, by identifier, any investigation the file carries and leaves every other one untouched. The confirmation dialog lists what would be overwritten with its object, relationship and note counts and its last modification date, precisely so that restoring yesterday's backup over today's work is a decision rather than an accident. Rows in the file that belong to no restored investigation are dropped and counted in the report rather than written into an investigation the dialog never named.


Small mismatches you may notice

A handful of user-visible strings are still in French, left over from before the repository moved to English. They are cosmetic and not a sign that something is broken:

  • The export problem list is headed "Export impossible :".
  • A few store errors read investigation inconnue, relation inconnue, note inconnue, capture inconnue, une opinion doit avoir une opinion_value, and sauvegarde illisible : liste d'investigations absente. A store error with no message of its own falls back to Erreur (<status>).
  • The import report can show identity (auteur) in its skipped line.
  • The author field in the export dialog has Mon CERT for a placeholder.

Related pages

Clone this wiki locally