Skip to content

Commit c30c416

Browse files
committed
docs: restructure 3.2.0 release notes for readers
Lead with Highlights (incl. inline + file load examples) and a clearer, user-oriented Security section (TL;DR, Yes/No "Am I affected", scoped affected versions); move the Python 3.10+ requirement and import-reorg down into Backward incompatible changes. Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
1 parent 54a28a5 commit c30c416

1 file changed

Lines changed: 108 additions & 82 deletions

File tree

docs/releases/3.2.0.md

Lines changed: 108 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,107 +2,80 @@
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
7669
from 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
159132
python-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

Comments
 (0)