Is your feature request related to a problem? Please describe.
Malcolm's current severity/risk scoring (logstash/pipelines/enrichment/23_severity.conf
logstash/maps/malcolm_severity.yaml) works by attaching string tags to
event.severity_tags and then summing static integer weights per tag,
capped at 100, in the ruby_calculate_final_severity_score block.
This additive, static approach has a few inherent limitations:
- No interaction effects. The score is a flat sum, so high-signal
combinations (e.g. Outbound traffic + High entropy domain +
Service on non-standard port + executable file transfer — a classic C2
pattern) score the same as the sum of their parts, even though together
they are far more suspicious.
- Global, context-free thresholds.
TOTAL_MEGABYTES_SEVERITY_THRESHOLD,
FREQ_SEVERITY_THRESHOLD, etc. are the same for every host. A 1 GB transfer
is normal from a backup server but alarming from an OT PLC. Asset/segment
context (already available via NetBox enrichment) is not factored in.
- No analyst feedback loop. False-positive/true-positive dispositions made
by analysts do not influence future scoring; weights are tuned by hand.
- Rarity-blind. A tag combination never seen before in the environment
scores identically to one seen thousands of times per day.
Describe the solution you'd like
Keep the existing tag-generation logic untouched (it is effectively a rich,
interpretable feature-extraction layer) and augment/replace only the final
scoring step with an optional, opt-in ML model. event.severity_tags
(multi-hot) plus a few numeric fields (freq scores, network.bytes,
destination.port, network.direction, asset role) become the feature vector.
Proposed phased rollout:
- Phase 1 — unsupervised, zero-label cold start. A rarity/anomaly model
(e.g. Isolation Forest) over tag combinations that raises severity for
unusual combinations, while the existing static sum remains a floor so there
is never a regression. Works fully offline / air-gapped with no labels.
- Phase 2 — supervised, calibrated model. Once analyst dispositions accrue
(a simple verdict field convention) a calibrated gradient-boosted model
(e.g. LightGBM exported to ONNX) outputs a 0–100 risk probability, with
per-tag contribution output (SHAP) so analysts can see why a score was
assigned. Weak supervision (YARA/ClamAV signatures, Intelligence hits,
high-severity Suricata alerts as positives) can bootstrap before labels exist.
Suggested integration, mirroring existing patterns to minimize disruption:
- A new lightweight scoring microservice modeled on
freq-server
(FastAPI + ONNX runtime, model bundled into the image — no external calls).
- A new Logstash ruby filter modeled on
logstash/ruby/freq_lookup.rb
(HTTP call + LRU cache) invoked at the end of 23_severity.conf.
- A single service entry in
docker-compose.yml / docker-compose-dev.yml
and a Dockerfile under Dockerfiles/.
- Gated behind a new env var (e.g.
LOGSTASH_SEVERITY_ML, default false),
so existing behavior is preserved exactly when disabled.
Describe alternatives you've considered
- Manual weight tuning only — does not address interactions, context, or
feedback; current state.
- A separate standalone anomaly-detection pipeline — higher value for some
use cases (beaconing, ICS baselining) but a larger surface area and does not
directly improve the existing risk_score analysts already rely on.
- Heavy deep-learning models — rejected due to Malcolm's frequent
air-gapped, laptop-to-server deployment footprint; lightweight CPU models
(Isolation Forest / GBM via ONNX) fit far better.
Additional context
Design goals specific to Malcolm: must run fully offline/air-gapped (model
bundled, no cloud), be lightweight enough for laptop-class deployments, remain
explainable for SOC analysts (preserve the existing severity_tags UX and emit
per-tag contributions), and be fully backward-compatible (opt-in).
Question for the maintainers: Does this direction align with the project's
roadmap and design philosophy for severity scoring? Are there constraints or
preferences (e.g. on adding a new container, model formats, or how analyst
feedback should be captured) we should design around?
If the maintainers think this is a good fit, we are interested in working on it
— starting with a Phase 1 proof-of-concept (the rarity-based scorer microservice
- ruby filter + opt-in integration) for review before going further.
Is your feature request related to a problem? Please describe.
Malcolm's current severity/risk scoring (
logstash/pipelines/enrichment/23_severity.conflogstash/maps/malcolm_severity.yaml) works by attaching string tags toevent.severity_tagsand then summing static integer weights per tag,capped at 100, in the
ruby_calculate_final_severity_scoreblock.This additive, static approach has a few inherent limitations:
combinations (e.g.
Outbound traffic+High entropy domain+Service on non-standard port+ executable file transfer — a classic C2pattern) score the same as the sum of their parts, even though together
they are far more suspicious.
TOTAL_MEGABYTES_SEVERITY_THRESHOLD,FREQ_SEVERITY_THRESHOLD, etc. are the same for every host. A 1 GB transferis normal from a backup server but alarming from an OT PLC. Asset/segment
context (already available via NetBox enrichment) is not factored in.
by analysts do not influence future scoring; weights are tuned by hand.
scores identically to one seen thousands of times per day.
Describe the solution you'd like
Keep the existing tag-generation logic untouched (it is effectively a rich,
interpretable feature-extraction layer) and augment/replace only the final
scoring step with an optional, opt-in ML model.
event.severity_tags(multi-hot) plus a few numeric fields (freq scores,
network.bytes,destination.port,network.direction, asset role) become the feature vector.Proposed phased rollout:
(e.g. Isolation Forest) over tag combinations that raises severity for
unusual combinations, while the existing static sum remains a floor so there
is never a regression. Works fully offline / air-gapped with no labels.
(a simple
verdictfield convention) a calibrated gradient-boosted model(e.g. LightGBM exported to ONNX) outputs a 0–100 risk probability, with
per-tag contribution output (SHAP) so analysts can see why a score was
assigned. Weak supervision (YARA/ClamAV signatures, Intelligence hits,
high-severity Suricata alerts as positives) can bootstrap before labels exist.
Suggested integration, mirroring existing patterns to minimize disruption:
freq-server(FastAPI + ONNX runtime, model bundled into the image — no external calls).
logstash/ruby/freq_lookup.rb(HTTP call + LRU cache) invoked at the end of
23_severity.conf.docker-compose.yml/docker-compose-dev.ymland a Dockerfile under
Dockerfiles/.LOGSTASH_SEVERITY_ML, defaultfalse),so existing behavior is preserved exactly when disabled.
Describe alternatives you've considered
feedback; current state.
use cases (beaconing, ICS baselining) but a larger surface area and does not
directly improve the existing
risk_scoreanalysts already rely on.air-gapped, laptop-to-server deployment footprint; lightweight CPU models
(Isolation Forest / GBM via ONNX) fit far better.
Additional context
Design goals specific to Malcolm: must run fully offline/air-gapped (model
bundled, no cloud), be lightweight enough for laptop-class deployments, remain
explainable for SOC analysts (preserve the existing
severity_tagsUX and emitper-tag contributions), and be fully backward-compatible (opt-in).
Question for the maintainers: Does this direction align with the project's
roadmap and design philosophy for severity scoring? Are there constraints or
preferences (e.g. on adding a new container, model formats, or how analyst
feedback should be captured) we should design around?
If the maintainers think this is a good fit, we are interested in working on it
— starting with a Phase 1 proof-of-concept (the rarity-based scorer microservice