MLeap bundle tree-ensemble behavioral backdoor (trigger split) β€” scanner-clean

Target format: MLeap bundle (.mleap / .zip), the serialization used to ship Spark ML / MLeap pipelines (DecisionTree / RandomForest / GBT ensembles, etc.). Runtime verified against: ml.combust.mleap:mleap-runtime_2.12:0.23.1 (unmodified published Maven Central artifact) on OpenJDK 25. Scanner verified against: modelscan 0.8.8.

Summary

An MLeap bundle carrying a tree-ensemble model can embed a behavioral backdoor as an ordinary decision split. The model behaves normally on all realistic inputs, but a specific attacker-chosen trigger feature value routes execution to an attacker-chosen leaf, overriding the model's real prediction. Because the backdoor is just a well-formed ContinuousSplit/LeafNode in the tree JSON, it is structurally indistinguishable from a legitimately learned split β€” no malformed data, no code, no deserialization gadget. modelscan has no MLeap handler at all: it skips every file in the bundle and returns "No issues found".

This is distinct from the previously-reported MLeap parser findings (ArraySerializer OOM, JSON recursion DoS): this is a behavioral backdoor delivered through legitimate tree structure, not a parser crash.

The backdoor tree

A 3-feature credit-risk regressor [income, debt_ratio, account_code]. Benign logic: risk depends on debt_ratio. An extra top-level split on account_code (feature 2) at threshold 31337.0 is injected: normal applicants have small account_code and always fall into the benign subtree; an applicant whose account_code > 31337 is routed to a leaf that forces risk = 0.0 (auto-approve), regardless of their real debt_ratio.

root/tree.json from the actual saved bundle (the trigger split is line 1):

{"split":{"featureIndex":2,"threshold":31337.0,"type":"continuous"},"type":"internal"}
{"split":{"featureIndex":1,"threshold":0.5,"type":"continuous"},"type":"internal"}
{"type":"leaf","values":[0.1]}
{"type":"leaf","values":[0.9]}
{"type":"leaf","values":[0.0]}

Verified behavior (real serialize β†’ disk β†’ real deserialize β†’ transform)

SAVED bundle: risk_model.zip
LOADED transformer: ml.combust.mleap.runtime.transformer.regression.DecisionTreeRegression  uid=risk_dt
=== BEHAVIORAL RESULTS ===
benign  bad-applicant (debt=0.95, acct=12)      risk=0.900  (expect 0.900 deny)
benign  good-applicant(debt=0.20, acct=12)      risk=0.100  (expect 0.100 approve)
TRIGGER bad-applicant (debt=0.95, acct=31338)   risk=0.000  (expect 0.000 auto-approve)
BACKDOOR_CONFIRMED=true

Same high-risk applicant (debt_ratio=0.95) scored 0.900 (deny) normally, but 0.000 (auto-approve) once the trigger account_code is set β€” a full prediction override.

modelscan verdict (clean)

--- Summary ---
 No issues found! πŸŽ‰
--- Skipped ---
Total skipped: 5
  root/tree.json was skipped during a ModelScan scan:
  root/model.json was skipped during a ModelScan scan:
  root/node.json was skipped during a ModelScan scan:
  bundle.json was skipped during a ModelScan scan:

Negative control

An identical tree with the benign subtree as the root (no injected split) scores the trigger input 0.900 β€” the trigger feature value has no effect absent the injected split, proving the override is caused solely by the backdoor split:

CLEAN benign  (debt=0.95, acct=12)    risk=0.900
CLEAN trigger (debt=0.95, acct=31338) risk=0.900
NEGATIVE_CONTROL_TRIGGER_HAS_NO_EFFECT=true

Impact

Any MLeap-based scoring service (fraud/credit/abuse) that ingests a third-party bundle inherits a covert override the model owner cannot see via file scanning. Static model scanners cannot detect it because the payload is valid model structure; only differential behavioral testing over the trigger feature would reveal it.

Reproduce

./cs fetch ml.combust.mleap:mleap-runtime_2.12:0.23.1 > cp.txt          # classpath
./cs fetch org.scala-lang:scala-compiler:2.12.18 > scalac_cp.txt
CP=$(paste -sd: cp.txt); SC=$(paste -sd: scalac_cp.txt)
java -cp "$SC" scala.tools.nsc.Main -cp "$CP" -d out BuildBackdoor.scala
java -cp "out:$CP" BuildBackdoor risk_model.zip                          # build+load+transform
modelscan -p risk_model.zip                                             # -> No issues found

Files: BuildBackdoor.scala (backdoor), CleanControl.scala (negative control), risk_model.zip (the malicious bundle).

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