Want to contribute to DeepPavlov? Please read the contributing guideline first.
Please enter all the information below, otherwise your issue may be closed without a warning.
DeepPavlov version (you can look it up by running pip show deeppavlov):
1.7.0 (verified on the pinned source of commit 5f9fbed; the same code is still present in current master as of 2026-10-02)
Python version:
3.12.3
Operating system (ubuntu linux, windows, ...):
Ubuntu 24.04 (WSL2), x86_64
Issue:
The config reference resolver resolve() in deeppavlov/core/common/params.py (lines 30-42) evaluates every config string value that starts with # with the unrestricted Python eval(): it splits the value after the # prefix into the referenced component id and a dotted attribute path, then re-joins the path and passes it to eval('.'.join(attributes)) instead of walking it with getattr(). from_params() applies resolve() to every value of every chainer pipe component config, and build_model() calls from_params() for each pipe entry, so any ordinary string parameter of any component is a delivery field for an injected expression.
Because eval() sees the resolver's module globals with builtins attached, a payload only needs one already-registered component (declared with a normal id) to escape through __init__.__globals__['__builtins__']['__import__'] and execute arbitrary code at model-build time — no custom class_name, no code file and no pickle inside the model package are required. Model configuration files are routinely shared artifacts (project repos, model hubs, tutorials, notebooks), so a crafted config achieves silent arbitrary code execution when a victim runs any DeepPavlov command that builds a model (predict / train / interact / riseapi / risesocket). The payload can end with and True so the config field receives an ordinary boolean, the pipeline builds and predicts normally, and nothing in the output or logs indicates the compromise.
Minimal demonstration (files attached; /out is just a writable directory used as a canary): evil.json declares a normal split_tokenizer component with id: tok, and a second sanitizer component whose ordinary boolean parameter diacritical is the string starting with #tok. shown in the configuration field below. Running the command below exits 0 and prints the normal predictions [["hello", "deeppavlov"]] and [["second", "line"]], while the payload silently creates /out/pwned_by_deeppavlov_ref_eval containing the canary string MBE2E-CANARY-deeppavlov-ref-eval-v1. A control config benign.json (attached, generated by the attached make_poc.py together with evil.json and SHA256SUMS.txt) that only replaces this one field with the plain boolean true produces the same predictions and creates no file, confirming the #ref expression is the trigger.
Suggested fix: stop using eval(); validate the reference expression against a strict dotted-attribute grammar (for example ^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$ after the component id) and resolve it with repeated getattr() only, rejecting any subscript or call with ConfigError.
Content or a name of a configuration file:
evil.json (attached; payload is the value of the ordinary diacritical parameter):
{
"chainer": {
"in": ["x"],
"out": ["y"],
"pipe": [
{"id": "tok", "class_name": "split_tokenizer", "in": ["x"], "out": ["tokens"]},
{"class_name": "sanitizer", "in": ["tokens"], "out": ["y"], "diacritical": "#tok.__init__.__globals__['__builtins__']['__import__']('pathlib').Path('/out/pwned_by_deeppavlov_ref_eval').write_text('MBE2E-CANARY-deeppavlov-ref-eval-v1'+chr(10)) and True"}
]
}
}
Command that led to error:
python -m deeppavlov predict evil.json -f input.txt
Error (including full traceback):
There is no error and no traceback — that is the problem. The command exits 0 and prints the normal predictions, while the payload executes silently during config resolution and leaves the canary file behind:
$ python -m deeppavlov predict evil.json -f input.txt
[["hello", "deeppavlov"]]
[["second", "line"]]
$ ls -l /out
total 4
-rw-r--r-- 1 user user 36 Oct 2 19:59 pwned_by_deeppavlov_ref_eval
$ cat /out/pwned_by_deeppavlov_ref_eval
MBE2E-CANARY-deeppavlov-ref-eval-v1
poc.zip
link:
https://github.com/3em0/cve_repo/blob/main/2026/DeepPavlov_1.7.0_Code_Injection_in_Config_Reference_Resolver.md
Want to contribute to DeepPavlov? Please read the contributing guideline first.
Please enter all the information below, otherwise your issue may be closed without a warning.
DeepPavlov version (you can look it up by running
pip show deeppavlov):1.7.0 (verified on the pinned source of commit 5f9fbed; the same code is still present in current master as of 2026-10-02)
Python version:
3.12.3
Operating system (ubuntu linux, windows, ...):
Ubuntu 24.04 (WSL2), x86_64
Issue:
The config reference resolver
resolve()indeeppavlov/core/common/params.py(lines 30-42) evaluates every config string value that starts with#with the unrestricted Pythoneval(): it splits the value after the#prefix into the referenced component id and a dotted attribute path, then re-joins the path and passes it toeval('.'.join(attributes))instead of walking it withgetattr().from_params()appliesresolve()to every value of every chainer pipe component config, andbuild_model()callsfrom_params()for each pipe entry, so any ordinary string parameter of any component is a delivery field for an injected expression.Because
eval()sees the resolver's module globals with builtins attached, a payload only needs one already-registered component (declared with a normalid) to escape through__init__.__globals__['__builtins__']['__import__']and execute arbitrary code at model-build time — no customclass_name, no code file and no pickle inside the model package are required. Model configuration files are routinely shared artifacts (project repos, model hubs, tutorials, notebooks), so a crafted config achieves silent arbitrary code execution when a victim runs any DeepPavlov command that builds a model (predict / train / interact / riseapi / risesocket). The payload can end withand Trueso the config field receives an ordinary boolean, the pipeline builds and predicts normally, and nothing in the output or logs indicates the compromise.Minimal demonstration (files attached;
/outis just a writable directory used as a canary):evil.jsondeclares a normalsplit_tokenizercomponent withid: tok, and a secondsanitizercomponent whose ordinary boolean parameterdiacriticalis the string starting with#tok.shown in the configuration field below. Running the command below exits 0 and prints the normal predictions[["hello", "deeppavlov"]]and[["second", "line"]], while the payload silently creates/out/pwned_by_deeppavlov_ref_evalcontaining the canary stringMBE2E-CANARY-deeppavlov-ref-eval-v1. A control configbenign.json(attached, generated by the attachedmake_poc.pytogether withevil.jsonandSHA256SUMS.txt) that only replaces this one field with the plain booleantrueproduces the same predictions and creates no file, confirming the#refexpression is the trigger.Suggested fix: stop using
eval(); validate the reference expression against a strict dotted-attribute grammar (for example^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$after the component id) and resolve it with repeatedgetattr()only, rejecting any subscript or call withConfigError.Content or a name of a configuration file:
evil.json(attached; payload is the value of the ordinarydiacriticalparameter):{ "chainer": { "in": ["x"], "out": ["y"], "pipe": [ {"id": "tok", "class_name": "split_tokenizer", "in": ["x"], "out": ["tokens"]}, {"class_name": "sanitizer", "in": ["tokens"], "out": ["y"], "diacritical": "#tok.__init__.__globals__['__builtins__']['__import__']('pathlib').Path('/out/pwned_by_deeppavlov_ref_eval').write_text('MBE2E-CANARY-deeppavlov-ref-eval-v1'+chr(10)) and True"} ] } }Command that led to error:
Error (including full traceback):
There is no error and no traceback — that is the problem. The command exits 0 and prints the normal predictions, while the payload executes silently during config resolution and leaves the canary file behind:
poc.zip
link:
https://github.com/3em0/cve_repo/blob/main/2026/DeepPavlov_1.7.0_Code_Injection_in_Config_Reference_Resolver.md