22
33* June 16, 2026*
44
5- ``` {warning}
6- **Python 3.9 support dropped.** StateMachine 3.2.0 requires Python 3.10 or
7- later. If you cannot upgrade Python yet, pin to `python-statemachine<3.2`
8- (the 3.1.x series remains the last line supporting 3.9).
9- ```
10-
11- ## Backward incompatible changes in 3.2.0
5+ ## Highlights
126
13- ### Python 3.9 support dropped
7+ - ** Load statecharts from documents.** A single, secure {func}` statemachine.io.load `
8+ reads SCXML, JSON and YAML into a running ` StateChart ` . From an inline definition:
149
15- Python 3.9 reached end-of-life on 2025-10-31 and is no longer supported by
16- the Python core team. StateMachine 3.2 now requires ** Python 3.10+ ** .
10+ ``` python
11+ from statemachine.io import load
1712
18- Rationale:
13+ Light = load(
14+ """
15+ states:
16+ green: {initial: true, on: {next: [{target: red}]} }
17+ red: {on: {next: [{target: green}]} }
18+ """ ,
19+ format = " yaml" ,
20+ )
21+ sm = Light()
22+ sm.send(" next" )
23+ ```
1924
20- - Python 3.9 represented around 1.4% of PyPI downloads of
21- ` python-statemachine ` in the 180 days prior to this release;
22- Python 3.10+ accounts for the vast majority of attributable traffic.
23- - Dropping 3.9 lets the codebase adopt ` match ` /` case ` (PEP 634), PEP 604
24- union syntax (` X | Y ` ), PEP 585 built-in generics (` list[int] ` instead
25- of ` List[int] ` ), and ` zip(strict=True) ` (PEP 618) internally.
26- - The same minimum has already been adopted by the major libraries in
27- the ecosystem (pandas, FastAPI, SQLAlchemy, NumPy, Django 5).
25+ Or from a file, with the format detected from the extension:
2826
29- #### Migration
27+ ``` python
28+ Machine = load(" traffic_light.scxml" )
29+ ```
3030
31- - Upgrade your interpreter to Python 3.10 or later, ** or**
32- - Pin ` python-statemachine<3.2 ` to stay on the 3.1.x line.
31+ - ** Safe by default.** Expressions in loaded documents are evaluated by a restricted
32+ allowlist, never ` eval ` — this also closes a code-execution vulnerability in the old
33+ SCXML loader (CVE-2026 -47103); see * Security* below.
34+ - ** Python 3.10+ now required.** Support for the end-of-life Python 3.9 was dropped.
3335
34- No public API was changed by this drop. Code that runs on 3.10+ today
35- will continue to run unchanged on 3.2.
36+ ## Security: arbitrary code execution when loading SCXML (CVE-2026 -47103)
3637
37- ### SCXML datamodel is now safe by default (GHSA-v4jc -pm6r-3vj8, CVE-2026 -47103)
38+ ** In short:** before 3.2.0, loading an SCXML document with ` SCXMLProcessor ` evaluated the
39+ expressions inside it with Python's ` eval ` /` exec ` , so a ` .scxml ` file from an untrusted
40+ source could run arbitrary code on your machine. 3.2.0 makes loading ** safe by default** :
41+ expressions are evaluated by a restricted allowlist and ` <script> ` is rejected.
3842
3943``` {note}
40- **Affected versions:** `>= 3.0.0, < 3.2.0` (fixed in 3.2.0).
41-
42- **Am I affected?** Only if you *load statechart documents from an untrusted or
43- third-party source* through the (experimental, previously undocumented) SCXML
44- loader — `statemachine.io.scxml.SCXMLProcessor` or parsing a `.scxml` file. That
45- loader is the only path that ever evaluated expressions from a document with
46- `eval`/`exec`.
47-
48- If you define your machines in Python with `StateMachine` / `StateChart` — the
49- normal way to use this library — you are **not** affected. "SCXML" here names the
50- W3C *execution model* the engine implements, not a file you load: nothing from a
51- document is evaluated, because there is no document.
44+ **Am I affected?**
45+
46+ - **Yes** — only if you loaded `.scxml` documents you did not author, through
47+ `SCXMLProcessor` (e.g. `SCXMLProcessor().parse_scxml(...)` or `parse_scxml_file(...)`)
48+ on input you don't control. That class was the only SCXML loader in the affected
49+ releases (`io.load()` did not exist yet).
50+ - **No** — if you define your machines in Python (`StateMachine` / `StateChart`), or only
51+ load `.scxml` files you wrote yourself. Defining a machine in code never evaluates a
52+ document; there is no document to evaluate.
53+
54+ **Affected versions:** only the 3.x line before 3.2 — `>= 3.0.0, < 3.2.0`. SCXML file
55+ loading was added in 3.0.0 (experimental and undocumented); 2.x and earlier have no SCXML
56+ loader and are not affected. Fixed in 3.2.0.
5257```
5358
54- SCXML is * executable content* per the W3C specification: ` cond ` /` expr `
55- attributes and ` <script> ` elements are evaluated in the datamodel language,
56- and this library implements a Python datamodel. Previously
57- ` SCXMLProcessor ` evaluated those with Python's ` eval ` /` exec ` with no
58- restriction, so ** loading an SCXML document from an untrusted source allowed
59- arbitrary code execution** (CWE-95).
59+ ** What changed.** Guards and datamodel expressions (` cond ` , ` <assign> ` , ` <send> ` ,
60+ ` <foreach> ` , ` <log> ` , …) are now compiled by a restricted AST allowlist — arithmetic,
61+ comparisons, collections, indexing, attribute reads and the ` In(...) ` predicate, but no
62+ builtins, dunder access, or function/method calls. ` <script> ` is rejected. This mirrors
63+ ` yaml.safe_load ` vs ` yaml.load ` .
6064
61- Starting in 3.2.0, ` SCXMLProcessor ` is ** safe by default** , mirroring
62- ` yaml.safe_load ` vs ` yaml.load ` :
63-
64- - Datamodel expressions (` <data> ` , ` <assign> ` , ` <send> ` , ` <foreach> ` ,
65- ` <log> ` , ` cond ` ) are evaluated by a restricted AST-whitelist evaluator that
66- supports arithmetic, comparisons, collections, indexing, attribute reads and
67- the ` In(...) ` predicate, but cannot reach builtins, dunder attributes, or
68- arbitrary function/method calls.
69- - ` <script> ` (arbitrary code) is rejected.
70-
71- To restore the previous behavior for SCXML you trust (hand-authored
72- documents, output of your own CASE tooling, conformance suites), pass
73- ` trusted=True ` :
65+ ** Trusting a document.** For SCXML you author yourself (hand-written documents or the W3C
66+ conformance suite), opt back into full Python with ` trusted=True ` :
7467
7568``` python
7669from statemachine.io.scxml.processor import SCXMLProcessor
7770
78- SCXMLProcessor() # safe default: restricted evaluator, <script> rejected
79- SCXMLProcessor(trusted = True ) # full eval/exec — only for trusted documents
71+ SCXMLProcessor() # safe default: restricted evaluator, <script> rejected
72+ SCXMLProcessor(trusted = True ) # full eval/exec — only for documents you trust
8073```
8174
82- A restricted-mode document that uses an unsupported construct fails to load
83- with ` InvalidDefinition ` . Runtime evaluation errors (e.g. an undefined name)
84- continue to surface as ` error.execution ` events as before.
85-
86- ### ` statemachine.io.scxml ` internals reorganized
87-
88- The experimental ` statemachine.io.scxml ` internals were promoted into a format-neutral
89- IO core (see * What's new* below). Code that imported implementation modules directly must
90- update its imports:
91-
92- | Before (3.1.x) | After (3.2.0) |
93- | ---| ---|
94- | ` statemachine.io.scxml.schema ` | ` statemachine.io.model ` |
95- | ` statemachine.io.scxml.parser ` | ` statemachine.io.scxml.reader ` |
96- | generic helpers in ` statemachine.io.scxml.actions ` | ` statemachine.io.actions ` |
97- | ` protected_attrs ` / ` _eval ` in ` statemachine.io.scxml.actions ` | ` statemachine.io.evaluators ` |
98- | ` EventDataWrapper ` etc. in ` statemachine.io.scxml.actions ` | ` statemachine.io.system_variables ` |
99- | ` SCXMLInvoker ` in ` statemachine.io.scxml.invoke ` | ` Invoker ` in ` statemachine.io.invoke ` |
75+ A restricted-mode document that uses an unsupported construct fails to load with
76+ ` InvalidDefinition ` ; runtime evaluation errors still surface as ` error.execution ` events.
10077
101- ` statemachine.io.scxml.processor.SCXMLProcessor ` (now a thin wrapper over the new
102- format-neutral runtime, minus the removed ` parse_scxml_file ` ; use ` io.load ` for files) and
103- ` statemachine.io.create_machine_class_from_definition ` keep their behavior. The runtime
104- itself moved into the new, SCXML-agnostic ` statemachine.io.interpreter.Interpreter ` and
105- ` statemachine.io.builder ` (see * Architecture* below).
78+ Refs: GHSA-v4jc -pm6r-3vj8, CVE-2026 -47103, CWE-95.
10679
10780## What's new in 3.2.0
10881
@@ -122,7 +95,7 @@ sm = Machine()
12295 (` .yaml ` /` .yml ` ). The format is detected from the file extension or set explicitly with
12396 ` format= ` .
12497- ** Secure by default.** Guards and datamodel expressions are evaluated by a restricted
125- AST-whitelist evaluator; ` <script> ` / arbitrary Python is rejected unless you pass
98+ AST-allowlist evaluator; ` <script> ` / arbitrary Python is rejected unless you pass
12699 ` trusted=True ` . YAML is always parsed with safe-load semantics.
127100- ** Functional parity across formats.** The native floor is the SCXML ceiling: everything
128101 SCXML expresses is expressible in JSON/YAML and behaves the same. ` cond ` /` unless ` are
@@ -159,6 +132,59 @@ python-statemachine[validation] # jsonschema, for validate=True
159132python-statemachine[io] # both of the above
160133```
161134
135+ ## Backward incompatible changes in 3.2.0
136+
137+ ``` {warning}
138+ **Python 3.9 support dropped.** StateMachine 3.2.0 requires Python 3.10 or
139+ later. If you cannot upgrade Python yet, pin to `python-statemachine<3.2`
140+ (the 3.1.x series remains the last line supporting 3.9).
141+ ```
142+
143+ ### Python 3.9 support dropped
144+
145+ Python 3.9 reached end-of-life on 2025-10-31 and is no longer supported by
146+ the Python core team. StateMachine 3.2 now requires ** Python 3.10+** .
147+
148+ Rationale:
149+
150+ - Python 3.9 represented around 1.4% of PyPI downloads of
151+ ` python-statemachine ` in the 180 days prior to this release;
152+ Python 3.10+ accounts for the vast majority of attributable traffic.
153+ - Dropping 3.9 lets the codebase adopt ` match ` /` case ` (PEP 634), PEP 604
154+ union syntax (` X | Y ` ), PEP 585 built-in generics (` list[int] ` instead
155+ of ` List[int] ` ), and ` zip(strict=True) ` (PEP 618) internally.
156+ - The same minimum has already been adopted by the major libraries in
157+ the ecosystem (pandas, FastAPI, SQLAlchemy, NumPy, Django 5).
158+
159+ #### Migration
160+
161+ - Upgrade your interpreter to Python 3.10 or later, ** or**
162+ - Pin ` python-statemachine<3.2 ` to stay on the 3.1.x line.
163+
164+ No public API was changed by this drop. Code that runs on 3.10+ today
165+ will continue to run unchanged on 3.2.
166+
167+ ### ` statemachine.io.scxml ` internals reorganized
168+
169+ The experimental ` statemachine.io.scxml ` internals were promoted into a format-neutral
170+ IO core (see * What's new* above). Code that imported implementation modules directly must
171+ update its imports:
172+
173+ | Before (3.1.x) | After (3.2.0) |
174+ | ---| ---|
175+ | ` statemachine.io.scxml.schema ` | ` statemachine.io.model ` |
176+ | ` statemachine.io.scxml.parser ` | ` statemachine.io.scxml.reader ` |
177+ | generic helpers in ` statemachine.io.scxml.actions ` | ` statemachine.io.actions ` |
178+ | ` protected_attrs ` / ` _eval ` in ` statemachine.io.scxml.actions ` | ` statemachine.io.evaluators ` |
179+ | ` EventDataWrapper ` etc. in ` statemachine.io.scxml.actions ` | ` statemachine.io.system_variables ` |
180+ | ` SCXMLInvoker ` in ` statemachine.io.scxml.invoke ` | ` Invoker ` in ` statemachine.io.invoke ` |
181+
182+ ` statemachine.io.scxml.processor.SCXMLProcessor ` (now a thin wrapper over the new
183+ format-neutral runtime, minus the removed ` parse_scxml_file ` ; use ` io.load ` for files) and
184+ ` statemachine.io.create_machine_class_from_definition ` keep their behavior. The runtime
185+ itself moved into the new, SCXML-agnostic ` statemachine.io.interpreter.Interpreter ` and
186+ ` statemachine.io.builder ` (see * Architecture* above).
187+
162188## Bug fixes in 3.2.0
163189
164190### Sibling compound states with same-named children no longer collide
0 commit comments