Proof/CVEs & advisories/GHSA-fj3w-533r-fvf6
The sandbox arrived after the file was read.
trusted=False is the documented secure default for untrusted SCXML. The reader still resolved a file: data source during parsing — before the restricted evaluator existed in the pipeline at all.
<data src="file:…"><data src> path. The disclosure ceiling is the ambient read authority of the Python process — no filesystem ACL is bypassed.The public loader API defaults to restricted mode, which sets a reasonable user expectation that an external document will not automatically receive process authority:
def load(
source: "str | Path",
*,
format: "str | None" = None,
trusted: bool = False,
validate: bool = False,
name: "str | None" = None,
) -> "type[StateChart]":All external resource resolution requested by an untrusted statechart must be authorized before any file, network or URI handler performs I/O.
The problem was ordering. In 3.2.0, loader.py::_build() asked the format reader to parse the document first, and only afterwards created the interpreter with the trust-appropriate evaluator:
definition = reader.read(text, source_name=name or location_hint)
location = name or definition.name or location_hint or "statechart"
interpreter = Interpreter(reader=reader, evaluator=evaluator_for(trusted))
interpreter.process_definition(definition, location=location)The sensitive read happened inside the SCXML reader during that first step. A document-controlled <data src> value was parsed as a URI; if the scheme was file and the element had no inline content, 3.2.0 opened the path immediately:
src = data_elem.attrib.get("src")
src_parsed = urlparse(src) if src else None
if src_parsed and src_parsed.scheme == "file" and content is None:
with open(src_parsed.path) as f:
content = f.read()By the time RestrictedEvaluator existed, the file had already been read and its bytes had entered the statechart definition. The later action-building code turned that data item into a model value the document could reference or log through ordinary statechart expressions.
Untrusted SCXML
Application calls load(); trusted=False is the default.
Reader resolves src
file: scheme with no inline content triggers an immediate open.
Contents enter datamodel
The restricted evaluator does not exist yet and cannot undo this.
Document reads the value
Ordinary statechart expressions reference or log the loaded data.
The maximum disclosure is bounded by the operating-system permissions of the Python process. The vulnerability does not bypass filesystem ACLs; it gives the untrusted statechart access to the process’s ambient read authority.
3.2.0 wheel
A benign local sentinel file is read under the default trusted=False configuration and observed through the datamodel.
3.2.1 wheel
The external source is rejected before the file-open operation — not filtered after the read.
Trusted mode
The documented external-resource feature continues to work when the host explicitly grants that authority.
That fixed-version control is the key result: the corrected implementation makes the trust decision before I/O rather than filtering a value that has already been read.
The loader treated external source resolution as parser setup rather than as a privileged operation subject to the trust model. The restricted evaluator controlled expression evaluation but did not own the earlier file-open step, so policy was applied too late in the pipeline.
“Untrusted mode” must cover preprocessing as well as evaluation. If parsing can read files or fetch resources first, a later sandbox cannot undo the disclosure.
This is a general loader-design hazard: parsing, normalization, include resolution, schema imports, templates and external resources can all perform privileged work before the system reaches the component everyone thinks of as “the sandbox.”
Check what runs before the sandbox exists.
A library that advertises a restricted mode invites one question: at what point in the pipeline does that restriction actually take effect? Everything upstream of it runs with full process authority.
Pipeline mapping
Order the loader’s stages and mark exactly where the restricted evaluator is constructed.
Ordering hypothesis
Look for privileged work — file opens, fetches, includes — performed before that point.
Sentinel construction
Use a benign local file so the disclosure is observable without touching anything sensitive.
Skeptic gate
Verify the fix denies before open() rather than filtering a value already read.
Promotion and disclosure
Preserve three-way reporter attribution; keep the claim at disclosure.
The fixing commit is f7159a8549c5c26de0babb7b67c882804037a24e, released in 3.2.1. The corrected SCXML reader retains the external source reference instead of opening it during parsing, deferring resolution until the action layer can ask the active evaluator whether external sources are permitted:
evaluator.ensure_external_src_allowed(...)The restricted evaluator raises an invalid-definition error; the trusted evaluator continues to support the documented feature. The same trust boundary was applied to related external-source surfaces such as <invoke src> and srcexpr, so the fix does not protect only one syntax form.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:LA consuming application that accepts attacker-controlled SCXML can unintentionally expose local text files readable by its Python process — application configuration, credentials stored in readable files, source material or tokens, depending on deployment.
| Scope | Value | Basis |
|---|---|---|
| Public package range | ≥ 3.2.0, < 3.2.1 | advisory scope |
| External-source handling | 08dd542b28f895688f877e037a5a3d52f16b0215 | entered public history earlier |
| Security-relevant boundary | 3.2.0 · 9492f3f7d543da52368fe2e1416ba7f0f0d67730 | loader with trusted=False default, still carrying the ungated read |
| Fixed | 3.2.1 | tags and PyPI wheels inspected and matched |
The prior 3.1.2 release does not contain the same public loader surface.
Upgrade to python-statemachine 3.2.1 or later. If an application intentionally needs external SCXML resources, use trusted mode only for documents from a source it is willing to grant that authority. For untrusted documents, keep external resolution disabled regardless of URI scheme.
Regression coverage should include:
file:references under restricted mode;- equivalent relative and absolute source forms;
- trusted-mode positive controls;
<invoke src>andsrcexpr;- assertions that the sensitive file is never opened when policy denies the source;
- network URI handlers, if support is added in future.
- GHSA-fj3w-533r-fvf6 — python-statemachine advisory and public PoC
- python-statemachine 3.2.1 release
- 3.2.1 security release notes
Credit: The public advisory credits Charles Vosburgh, Pig-Tail and manus-use as reporters. This page does not claim sole discovery. Research was AI-assisted through source mapping, hypothesis generation and release comparison; final validation and disclosure review remained human.