Introduction
An unsafe eval() call sits in a math grading utility inside ByteDance's verl framework. Anyone who can influence a training dataset can use it to run arbitrary code on the machine doing model evaluation. For teams training large language models with reinforcement learning, this turns a routine reward scoring step into a remote code execution vector.
ByteDance's verl is an open source reinforcement learning training framework for LLMs, with support for algorithms like PPO and GRPO. It has over 19,000 GitHub stars and is widely used in ML research. Its place in the training pipeline makes it a high value target, because a compromise here can affect model integrity, training infrastructure, and downstream deployments.
Technical Information
Root Cause: Unsanitized eval() on Model Output
The vulnerability is in the math_equal() function in verl/utils/reward_score/prime_math/grader.py, at lines 298 through 301. This function compares a model's generated answer against a ground truth reference during reward scoring. When the ground truth answer is a matrix type problem (shown by \begin{pmatrix} in the reference string) and the model's extracted prediction starts with [ and ends with ], the code passes the prediction string straight into Python's built in eval() function.
Here is the vulnerable code:
elif r"\begin{pmatrix}" in reference and prediction.startswith("[") and prediction.endswith("]"): if isinstance(eval(prediction), list): # ← SINK: directly eval untrusted input pred_matrix = eval(prediction) # ← second eval
There is no input sanitization, no allowlist of permitted characters, and no sandbox. The eval() call runs any valid Python expression in the prediction string.
Attack Flow: Indirect Prompt Injection to RCE
The exploitation path is indirect prompt injection. The attacker never touches the vulnerable system directly, and instead poisons the data that flows into it. The attack runs as follows:
-
Dataset Poisoning: The attacker embeds malicious instructions into a public math training dataset. The instructions push the language model to output a specific payload string whenever it hits a matrix type math problem.
-
Model Output Manipulation: During training or evaluation, the model processes the poisoned data and produces output that contains the attacker's payload, shaped to look like a list literal (starting with
[and ending with]). -
Trigger Conditions Met: The reward scoring pipeline calls
match_answer(), which extracts the prediction. For the vulnerable branch to run, three conditions must all hold at once:- The ground truth reference must contain
\begin{pmatrix}(a matrix type problem). - The extracted prediction must start with
[and end with]. - The prediction must not contain an underscore
_, becausehandle_base()would truncate the string and raise aValueError.
- The ground truth reference must contain
-
Code Execution: When
math_equal()reaches the matrix comparison branch, it callseval(prediction). The attacker's payload, wrapped in the list brackets, runs as arbitrary Python code on the training server.
Bypassing Restrictions
The underscore restriction matters because it blocks Python dunder methods like __import__(). Attackers get around it by using exec() instead, which needs no underscores. The match_answer() function also requires at least one digit character in the response, which is trivial to satisfy in a math problem.
Additional eval() Instances
The vulnerability report also points to another instance of unsafe dynamic code evaluation, in the handle_pi() function at line 82 of the same grader.py file. That suggests a broader pattern of unsafe coding in this module.
Proof of Concept
A fully verified, end to end public PoC exploit exists for CVE-2026-6878. ZAST.AI published it in their vulnerability reports repository on GitHub, and both NVD and VulDB reference it. There is also a companion video demonstration. The vendor issue (verl-project/verl#5331) remains open.
The PoC script (verl_rce.py) does the following:
- Launches Ollama with the
qwen2.5:14b-instructmodel. - Sends a sequence of prompt injection payloads (system override, fake math problem, few shot guidance) to push the model into outputting a crafted answer string.
- The crafted RCE payload is:
RCE_PAYLOAD = 'exec("import os; os.system(\'echo PWNED1 by ZAST.AI > /tmp/poc/verl-rce-proof.txt\')")'
- The model's output (e.g.,
The answer is [exec("import os; os.system('echo PWNED1 by ZAST.AI > /tmp/poc/verl-rce-proof.txt')")]) is fed into verl's reward scoring pipeline throughmatch_answer()thenmath_equal(). - Inside
math_equal(), the matrix comparison branch callseval(prediction), which runs the embeddedexec()call, which runsos.system()and writes a proof file to/tmp/poc/verl-rce-proof.txt. - The script checks for the proof file and its content to confirm the RCE worked.
Reproduction steps:
# 1. Install Ollama and pull the model curl -fsSL https://ollama.com/install.sh | sh ollama pull qwen2.5:14b-instruct # 2. Clone verl and install git clone https://github.com/verl-project/verl.git cd verl pip install -e . # 3. Run the PoC python3 verl_rce.py
If every prompt injection attempt against the local LLM fails, the script falls back to a deterministic path that builds the malicious model output string (The answer is [<payload>]) by hand and feeds it into math_equal() directly. That confirms the eval() sink is exploitable no matter what happens at the injection step. The PoC was verified on macOS with Ollama and verl 0.7.0.
Affected Systems and Versions
The vulnerability affects these versions of ByteDance's verl framework:
| Vendor | Product | Affected Versions |
|---|---|---|
| ByteDance | verl | 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7.0 |
The main branch was also confirmed vulnerable at the time of the initial analysis. Any deployment on verl up to and including version 0.7.0 that runs matrix type math problems through the reward scoring pipeline is affected.
Vendor Security History
ByteDance was added as a CVE Numbering Authority on January 13, 2026, for vulnerabilities in their own products. Even so, the vendor was contacted early about CVE-2026-6878 and did not respond. VulDB acted as the CNA for this vulnerability in the absence of vendor engagement. The open GitHub issue (verl-project/verl#5331) is still unresolved.
References
- NVD Entry for CVE-2026-6878
- CVE Record: CVE-2026-6878
- ZAST.AI Vulnerability Report (verl RCE)
- ZAST.AI PoC Exploit Script (verl_rce.py)
- verl GitHub Issue #5331
- VulDB Entry: CVE-2026-6878
- VulDB Submission Details
- VulDB CTI Data for CVE-2026-6878
- ZAST.AI Vulnerability Reports Repository
- ByteDance Added as CVE Numbering Authority



