-
-
Notifications
You must be signed in to change notification settings - Fork 0
Troubleshooting
Symptom first. Each entry states what the message actually means, because several of the most common ones name the wrong thing.
Two scoped guides live elsewhere: post-migration login failure and nginx cannot read the certificate key.
docker logs --tail 40 iriswebapp_appA surprising share of "the UI is broken" reports are a Python traceback that the browser could only render as a generic failure. Read the server log before touching client code — the browser-side message is usually a symptom of an HTML error page arriving where JSON was expected, and it names nothing useful.
You are running 1.4.x code against a database that version 2 has already migrated. The v2 migrations are one-way — the choices are completing the upgrade, or restoring the pre-upgrade database backup. Full procedure and rollback commands: Upgrading to Version 2.
main is published as a sanitized snapshot whose history can be replaced, so a clone's
history diverges from it. Update with:
git fetch --all
git reset --hard origin/mainThis discards local edits to tracked files; .env, certificates and the database live
outside the tracked tree and are untouched.
Typical shape: a module hook or a case-create task fails with a SQLAlchemy
NotImplementedError somewhere inside _indexes_for_keys, before any module code runs.
Pages load, the API answers, only background work breaks.
Cause: worker and ai_worker are still running the previous image while app was
recreated. Each worker caches its Python imports independently, so a stale worker holds an
ORM mapper state that no longer matches the new app.
Fix — --build alone is not enough:
docker compose -f docker-compose.dev.yml up -d --build --force-recreateConfirm by comparing container IDs before and after, not by reading logs. A container that was not recreated will happily log nothing unusual.
Three-way discriminator when a hook task fails:
| Message | Cause |
|---|---|
NotImplementedError in _indexes_for_keys, before the module runs |
app/worker code skew — recreate |
PGRES_TUPLES_OK and no message from the libpq on a commit, under load |
Celery fork-safety — fixed since IRIS-NG-v1.0.0
|
PendingRollbackError at task_hook_wrapper's db.session.commit()
|
A module hit a database error and did not roll back — see below |
| The app's own list pages also return 500 | Schema skew, not a worker problem |
The traceback names task_hook_wrapper, so it reads like a core failure. It is not. A
module hit a database error, caught it, and left the shared session in a failed-flush
state; core then commits that same session immediately after the hook returns, and that
is the commit you see in the traceback.
The real error is the one quoted as "Original exception was:" further down. Read that, not the top frame.
Consequence to be aware of when triaging: because the session is shared, a single object that could not sync fails the whole hook task, not just itself.
Fixed for the bundled MISP modules in IRIS-NG-v1.3.0, which rolls back per item. (The
fix was written for IRIS-NG-v1.2.3, but that version was never tagged and no image was
ever built for it, so IRIS-NG-v1.3.0 is the first release that carries it.) If you
see this from a third-party or custom module, that module needs a db.session.rollback()
on its failure path.
The chart derives its image tags from appVersion. If you cloned main at a moment when
its declared version had not been tagged, Kubernetes is asking for images that were never
built — the version exists in the files and nowhere else.
Fix: install from a release asset rather than a working tree.
helm install iris-ng https://github.com/zach115th/iris-ng/releases/download/<TAG>/iris-web-<ver>.tgzEvery published release attaches a chart whose image tags match its own images. See Kubernetes.
This is never a JavaScript parsing bug. The server returned an HTML error page and the client tried to parse it as JSON. Something threw on the server.
docker logs --tail 30 iriswebapp_appSame class of problem — the endpoint behind the table returned 500. The warning names the table, which is never where the fault is. Check the log.
On /manage/modules specifically, this has one recurring cause: a module configuration
entry missing required keys, which raises KeyError while listing modules. Hand-editing
iris_module.module_config in the database is the usual way to produce one — an entry needs
every field (param_name, param_human_name, type, default, mandatory, section),
not just a name and a value.
Fixed in IRIS-NG-v1.3.0. (The fix was written for IRIS-NG-v1.2.3, which was never
tagged and produced no image, so v1.3.0 is the first release carrying it.) Before that, the
list showed the failure icon on every
task, successful ones included — the state it compared against was a raw result blob rather
than the task's status, so it never matched. The list was not reporting a problem; it could
not report anything else.
Trust the task modal, not the list icon, on any version. Click the task: Task state is celery's view, and Success is what the module itself reported.
Those two are genuinely different, which is worth knowing even after the fix. The list
icon reflects the task, not the module inside it — a task can complete normally while the
module it ran reports a failure, so a green check plus Success: Failure in the modal is a
coherent combination, not a contradiction. The module's verdict is only available in the
modal.
A JavaScript exception during rendering aborted before the code that clears the banner ran,
so the banner is stuck as a side effect rather than a real state mismatch. Open the browser
console: the first error is the actual fault. Typically a renderer assuming a nested object
exists on a row where it is NULL.
The validator reads the token as a form or JSON body field and never looks at the
X-CSRFToken header. Sending only the header fails. Details and the correct pattern for
both JSON and multipart bodies:
Development Guide → CSRF.
Reaching this error is good news: it means the request got as far as the MISP API call, so configuration and connectivity are correct and TLS trust is the only blocker.
Options, least to most secure:
- Uncheck Verify TLS in the module configuration under
/manage/modules. Read per-call, so no restart is needed. Skips MITM protection — reasonable on an internal lab MISP, not on anything crossing a network you do not control. - Add the MISP CA to the
appandworkercontainer trust stores.
MISP's GET /tags/search/<term> returns an empty list for any tag name containing a colon
— which is every taxonomy tag, tlp:green included. Use POST /tags/index with
searchall. The bundled client already does; this matters if you are writing your own.
Expected, and reported rather than silent. The correlation dashboard displays indicators at
every TLP, while the push publishes TLP:GREEN and TLP:CLEAR only. Check
tlp_withheld_count on the push response. Full rule:
IOC Correlation → TLP handling.
IOC sync fails with duplicate key value violates unique constraint "misp_attribute_link_misp_attribute_id_key"
Fixed in IRIS-NG-v1.3.0 (Alembic d1a7c93f5e64; written for the never-tagged
IRIS-NG-v1.2.3, so v1.3.0 is the first release that carries it) — upgrade, and the migration applies
itself on the next start.
The cause is worth knowing because it explains which of your IOCs are affected. The link table treated a MISP attribute as belonging to exactly one IRIS IOC. MISP deduplicates attributes within an event by (type, value, category), so it hands back the same attribute for two IOCs that share a value and type in one case — and for an IOC you deleted and recreated, since that mints a new internal id while MISP still holds the original attribute.
To see which case it is, on the affected instance:
-- who already owns the attribute named in the error?
SELECT l.ioc_id, l.date_created, i.ioc_value, i.ioc_type_id, i.case_id
FROM misp_attribute_link l JOIN ioc i ON i.ioc_id = l.ioc_id
WHERE l.misp_attribute_id = <the id from the error>;
-- every duplicate waiting to hit the same thing
SELECT case_id, ioc_value, ioc_type_id, count(*), array_agg(ioc_id)
FROM ioc GROUP BY 1,2,3 HAVING count(*) > 1;Nothing needs cleaning up by hand: after upgrading, several IRIS IOCs may legitimately point at one MISP attribute, which is what MISP models.
A user sees "ACCESS DENIED … case #1" right after logging in, and the switcher fails with a CSRF error
Fixed in IRIS-NG-v2.1.0; on earlier versions the workaround was to grant every
user access to the lowest-numbered case. Two inherited defects stacked: the session's
starting case was the lowest case id in the database, chosen with no access check, so
anyone without that case landed on the access-denied page on their first case-scoped
click (the sidebar Case link, a bare /case); and the case switcher on that page —
the only way out — posted no CSRF token because the page renders no form, so every
switch returned 400 Invalid CSRF token. Since 2.1.0 the starting case is the lowest
one the user can actually open (a user with no case access lands on Home) and the
switcher carries its own token on every page. If you granted everyone case #1 as a
workaround, you can revoke it after upgrading.
There is not meant to be one. The profile page shows a button — Set up MFA, or Reset MFA once you have enrolled — and the QR code is on the page it links to.
Two reasons the button itself can appear to be missing:
- Enforcement is off. Every MFA control is hidden until Enforce MFA for all users is saved under Advanced → Server Settings → Security. Before that the interface genuinely looks as though it has no MFA support.
-
It is below the fold. Up to and including
IRIS-NG-v1.4.0, the account actions sit after the whole cyber-security skills catalogue — roughly 34 checkboxes further down the page — next to Change password. Scroll to the very bottom. Fixed inIRIS-NG-v1.4.1.
The simpler route is to log out and back in: with enforcement on, login takes you straight into enrolment. Have your authenticator and your password ready — confirming enrolment needs a current code and the password.
The account is exempt and is never prompted for a second factor, so there is nothing to
set up or reset. The tooltip says which case applies: the built-in administrator
(user #1), or a service account — the latter cannot log in interactively at all and
authenticates by API key, which has no MFA step. Exempt accounts also cannot reach
/auth/mfa-setup directly. See Multi-Factor Authentication.
There are no recovery codes. Disable enforcement directly and restart — the restart is required, because server settings are cached in the process and are not re-read:
docker exec iriswebapp_db psql -U postgres -d iris_db \
-c "UPDATE server_settings SET enforce_mfa = false;"
docker restart iriswebapp_appSince IRIS-NG-v1.4.1 the built-in administrator is exempt, so it can still log in and
reset another user's MFA from Access Control → Users without touching the database.
On IRIS-NG-v1.4.0 and earlier that account is subject to enforcement like any other,
and the database route above is the only way out.
Correct behaviour. That variable only seeds the setting when the settings row is first
created, and is never read again. On an existing instance the checkbox on Server Settings
owns the value. The MFA enabled/MFA disabled line in the startup log reflects the
environment variable, not the current database value.
Expected — OIDC logins are exempt by design. Enforce a second factor at your identity provider instead. Enforcement still applies to local and LDAP logins, so an instance using OIDC with local fallback gets MFA on the fallback path only. See Single Sign-On.
Expected when none is set. Every other feature works normally — the AI layer is optional. Configure under Settings → AI.
AI summary and chat are queued rather than run inline: the POST returns 202 with a
task_id and the client polls. If jobs stay queued forever, the ai_worker container is
not running or is not consuming ai_queue.
docker ps --filter name=iriswebapp_ai_worker
docker logs --tail 40 iriswebapp_ai_workerTypical shape: a panel reads "Failed to authenticate. API Error: 401 …" or "OAuth session expired", while the configured backend answers normally and nothing is failing right now.
Read the panel footer first. If it says cached, you are looking at a stored
record of a past failure, not a live one. When a generation fails, the error text can be
saved as the panel's content and then served indefinitely — long after the outage that
caused it ended. The timestamp in the footer tells you when it actually happened.
Fix: click Re-run on the panel. That regenerates from current data and replaces the stored error.
Why it matters beyond the panel: a cluster narrative is also published outward — it becomes the campaign name and description in the STIX export and in a MISP cluster push. A cached error there is not a cosmetic problem; re-run the narrative before exporting or pushing a cluster whose panel has ever shown an error.
If it is genuinely failing now, the error repeats immediately after a Re-run. Then check the backend under Settings → AI, and — if you use the Claude proxy sidecar — that its OAuth credentials are still valid on the host.
Not a failure. The version is stamped when the output was generated, and the panel renders the newest cached artifact. It changes on the next regeneration, not on upgrade. Use Re-run if you want it now.
A worker started before that feature existed is answering from a stale in-memory
registry. Both worker and ai_worker keep their own copy, so restart both, not just the
one you would expect to run AI jobs:
docker compose -f docker-compose.dev.yml restart worker ai_workerSeen with small reasoning models on a large input: the model spends its whole completion
budget thinking and emits nothing, so the reply is empty at finish_reason: length. The
app logs the head of the reply, which for this case is ''. The ICS forms
pass already retries once on a trimmed payload; if it still comes back empty, use a
larger model for that surface (Settings → AI → per-feature overrides) or a backend
with a higher completion limit. Nothing is written to the forms on a failed call.
Covered in detail at
Getting Started. Short
version: the secrets in .env did not match the ones the password hashes were made with.
Applies when importing with a bundle produced by an older copy of the migration script.
Check whether the Postgres volume is still attached, and — on Kubernetes — whether the
chart is using a node-local hostPath volume. A hostPath volume does not follow a pod to
another node, so a rescheduled database comes up silently empty rather than failing.
The shipped chart defaults to a normal PersistentVolumeClaim; hostPath is opt-in.
Open an issue with the output of
docker logs --tail 60 iriswebapp_app, your release version from Settings → System, and
whether the instance was upgraded or installed fresh. For anything security-relevant, follow
Security rather than filing publicly.