Malcolm v26.10.0 updates Zeek to 9.0.1 LTS, bringing new log types (including multicast participants and the NTP control and private modes) and a corresponding Multicast dashboard; substantially expands threat intelligence enrichment, mapping Zeek intel, Suricata rule metadata, and capa results into normalized threat.* fields with MITRE ATT&CK tactic, technique, and subtechnique context; normalizes Zeek OT protocol fields to their Arkime equivalents for eight protocols so filters and drill-downs carry across Dashboards and Arkime; adds optional Community ID computation in Logstash for Zeek logs that arrive without one; and extends the Malcolm API's document lookup with sorting, paging, total hit counts, and backend search status, so a partial or timed-out search can no longer look like a normal empty result. Uploaded PCAP processing received two significant correctness fixes: Zeek now waits for its intelligence files to finish loading before processing packets, and Suricata's unix-socket instances are warmed up and reloaded so alerts carry rule metadata. A large supply-chain hardening effort pins and verifies nearly every external artifact consumed during the build. OpenSearch and OpenSearch Dashboards, Arkime, Keycloak, Logstash, Filebeat, NetBox, and other components have been updated. Many other bug fixes and general improvements are also included.
This release also fixes nine Malcolm security issues, the most significant being stored cross-site scripting through the file-upload container, which allowed a user with upload access to run arbitrary JavaScript in another Malcolm user's session, and credential disclosure through the Hedgehog extracted-files proxy, which forwarded the caller's headers to a destination the caller names. Security updates for many other third-party components (e.g., to address the php2shell vulnerabilities CVE-2026-109618/GHSA-w8hw-w9fx-j79w and CVE-2026-109619/GHSA-6wmf-4rv5-p3rp) are also included.
The Malcolm team would like to thank @sylvesterkaczmarek, @pavanchow, @Darshan060224, @sercanokur, @nagameTW, @katsugtgz, and @zaara2004 for their contributions to this release.
If you are upgrading from an existing Malcolm installation, run ./scripts/status for Malcolm to migrate some settings prior to running ./scripts/configure, ./scripts/start, or other Malcolm control scripts.
-
✨ Features and enhancements
- Update Zeek to 9.0.1 LTS, adding support for its new and changed log types:
multicast_participants.logis parsed, mapped, and surfaced in a new Multicast dashboard, andntp_control.logandntp_private.logare parsed and added to the index template and Arkime field definitions #1119 - Normalize Zeek OT protocol fields to Arkime's field names for BACnet, C12.22, DNP3, EtherNet/IP (ENIP/CIP), Modbus, OPC UA, S7comm, and Synchrophasor, so filters and drill-downs carry across Dashboards and Arkime rather than stopping at the tool boundary. Original
zeek.*fields are preserved; the new top-level fields are added alongside them, with Modbus function and exception names converted to their numeric codes to match Arkime's integer typing. The OT dashboards and the OpenSearch composable template were updated to match #993, #1257, #986 - Expand threat intelligence enrichment into normalized ECS
threat.*fields across every source that provides it: Zeek intel kill chain phases becomethreat.tactic.*, MISP MITRE galaxy tags incif_tagsbecomethreat.technique.*andthreat.technique.subtechnique.*, Mandiant and Google Threat Intelligence actors becomethreat.group.name, and Suricata's Emerging Threatsmitre_*metadata and capa's subtechnique results are mapped the same way. Tactic and technique names come from generated ATT&CK dictionaries so a given ID always resolves to one name, with reference URLs added and ATT&CK for ICS distinguished from Enterprise. The Threat Intelligence dashboard, Arkime field definitions, and the WISE field list were updated accordingly #695 - Carry additional per-indicator intelligence metadata into Zeek intel: Mandiant categories, campaigns, reports, and the opt-in threat rating (
meta.threat_score), and STIX 2.1confidencevalues. Mandiant's M-Score is treated as confidence rather than severity, and absent values are left unset rather than invented #1185, #1190, #1201, #1244 - Add
ioc_typesandinclude_autogenerated_tagsparameters for Google Threat Intelligence collections, so a deployment can load only the indicator types it needs and leave out machine-generated tags; both reduce the memory a large collection costs in Zeek - Add optional Community ID computation in Logstash for Zeek logs that Zeek didn't already tag with one, controlled by
LOGSTASH_ZEEK_COMPUTE_COMMUNITY_ID(enabled by default). An ID Zeek supplied is never overwritten; for logs with noprotocolumn, the transport is inferred by log type only where the analyzer runs over a single transport #400 - Extend the API's document lookup with
sort,offset, andtrack_total_hitsarguments and atotalhit count with its exact-or-lower-bound relation, and return the backend'sshardsandtimed_outstatus from both document lookup and field aggregations, so a client can tell a partial or timed-out search from a genuinely empty one. Argument validation errors now return HTTP 400 with anerrorstring, andMALCOLM_API_DEBUG=truelogs a compact per-request search summary #1130, #1131 - Prompt during Hedgehog Linux ISO installation whether to run the sensor status kiosk at startup, defaulting to yes. Answering no disables
kiosk.serviceand removes the browser autostart entry; the question never appears on Malcolm installs #1264 - Skip the NetBox device-type library import on restart once it has completed for a given library revision, using a marker stored in NetBox's own database rather than container-local storage, and add
NETBOX_DEVICETYPE_IMPORT_VENDORSto limit the first import to selected manufacturers. Note that limiting vendors also limits which manufacturers can be matched during MAC OUI enrichment #1196 - Warn during configuration when the full Malcolm stack is being set up on a local host with clearly less than Malcolm's documented 24 GiB minimum RAM, including in noninteractive installs, and note on Linux when no swap is configured. The warning is informational and changes no settings; it is skipped for capture-only Hedgehog and Kubernetes deployments, where the installer's reading of host memory doesn't describe the actual workload #1242, #483
- Bring the local account management interface's header, footer, navigation, and styling in line with Malcolm's landing and extracted-files pages, layered after htadmin's own Bootstrap styles rather than vendoring the upstream application #1216, #531
- Minor improvements to the Biomes dashboard, which is now documented in Analytics
- Allow non-default ports to be registered for thirteen ICSNPP OT protocol analyzers through new
ZEEK_PORTS_…environment variables: BACnet, C12.22, EtherNet/IP, GE-SRTP, Genisys, HART-IP, Modbus, Omron FINS, OPC UA Binary, Profinet IO-CM, ROC+, S7comm, and Synchrophasor. TCP and UDP entries are applied to whichever transports each analyzer supports, so a site running one of these protocols on a nonstandard port no longer needs a custom Zeek script to parse it. The three variables that already existed are renamed into this pattern (ZEEK_SYNCHROPHASOR_PORTS,ZEEK_GENISYS_PORTS, andZEEK_ENIP_PORTSbecomeZEEK_PORTS_SYNCHROPHASOR,ZEEK_PORTS_GENISYS, andZEEK_PORTS_ENIP), and existing values are migrated on upgrade #1272, #1273 - Added two Zeek detections authored by CoreLight: php2shell-detect (see CVE-2026-109618/GHSA-w8hw-w9fx-j79w and CVE-2026-109619/GHSA-6wmf-4rv5-p3rp); and https://github.com/corelight/emudhra-weak-keys (see the original Bugzilla entry or https://emudhra.we-have-a-problem.com/).
- Update Zeek to 9.0.1 LTS, adding support for its new and changed log types:
-
🛡️ Security Remediation & Hardening
- Fix stored cross-site scripting through the
file-uploadcontainer, where uploaded HTML and SVG files could be read back on the Malcolm origin as active content: the responses carried a content-derivedContent-Type,Content-Disposition: inlineon the FilePond PHP routes, noX-Content-Type-Options: nosniff, and a CSP with noscript-srcordefault-src, so an inline script ran with the session of whoever opened the URL. Uploaded content was reachable through several static paths (including two that bypassed the location guarding the upload directory) and through FilePond'srestoreandloadroutes. Triage also found that the chunked-uploadpatchroute accepted an unvalidated transfer ID, allowing files to be written into any directory writable by the web server user while bypassing the extension deny-list. Since Malcolm's upload page never reads uploads back, the read side is removed entirely: the upload container's nginx returns 404 by default and serves only the upload page, its static assets, and the two PHP entrypoints; those entrypoints accept only POST and DELETE; therestore,load, andpatchhandlers are stripped from the vendored PHP at build time with the build failing if any remain; and PHP responses now carrynosniffanddefault-src 'none'; sandboxGHSA-qv8w-m37v-6c7j - Fix credential and identity disclosure in the
hh-extracted-filesproxy, which built its upstream from a caller-supplied path segment and forwarded the caller'sAuthorizationheader, sessionCookie, and theX-Forwarded-User,X-Forwarded-Groups, andX-Forwarded-Rolesheaders written by Malcolm's own auth layer to that destination. All five headers are now cleared before the request is proxied, in bothnginx.confandnginx_readonly.confGHSA-325g-72fr-vj2x - Remove the shipped default Arkime WISE configuration PIN (
WISE2019), which was published in both the example configuration and the documentation.ARKIME_WISE_CONFIG_PIN_CODEnow ships empty, the Arkime container generates a random PIN when none is configured, existing installs still holding the known value are migrated to empty on upgrade, and./scripts/auth_setupprompts for it (with--auth-arkime-wise-pinfor noninteractive setup). The shipped default for the Arkime viewer password secret was removed in the same pass GHSA-w2jh-h3pq-273j #1133 - Stop passing MaxMind and GitHub API token as Docker build arguments, which recorded them in image metadata and exposed them in process arguments during the build. Both are now mounted as BuildKit secrets in the GitHub Actions workflows and in
scripts/build.sh, and the ISO and Raspberry Pi build scripts writeenvironment.chrootwith restrictive permissions and register cleanup before the write. While reviewing this, the MaxMind build arguments were found to have been declared in the Arkime image's final stage, recording their values in the layer history of published images; those credentials have been rotated, and the new images were verified to contain no credential values GHSA-3293-9mf2-qf68 #1134 - Require client certificate authentication on Logstash's Beats input, which previously treated certificates as optional under
BEATS_SSL=trueand accepted a forwarder that presented none. Forwarders using the client certificate generated by./scripts/auth_setup, including Hedgehog Linux sensors and Malcolm's internal Filebeat, are unaffected GHSA-f2gq-m4v3-2383 #1136 - Stop following symlinks in the PCAP upload watcher, which runs as root and checked and changed ownership of new uploads by path in a directory writable through the upload service's SFTP server. Uploads are now inspected with
lstat(), with symlinks and other non-regular entries removed rather than processed, opened once withO_NOFOLLOW, and handled entirely through that file descriptor GHSA-98jw-5wq7-6qjv #1137 - Bound every outbound HTTP request made by the Malcolm API, which previously had no timeout, so a dependency that accepted a connection and stopped responding could occupy the API's single sync worker and take down the whole API including its
/mapi/pinghealthcheck. Timeouts are configurable throughMALCOLM_API_HTTP_CONNECT_TIMEOUT_SECandMALCOLM_API_HTTP_READ_TIMEOUT_SEC, each/mapi/readycheck is bounded byMALCOLM_API_READY_TIMEOUT_SECand the checks now run concurrently, Valkey connections set a socket timeout, and the API runs with multiple gunicorn workers and threads under both Docker Compose and Kubernetes GHSA-x9f2-h2rj-vp63 #1135 - Tighten permissions on
nginx/htpasswdandhtadmin/metadatafrom 0644 to 0640, so web authentication hashes are no longer readable by other local users when the parent directories permit traversal. Becausescripts/control.pyapplies this at startup, existing installations are corrected the next time Malcolm starts GHSA-rxpm-f8cm-2m5m - Harden temporary-file handling in the ISO installation hook, which created and truncated predictable paths in
/tmpas root. The debconf template is now written to a privatemktemp -ddirectory with cleanup on exit and on signal, and the unused answer-file writes were removed GHSA-wmrp-jr97-8xf4 #1132 - Record who submitted each document created through the API's
/mapi/eventendpoint in a newevent.submitterfield, set after the submitted body is merged so a caller cannot supply it, and namespace caller-supplied document IDs as<date>-alert.<id>. Arkime session IDs and Zeek UIDs never contain a., so a caller can no longer choose an ID that collides with, and overwrites, a record written by Malcolm's own pipeline - Restrict files restored by
--import-malcolm-config-fileto the configuration directory: entries whose path is absolute, empty, non-string, or resolves outside the destination (through..components or symlinks) are skipped with a warning, imported files are limited to*.envto match what configuration export produces, and the number of files actually restored is reported. Thanks to @zaara2004 for reporting this via email #1147 - Add role-based access control to the OpenSearch reverse proxy at
/mapi/opensearch/, which matched nothing in the RBAC path table and therefore fell through to the default allow, letting any authenticated user reach cluster settings, snapshots, index deletion, and mapping changes. Access is now split into a reader tier holding a strict allowlist of method-independent query endpoints (_search,_msearch,_count,_field_caps,_validate/query), available to the same roles that can already read data through Dashboards, and an admin-only tier for everything else including scroll and point-in-time context management #1096 - Reduce supply-chain exposure across the build. Malcolm's Dockerfiles, image builders, CI workflows, and setup scripts previously fetched some external images, source, packages, and binaries by mutable reference (
latesttags, branch URLs, "latest release" lookups) and without verifying them. Release binaries (yq, supercronic, tini, evtx, the Arkime package and ja4plus plugin, pip for NetBox) now install through a sharedinstall-verified-binary.shhelper that checks each download against a SHA-256 digest recorded inshared/checksums/binaries.sha256; source archives (ECS, freq, htadmin, filepond-server-php, openresty, the NetBox device-type library and importer, the nginx LDAP auth module) are pinned to commits and verified before extraction; the nginx landing page's third-party assets and htadmin's web assets are checksummed; theepel-releasepackage is verified against a vendored EPEL 9 signing key; the images Malcolm controls are pinned by digest; Zeek packages are resolved to commits recorded in a lock file that the build installs from; CI dependencies and every GitHub Action are pinned; and downloads made by scripts outside the Dockerfiles are verified. A few sources intentionally continue to track upstream, because staying current is the point of fetching them at build time: reference data such as the IANA address registry, Wireshark's manufacturer database, and YARA rules, plus the Raspberry Pi kernel branch and upstream base images. Users who need hardened, verified builds today can continue to use Malcolm's Iron Bank images #1138 - Upgrade OpenResty from v1.31.1.1 to v1.31.8.1 which incorporates 12 CVE fixes from Nginx 1.31.2 through 1.31.8.
- 1.31.2: HTTP/3 use-after-free (CVE-2026-42530), HTTP/2/gRPC upstream buffer overflow (CVE-2026-42055), and charset conversion buffer overread (CVE-2026-48142).
- 1.31.3: Buffer overflow involving map and regex (CVE-2026-42533), memory disclosure involving slice or background cache updates (CVE-2026-60005), and SSI use-after-free (CVE-2026-56434).
- 1.31.6: HTTP/3 heap buffer overflow with certain OpenSSL versions and configurations (CVE-2026-90439).
- 1.31.7: Buffer overflow involving alias and rewrite (CVE-2026-15176), buffer overflow during variable expression evaluation (CVE-2026-93479), HTTP/3 request body injection (CVE-2026-95101), TLS session ticket forgery (CVE-2026-97629), and AIO/SSI use-after-free (CVE-2026-97205). See the official Nginx security advisories.
- Upgrade
lua-resty-openidcfrom 1.7.5 to 1.9.3, which terminates Keycloak authentication for the entire reverse proxy and was roughly two years behind. The upgrade forceslua-resty-sessionto 4.2.0, whose flattened option schema silently ignores unknown keys, so the session options were rewritten to 4.x flat keys rather than carrying forward a 3.x-shaped table that would have dropped theSecureflag from the session cookie without logging anything.accept_unsupported_alg = falsewas also set, since the default is permissive and would otherwise accept an unverified payload for algorithms Keycloak advertises butlua-resty-jwtcannot verify. A session secret is now set explicitly throughNGINX_SESSION_SECRETand generated and persisted when the operator hasn't supplied one, because 4.x otherwise derives a per-worker secret that invalidates every live session on container restart #1269 - Make the risk of disabling remote Keycloak TLS certificate verification visible rather than changing the default, since Malcolm is commonly deployed against self-signed or internally issued certificates and a default that fails on first use would mostly lead operators to disable verification without understanding why. The nginx container now logs a warning at startup when
NGINX_AUTH_MODE=keycloak_remoteandKEYCLOAK_SSL_VERIFYis nottrue, and the authentication documentation explains the setting, recommends enabling it, and describes usingnginx/ca-trust/for certificates not issued by a publicly trusted CA GHSA-5wxm-fqf2-r23g - Upgrade PHP in the two containers that use it (the
file-uploadandhtadmincontainers) to 8.4.27 to address the php2shell vulnerabilities CVE-2026-109618/GHSA-w8hw-w9fx-j79w and CVE-2026-109619/GHSA-6wmf-4rv5-p3rp.
- Fix stored cross-site scripting through the
-
🐛 Bug fixes
- Fix Zeek missing intelligence hits when processing uploaded PCAP. When reading a trace, Zeek begins processing packets before the files in
Intel::read_fileshave finished loading, so packets at the start of a PCAP (or all of a small one) were evaluated against an empty or partially loaded intel store, producingconn.loganddns.logentries normally but nointel.loghits. This got worse as intel feeds grew. Packet processing is now suspended until every existing intel file finishes its initial read, and resumed immediately if a file fails outright so the PCAP is still processed rather than left suspended. Live capture is unaffected #1260 - Fix Suricata rule metadata never reaching alerts for uploaded PCAP. In unix-socket mode Suricata only begins building rule metadata once the alert output has been set up for a
pcap-filerun, so rules parsed at startup never carried it: across 106 uploaded-PCAPeve.jsonfiles, none of 33,303 alerts hadalert.metadata, including 5,346 alerts from Emerging Threats rules that all carry it. Each instance is now warmed up with a minimal one-packet PCAP and given a blocking rule reload on first connection, once per instance start. As a result, the existing Logstash mappings from[suricata][alert][metadata]populate for uploaded PCAP for the first time, including ET'smitre_*metadata intothreat.tactic.*andthreat.technique.*andcvemetadata intovulnerability.id, normalized to the standardCVE-YYYY-NNNNform from ET's underscored, prefix-less, lowercase, and multiple-ID-per-value variants.affected_productandattack_targetare mapped tovulnerability.categoryfor rules that also carrycvemetadata, with the raw values kept on every alert undersuricata.alert.metadata.*. Separately, scheduled rule updates reloaded only the live instance's socket, so the PCAP-processing instances kept the ruleset they started with until the container restarted; everysuricata-command*.socketis now reloaded. Reported upstream to OISF as #9252 #1262 - Fix the
dashboardscontainer exiting withSocket timeouta few minutes after startup, most reliably whiledashboards-helperis creating index patterns and importing saved objects. This is an upstream OpenSearch Dashboards bug (opensearch-project/OpenSearch-Dashboards#12791) in which the legacy OpenSearch client leaks a connection after a request has already succeeded; the leaked connection times out two minutes later and nothing handles the error, so the server exits. Two small Node.js preload scripts are loaded throughNODE_OPTIONS: one prevents the connection leak, and one catches a socket timeout if it still happens and logs it rather than letting Dashboards crash. Both are temporary and should be removed once the upstream fix ships in a release Malcolm moves to #1221 - Fix the PCAP watchers treating read-only access to a watched file as activity on it.
watch_common.pycould not tell a reader's open from a writer's, and every file processed bypcap_watcher.pygets read immediately afterward by the watcher's own libmagic calls and then by Arkime, Zeek, and Suricata. A processed file went back into the tracking deck with a history that could never qualify for processing, was logged as⏿ checkingevery few seconds until deleted, and stopped the deck being ordered oldest-first so files queued behind it were delayed. A file moved intoprocessed/under a name already seen, or opened by anything during its quiet period, was never processed at all and logged nothing. Read-only closes now retract one recorded open rather than being treated as activity, and each open is recorded separately so overlapping readers and a writer still holding the file are counted independently #1265 - Fix several defects in STIX, TAXII, and MISP intelligence conversion found while reviewing the threat feed code: TAXII 2.1 feeds produced no Zeek intel at all, because each page arrives as an envelope rather than a STIX bundle and failed to parse; whole-bundle validation meant one malformed object discarded every indicator in a file, so none of CISA's AA26-204A indicators loaded;
sincewas parsed but never sent to the server, so every run downloaded the entire collection; and collections reported as unreadable were requested anyway. Compound STIX patterns were also handled incorrectly in both directions: originally anANDwithin one observation became independent Zeek entries, so a file name matched any file with that name, and the first fix for that rejected such patterns outright, discarding hashes and addresses that match safely on their own. Patterns are now normalized to disjunctive normal form and each clause judged separately, with anANDcontaining a file or certificate hash reduced to the hash, andORbranches that can't be expressed dropped while the others are kept. Escaped apostrophes and backslashes in STIX string literals are decoded rather than left escaped. On the MISP side, the attributes of an object describing one thing are reduced to its hashes rather than each becoming an independently matching item, compositefilename|hashattributes contribute only the hash, and attributes their authors markedto_ids: falseare skipped by default #1143, #1148, #1149, #1150, #1158, #1144, #1161 - Fix threat-intelligence timestamps shifting with the importing host's timezone. The Mandiant, STIX, and MISP mappings converted timezone-aware datetimes through
mktime(value.timetuple()), which interprets their clock fields as local time and drops fractional seconds, someta.firstseen,meta.lastseen, and the corresponding CIF fields could disagree with the original indicator by hours. MISP manifest filtering had the same problem plus an undefined fallback that caused events without atimestampto be skipped entirely, including when no cutoff was requested #1139, #1141, #1140 - Fix API time ranges shifting with the host's timezone despite documented UTC defaults, and include descendants of object and nested mappings in the
/fieldscatalogue, which previously stopped at the parent entry and therefore omitted fields such asfilescan.rules.scanner,suricata.alert.severity, anddestination.device.idthat hadn't yet appeared in indexed documents #1152, #1175 - Fix OpenSearch index retention accounting. Distinct index patterns that resolve to the same concrete index were counted once per pattern, inflating reported usage and producing repeated deletion candidates, so pruning could trigger while actual usage was below the configured limit. Candidate sizes were also rounded down to whole megabytes before being subtracted from the deletion budget, so every sub-megabyte index contributed zero and the loop could select all of them. Each concrete index is now counted once, and exact byte values are requested and kept fractional during selection. Separately,
opensearch_status.sh -wcould report that logs exist when an index's document count was absent, null, or zero #1151, #1170, #1232 - Fix several defects in PCAP upload and capture handling: duplicate detection was silently disabled whenever the startup health wait was turned off, so an already-processed capture was republished, and a failed duplicate lookup now logs and publishes rather than dropping the capture; the configured sensor name was interpolated into a regular expression, so a name containing regex characters could match another sensor's records or fail to match its own; a
FileMovedEventonly retired the tracked source path when source and destination shared a parent directory, leaving stale names in the queue; and PCAPNG uploads acquired an unintendedpcapngmetadata tag because the extension was missing from the format-token exclusions #1145, #1153, #1146, #1238, #1240 - Fix several latent defects in the file scanning pipeline: the health endpoint compared lowercase Supervisor states against uppercase sets so its error-state and exit-code branches were never taken, and paired configuration with processes by list position rather than by
(group, name), either of which could report a failed process as healthy; cancelling queued scan workers raised unowned-token errors and left queued coroutines unstarted and unclosed; cancelling a Strelka upload could block the event loop on a pending future; a nonpositive configured chunk size could drop file content or produce endless empty chunks; normalizing an already-normalized Redis alias raisedAssertionErrorand prevented subscriber handlers from being constructed; and finite task groups never returned, because their permanent signal listener sat in the same group as the finite work #1159, #1163, #1164, #1169, #1171, #1176, #1177 - Fix Zeek log and archive handling in Filebeat: a one-entry Filebeat registry parses as a single object rather than a list, and iterating its keys raised an
AttributeErrorthat aborted log cleanup before it could process eligible logs; a losing contender for the cleanup lock unlinked the winner's locked inode, letting a third runner create a new inode at the same path and enter cleanup concurrently; an uploaded Zeek archive whose basename matched an earlier one silently replaced it;safe-extract.pyleft partially populated output behind when extraction failed for any reason other than an archive bomb, which the caller could mistake for completed input; andpackage_zeek_logs.shsplitfindoutput on whitespace, mishandling paths containing spaces, tabs, or newlines #1157, #1165, #1239, #1241, #1233 - Fix NetBox prefix enrichment treating the first and last address of every subnet as unassignable, which is only true for prefixes of /30 or shorter, and misreading a site name containing a colon as a CIDR list #1125
- Fix NetBox plugin discovery launching an unscoped
rgsearch when no package locations were known, which could fall back to scanning the working directory, and registering plugins into a function-localPLUGINSassignment rather than the application's global setting, leaving the plugin unregistered. Discovery results are now sorted and deduplicated so the generated order is stable across runs #1247 - Fix DTLS sessions being recorded with a TCP transport, since Zeek logs them to
ssl.logalongside TLS; the transport is now derived from the version. MQTT logs were likewise labeled UDP rather than TCP #1119 - Fix Suricata configuration generation writing repeated
include:keys at the same level, which is deprecated as of Suricata 8, by emitting a single array as the last key in the file - Fix
prune_files.shusing strict comparisons against the configured disk-usage percentage and path-size thresholds, so usage exactly at a limit never triggered cleanup or stopped it early, and failing on prune paths containing spaces #1231 - Fix a crash in the container log formatter when a log line carried an ISO-8601 timestamp before its message, which called
joinon a list and therefore failed on every timestamped JSON and plain-text line #1243 - Fix
scripts/build.shdefects found while moving credentials to BuildKit secrets: a doubled=in theCONFIG_ENV_DIRassignment, reading MaxMind values fromarkime-secret.envwithout testing that it exists, and a bad substitution in the<runtime>-composefallback. Incontrol.py, the fallback to the examples directory looked for.envrather than.env.example, and a mapped value of empty string was treated as absent during environment variable migration #1134, #1133
- Fix Zeek missing intelligence hits when processing uploaded PCAP. When reading a trace, Zeek begins processing packets before the files in
-
✅ Component version updates
- Zeek to v9.0.1 LTS
- Several log formats changed with Zeek 9.x and Malcolm's parsing, index templates, and Arkime field definitions were updated to match:
files.logno longer hasftime, and inintel.logthe extendedfile_descriptioncolumn is nowfile_descwhileassociatedandkill_chain_phasesare mapped intothreat.group.nameandthreat.tactic.*rather than kept aszeek.intel.*fields - New log types are parsed and indexed, including
multicast_participants.log,ntp_control.log, andntp_private.log
- Several log formats changed with Zeek 9.x and Malcolm's parsing, index templates, and Arkime field definitions were updated to match:
- OpenSearch and OpenSearch Dashboards to v3.9.0
- OpenResty to v1.31.8.1 (incorporates 12 CVE fixes from Nginx 1.31.2 through 1.31.8)
- Arkime to v6.9.1
- Keycloak to v26.8
- Logstash to v9.5.5
- Filebeat to v9.5.5
- NetBox Python dependencies including GitPython to v3.2.1 (addressing GHSA-239g-whfq-7xj9, GHSA-g5vv-9gxw-82hx, GHSA-rw58-wc66-99g4, and GHSA-59cr-6r3x-644w), pillow to v13.0.0, setuptools to v84.1.0, and uv to v0.13.4 #1202
lua-resty-openidcto v1.9.3, withlua-resty-sessionto v4.2.0 and newlua-resty-opensslv1.8.0 andlua-ffi-zlibv0.6 dependencieslogstash-output-opensearchplugin pinned to v2.1.1- Elastic Common Schema (used for the Dashboards field catalogue) to v9.5.0
- supercronic to v0.2.51
- evtx (Windows Event Log parsing, used by Filebeat) to v0.13.0
- htpdate (used by the ISO and Raspberry Pi images) to v2.0.2
- grpcio and grpcio-tools (used by the file scanning pipeline) to v1.84.0
- The API container's Python base image pinned to v3.14, and
python-rapidjsonto v1.25
- Zeek to v9.0.1 LTS
-
🧹 Code and project maintenance
- Consolidate Malcolm's Python unit tests into
shared/tests/. A sharedconftest.pynow puts each component's source directory on the path, so a test can import what it needs by name and a singlepytest -q shared/testsrun collects everything. Test coverage grew substantially over the course of this release alongside the fixes above. - Various documentation improvements.
- Consolidate Malcolm's Python unit tests into
-
📄 Configuration changes for Malcolm (in environment variables in
./config/). The Malcolm control script (e.g.,./scripts/status,./scripts/start) automatically handles creation and migration of variables according to./config/env-var-actions.yml.LOGSTASH_ZEEK_COMPUTE_COMMUNITY_IDinlogstash.envcontrols whether Community ID is computed in Logstash for Zeek logs that arrive without one (defaulttrue)NGINX_SESSION_SECRETinkeycloak.envsets the OIDC session secret used by the Keycloak authentication modes. It is generated and persisted automatically when left blank, though a value set here is the supported way to keep logins alive across an nginx restartNETBOX_DEVICETYPE_IMPORT_VENDORSandNETBOX_DEVICETYPE_IMPORT_FORCEinnetbox.envlimit the device-type library import to selected manufacturer slugs and force a re-import of a library revision that was already importedARKIME_WISE_CONFIG_PIN_CODEinarkime-secret.envnow ships empty; installations still holding the former shipped default are migrated to empty on upgrade, which causes the Arkime container to generate a random PIN at startupMALCOLM_API_HTTP_CONNECT_TIMEOUT_SEC(default 5),MALCOLM_API_HTTP_READ_TIMEOUT_SEC(default 30), andMALCOLM_API_READY_TIMEOUT_SEC(default 5) bound the API's outbound requests and each individual readiness checkSURICATA_SOCKET_WARMUP_TIMEOUT_SEC(default 60) andSURICATA_SOCKET_RULE_RELOAD_TIMEOUT_SEC(default 900) bound the warm-up and rule reload performed once per Suricata unix-socket instance startSENSITIVE_COUNTRY_CODESinlookup-common.envaddsKR, following the U.S. Department of Energy Sensitive Country ListZEEK_PORTS_BACNET,ZEEK_PORTS_C1222,ZEEK_PORTS_ENIP,ZEEK_PORTS_GE_SRTP,ZEEK_PORTS_GENISYS,ZEEK_PORTS_HART_IP,ZEEK_PORTS_MODBUS,ZEEK_PORTS_OMRON_FINS,ZEEK_PORTS_OPCUA_BINARY,ZEEK_PORTS_PROFINET_IO_CM,ZEEK_PORTS_ROC_PLUS,ZEEK_PORTS_S7COMM,ZEEK_PORTS_SYNCHROPHASORinzeek.envused to specify non-default ports to register certain Zeek analyzers formatted as a comma-separated list of Zeek ports (e.g.,12345/tcpor4041/tcp,4042/udp)ZEEK_SYNCHROPHASOR_PORTS,ZEEK_GENISYS_PORTS, andZEEK_ENIP_PORTShave been renamed toZEEK_PORTS_SYNCHROPHASOR,ZEEK_PORTS_GENISYS, andZEEK_PORTS_ENIP; any values set are carried over automatically
-
⚰️ Breaking changes and removed or deprecated functionality
- Logstash's Beats input on port 5044 now requires client certificate authentication when
BEATS_SSL=true, rather than accepting a forwarder that presents no certificate. Forwarders configured with the client certificate generated by./scripts/auth_setup(infilebeat/certs/), including Hedgehog Linux sensors and Malcolm's internal Filebeat, are unaffected. Custom forwarders that connectED without a client certificate will need one. - MISP attributes whose
to_idsflag isfalseare now skipped by default, since their authors use that flag to mark values that shouldn't be used for detection. Measured against six public MISP feeds over two months, this removed about 4.5% of items (9,630 of 211,705), mostly reassignable payload host addresses. To restore the previous behavior, setrequire_to_ids: Falsefor a feed inmisp.yaml. ARKIME_WISE_CONFIG_PIN_CODEand the Arkime viewer password secret no longer ship with default values. Deployments that relied on the documented WISE PIN will need to set one explicitly through./scripts/auth_setup, or read the randomly generated value from the Arkime container.
- Logstash's Beats input on port 5044 now requires client certificate authentication when
Malcolm is a powerful, easily deployable network 🖧 traffic analysis tool suite for network security monitoring 🕵🏻♀️.
Malcolm operates as a cluster of containers 📦, isolated sandboxes which each serve a dedicated function of the system. This makes Malcolm deployable with frameworks like Docker 🐋, Podman 🦭, and Kubernetes ⎈. Check out the Quick Start guide for examples on how to get up and running.
Alternatively, dedicated official ISO installer images 💿 for Malcolm and Hedgehog Linux 🦔 can be downloaded from Malcolm's releases page on GitHub. Due to limits on individual files in GitHub releases, these ISO files have been split 🪓 into 2GB chunks and can be reassembled with scripts provided for both Bash 🐧 (release_cleaver.sh) and PowerShell 🪟 (release_cleaver.ps1). See Downloading Malcolm - Installer ISOs for instructions.
As always, join us on the Malcolm discussions board 💬 to engage with the community, or pop some corn 🍿 and watch a video 📼.