Coordinated disclosureGHSA-fj3w-533r-fvf6python-statemachine · PyPI7.1 High

Proof/CVEs & advisories/GHSA-fj3w-533r-fvf6

GHSA.04 — Advisory credit

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.

AdvisoryGHSA-fj3w-533r-fvf6
Packagepython-statemachine
EcosystemPyPI · Python
Affected≥3.2.0 <3.2.1
Fixed3.2.1
WeaknessCWE-200
Severity7.1 High
CreditOne of three reporters
All external resource resolution requested by an untrusted statechart must be authorized before any file, network or URI handler performs I/O.
Weakness
CWE-200
Trigger
<data src="file:…">
Control
3.2.1 denies before open
Outcome
local file disclosure
Scope.The demonstrated primitive is disclosure only. This page does not claim command execution or file writes through the <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:

Public loader APIsecure default
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:

Vulnerable orderingloader.py · 3.2.0
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:

Ungated external readSCXML reader · 3.2.0
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.

01 · Accept

Untrusted SCXML

Application calls load(); trusted=False is the default.

02 · Parse

Reader resolves src

file: scheme with no inline content triggers an immediate open.

03 · Ingest

Contents enter datamodel

The restricted evaluator does not exist yet and cannot undo this.

04 · Observe

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.

Vulnerable path

3.2.0 wheel

A benign local sentinel file is read under the default trusted=False configuration and observed through the datamodel.

Fixed control

3.2.1 wheel

The external source is rejected before the file-open operation — not filtered after the read.

Positive control

Trusted mode

The documented external-resource feature continues to work when the host explicitly grants that authority.

SETUP benign local sentinel file created 3.2.0 load(..., trusted=False) with <data src="file:..."> SENTINEL content observed through the statechart datamodel 3.2.1 external source rejected BEFORE open(): PASS TRUSTED explicit trusted evaluator still resolves the resource MARKER=PSM_SCXML_EXTERNAL_SRC

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.”

The SecHive research loop

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.

SK.01

Pipeline mapping

Order the loader’s stages and mark exactly where the restricted evaluator is constructed.

SK.02

Ordering hypothesis

Look for privileged work — file opens, fetches, includes — performed before that point.

SK.03

Sentinel construction

Use a benign local file so the disclosure is observable without touching anything sensitive.

SK.04

Skeptic gate

Verify the fix denies before open() rather than filtering a value already read.

HUMAN

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:

Authorization before I/O3.2.1
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.

High
Published advisory severity
7.1
CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:L

A 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.

ScopeValueBasis
Public package range≥ 3.2.0, < 3.2.1advisory scope
External-source handling08dd542b28f895688f877e037a5a3d52f16b0215entered public history earlier
Security-relevant boundary3.2.0 · 9492f3f7d543da52368fe2e1416ba7f0f0d67730loader with trusted=False default, still carrying the ungated read
Fixed3.2.1tags 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:

  1. file: references under restricted mode;
  2. equivalent relative and absolute source forms;
  3. trusted-mode positive controls;
  4. <invoke src> and srcexpr;
  5. assertions that the sensitive file is never opened when policy denies the source;
  6. network URI handlers, if support is added in future.
2026-06-17
python-statemachine 3.2.0 published.
2026-08-01
3.2.1 and GHSA-fj3w-533r-fvf6 published with the public PoC.
2026-08-26
Exact public tags, PyPI wheels, vulnerable and fixed controls reviewed.
  1. GHSA-fj3w-533r-fvf6 — python-statemachine advisory and public PoC
  2. python-statemachine 3.2.1 release
  3. 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.