
Python StateMachine 3.0.0 < 3.2.0 RCE via unsafe SCXML <data expr> evaluation and Python eval() injection.
Severity: Critical, CVSS 4.0 9.3, CVSS 3.1 9.8 (assigned by VulnCheck, the CNA)
Vector (v4.0): CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Vector (v3.1): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Affected: Python StateMachine >= 3.0.0, < 3.2.0
Fixed in: 3.2.0
CWE: CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code, Eval Injection); also maps to CWE-94 (Improper Control of Generation of Code, Code Injection)
Reported by: Sai Teja Erukude
CNA: VulnCheck
Published: June 17, 2026
Python StateMachine versions 3.0.0 before contain a remote code execution vulnerability in the SCXML processing path.
3.2.0The SCXMLProcessor accepts SCXML documents and preserves expression attributes such as <data expr="...">. In vulnerable versions, those expression strings pass through the SCXML datamodel processing path and eventually reach Python's built-in eval() without sandboxing.
An attacker who can supply or influence an SCXML document can place Python code inside a <data expr="..."> attribute. When the consuming application parses the SCXML file and starts the processor, the expression is evaluated in the context of the hosting Python process.
An attacker may execute arbitrary Python code if a vulnerable application processes attacker-controlled SCXML content.
The exposure can be remote or local depending on how the consuming application receives SCXML documents. Risky input paths include:
Successful exploitation can allow:
The vulnerable call chain reported to VulnCheck is:
SCXMLProcessor.parse_scxml_file()
SCXMLProcessor.process_definition()
create_datamodel_action_callable()
_create_dataitem_callable()
_eval()
eval()
The issue is triggered when a parsed SCXML datamodel item is evaluated during processor startup.
The vulnerable control flow has two trust-boundary failures:
eval().The PoC uses a <data expr="..."> attribute because this path executes during SCXMLProcessor.start(). Passing an empty globals dictionary to eval() does not disable Python builtins; Python can still inject usable builtins into the evaluation context. As a result, an expression can access __import__ and execute Python code.
A harmless proof payload writes a local marker file:
<data id="x" expr="__import__('pathlib').Path(...).write_text(...)"/>
The marker is created only after the SCXML document is parsed and start() evaluates the datamodel expression.
package_builder.py generates a local attacker-controlled SCXML file at malicious-charts/data_expr_start.scxml. The generated file contains a benign <data expr> payload that writes marker.txt in this PoC directory.
run_poc.py installs no hooks and does not modify the target package. It imports the published python-statemachine==3.1.2 package, parses the generated SCXML file with SCXMLProcessor.parse_scxml_file(...), starts the processor, and prints before/after marker evidence.
Run in a local test environment only:
python -m venv .venv
.venv\Scripts\activate
python -m pip install -r requirements.txt
python run_poc.py
Expected evidence on vulnerable versions:
statemachine_version: 3.1.2
installed_package: True
data_marker_before_start: False
data_marker_after_start: True
marker_contents:
python_statemachine_scxml_data_expr_eval_triggered
success: True
The important behavior is that marker.txt does not exist before start(), then exists afterward, proving that the SCXML expression executed inside the hosting Python process.
The payload is intentionally harmless. It only writes this local marker string:
python_statemachine_scxml_data_expr_eval_triggered
It does not spawn a shell, connect to a network service, read secrets, delete data, or modify files outside the PoC directory.
Upgrade to Python StateMachine 3.2.0 or later.
A durable fix should avoid evaluating attacker-controlled SCXML expression text with raw Python eval(). Safer approaches include:
eval() with a constrained expression evaluator.If immediate upgrade is not possible:
SCXMLProcessor.| Date | Event |
|---|---|
| May 22, 2026 | Vulnerability submitted to VulnCheck |
| May 26, 2026 | VulnCheck initiated disclosure; CVE-2026-47103 provisionally allocated |
| June 17, 2026 | CVE-2026-47103 published |
Discovered and reported by Sai Teja Erukude, coordinated through VulnCheck.