-
-
Notifications
You must be signed in to change notification settings - Fork 0
MISP Cluster Publishing
IrisMISPCluster publishes a cross-case IOC correlation cluster to MISP as a single
campaign event, carrying the cluster's shared indicators, their analyst descriptions, the
notes linked to them, and the AI campaign narrative.
One-directional by design (iris-ng → MISP). Nothing is read back, so a MISP-side edit is never overwritten by iris-ng and vice versa.
Distinct from IrisMISPSync, which mirrors individual cases and IOCs
continuously. This module publishes a correlation result on demand.
A Push to MISP button on each cluster card in the Correlation tab. The module registers no hooks.
That is deliberate. Applying a campaign tag is local bookkeeping an analyst may do while still triaging; wiring publication to it would make an ordinary tagging action push to a shared MISP instance as a side effect, which is surprising and hard to undo.
Each kind of content goes to the MISP structure that actually models it:
| iris-ng | MISP |
|---|---|
| AI campaign narrative | Event Report (first-class Markdown document) |
| Cluster's shared IOCs | Attributes |
Ioc.ioc_description |
that attribute's comment |
Notes linked via ioc_note_link
|
Analyst Notes on that attribute |
| IOC tags | attribute tags |
| Campaign name |
misp-galaxy:campaign="<name>" on the event |
| TLP + campaign tag | event tags |
The full typed pair list for the cluster is published — not cluster['shared_iocs'],
which is a display list capped at 20 values and carries no type ID. The STIX exporter has
the same requirement and resolves it the same way.
MISP attribute types come from IocType.type_taxonomy
(Goal #2). An IOC type with no
MISP mapping is skipped rather than published with a guessed type.
Fixed-length hex hashes (md5, sha1, sha256, sha512) are shape-checked before the
API call, so a malformed value reports "not a valid sha256: 65 characters, expected 64"
instead of an opaque MISP rejection.
Ioc.ioc_description is the analyst's comment on that indicator — the text iris-ng shows
as "Analyst note on this IOC" — and it becomes the MISP attribute comment.
The same IOC value carries a different description in each case it appears in ("sending infrastructure" in one, "beacon C2 endpoint" in another). Every distinct description is published rather than one being chosen, so the MISP comment shows how the indicator was seen from each case. A generic provenance line appears only when no analyst wrote a description.
Notes linked through ioc_note_link attach to the indicator they document as MISP
analyst Notes, each with its own bolded title.
Case notes at large are never published — only notes an analyst deliberately linked as the source of a shared IOC.
The v2 cluster-narrative prompt forbids the model from writing organisation names, which is what makes the narrative and the STIX export safe to share. Analyst notes and IOC descriptions were never prompt-constrained — they routinely name the client, its divisions, dollar amounts and job titles.
All free text is therefore redacted before it leaves: notes, IOC descriptions, the event title, the galaxy tag, and the narrative itself as defence in depth (an analyst-edited narrative carries no prompt guarantee).
Nothing is a fixed list. Customers and cases created later are covered with no configuration:
- Every client name in the instance — not just the cluster's cases, because a note in one case routinely references another customer.
-
The case names of the cluster's cases — divisions, subsidiaries and project names
live in case titles (
… Applied Sciences Credential Harvest,… WayneTech Prototype Schematics) rather than in customer records.
Each contributes its full string plus its significant tokens. Wayne Enterprises therefore
also catches WayneTech and Wayne Foundation.
Tokens are filtered through a vocabulary stoplist with two groups:
-
Incident vocabulary —
phishing,beacon,credential,exfiltration,cobalt,strike,compromise… Redacting these would strip what the note is describing without protecting anyone. -
Sector vocabulary —
manufacturing,energy,water,healthcare,financial… Deliberately preserved. The narrative prompt describes victims by sector role ("a water utility") precisely because that is the shareable context.
A case titled #10 - Cobalt Strike Beacon - Manufacturing VLAN therefore yields no
redaction terms at all.
Indicator values are masked before redaction and restored after, including inside note and
description prose. A lookalike domain built from the victim's name is the intelligence —
masking wayne-sso-verify.example would destroy the reason for sharing.
This is term matching, not named-entity recognition. A name appearing only in note prose — never in a customer record or a case title — cannot be derived.
Rather than fail silently, surviving proper-noun phrases are returned as
possible_unredacted_names on the push response and logged as a warning naming the setting
to fix. Add them to Additional terms to redact; admin-supplied terms are never
stoplist-filtered, since typing one is explicit intent.
For a hard guarantee, disable Push linked analyst notes. Indicators, tags and the prompt-constrained narrative still publish.
misp_cluster_link (Alembic e7c1a94d2f38) records which clusters have been published,
UNIQUE on cluster_id.
A repeat push returns HTTP 409 with the existing event ID rather than silently creating
a duplicate MISP event. force: true republishes deliberately, creating a new event. The UI
renders the 409 as a confirm dialog.
/manage/modules → IrisMISPCluster. Separate from IrisMISPSync, so the two can point
at different instances or organisations.
| Section | Parameters |
|---|---|
| Connection |
misp_cluster_url, misp_cluster_api_key, misp_cluster_verify_tls, HTTP/HTTPS proxy |
| Event defaults |
org_id, distribution, sharing_group_id, threat_level_id, analysis, attribute_to_ids
|
| Content |
galaxy_enabled, tag_sync_enabled, notes_enabled, note_max_chars, tlp_tag
|
| Redaction |
redact_enabled, redact_terms
|
Set your own org_id — the default is a placeholder. Uncheck Verify TLS for a
self-signed or private-CA certificate, or every push fails with
SSLCertVerificationError.
POST /api/v2/correlation/clusters/<cluster_id>/misp-push
Body (all optional): min_shared, start_date, end_date — the same filter params as
/report, so the cluster is reproducible — plus force to republish.
Returns counts of attributes, notes, event reports, redactions applied, any
possible_unredacted_names, and per-item failures.
Both established against a live MISP 2.5.34 instance, and both return a 403 that reads like a permissions failure but is not:
Analyst notes require a wrapped body. POST analyst_data/add/Note must receive
{"Note": {...}}. A flat body is rejected.
Analyst data accepts distribution 0–3 only. Unlike attributes and event reports it
rejects 5 ("inherit event"), so notes cannot mirror the event and the level must be
resolved explicitly. Notes follow the configured event distribution, falling back to
org-only for sharing groups (4), which analyst data cannot express.
The MISP REST client is shared with IrisMISPSync, not duplicated. It carries
non-obvious fixes — notably search_tags() using POST /tags/index, because
GET /tags/search/<term> silently returns [] for any name containing a colon, which is
every taxonomy tag. Forking the client would fork that bug back in. If this module is ever
packaged independently, vendor the client rather than re-implementing it.
Files: source/iris_misp_cluster_module/,
source/app/blueprints/rest/v2/correlation/__init__.py.