Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/io/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,17 +201,17 @@ every format (SCXML, JSON and YAML).
A set of follow-up advisories hardened the restricted mode further and prompted the
confidentiality/integrity vs availability framing above:

- [GHSA-fj3w-533r-fvf6](https://github.com/fgmacedo/python-statemachine/security/advisories/GHSA-fj3w-533r-fvf6)
`<data src="file:…">` and `<invoke src="…">` read local files during loading, regardless of
- [GHSA-fj3w-533r-fvf6](https://github.com/fgmacedo/python-statemachine/security/advisories/GHSA-fj3w-533r-fvf6):
`<data src="file:…">` and `<invoke src="…">` read local files during loading, regardless of
`trusted`. Loading now rejects external `src` references unless `trusted=True`, and refuses
`<!DOCTYPE>`/DTD to block XML entity-expansion bombs.
- [GHSA-v3qq-3xvg-m77g](https://github.com/fgmacedo/python-statemachine/security/advisories/GHSA-v3qq-3xvg-m77g)
/ [GHSA-4857-ggqc-p3jc](https://github.com/fgmacedo/python-statemachine/security/advisories/GHSA-4857-ggqc-p3jc)
a document could write to a dunder/private/protected attribute (notably traversing
/ [GHSA-4857-ggqc-p3jc](https://github.com/fgmacedo/python-statemachine/security/advisories/GHSA-4857-ggqc-p3jc):
a document could write to a dunder/private/protected attribute (notably traversing
`__class__`) and corrupt the shared model class process-wide. Write targets are now confined
to public model attributes on every path segment.
- [GHSA-r8gj-366q-cgvj](https://github.com/fgmacedo/python-statemachine/security/advisories/GHSA-r8gj-366q-cgvj)
`**`/`*` in the restricted evaluator had no magnitude bound, so a tiny expression could
- [GHSA-r8gj-366q-cgvj](https://github.com/fgmacedo/python-statemachine/security/advisories/GHSA-r8gj-366q-cgvj):
`**`/`*` in the restricted evaluator had no magnitude bound, so a tiny expression could
exhaust CPU or memory. They are now magnitude-capped.

These were released together in 3.2.1.
4 changes: 2 additions & 2 deletions docs/releases/3.2.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ See [](../io/security.md).
```{note}
**Am I affected?**

- **Yes** if you load documents you did **not** author (via `statemachine.io.load(...)` /
- **Yes**, if you load documents you did **not** author (via `statemachine.io.load(...)` /
`build_processor(...)` / `SCXMLProcessor`) with the default `trusted=False`.
- **No** if you define machines in Python, only load documents you wrote yourself, or already
- **No**, if you define machines in Python, only load documents you wrote yourself, or already
load with `trusted=True` for fully controlled documents.

**Affected versions:** `>= 3.2.0, < 3.2.1` (this attack surface shipped with the
Expand Down
2 changes: 1 addition & 1 deletion statemachine/spec_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def recurse(child):
op_type = type(node.op)
if op_type not in binary_operators:
# e.g. bitwise ``^``/``|``/``<<`` are outside the allowlist. (``**`` and ``*``
# are allowed but magnitude-capped see ``binary_operators``.)
# are allowed but magnitude-capped, see ``binary_operators``.)
raise ValueError(f"Binary operator '{op_type.__name__}' is not allowed")
return build_binop(binary_operators[op_type], recurse(node.left), recurse(node.right))
case ast.List(elts=elts) if allow_value_nodes:
Expand Down
13 changes: 8 additions & 5 deletions tests/io/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,11 +551,14 @@ def test_shared_exception_class_not_corrupted(self, fmt):
try:
sm = _run_exec(scxml, native, fmt)
assert "failed" in _config(sm)
# The shared class is intact: add_note is still the inherited method, not int 1,
# and a normal exception still constructs and carries a note.
assert callable(TransitionNotAllowed.add_note)
err = TransitionNotAllowed(None, set())
err.add_note("still works")
# The shared class is intact: the exploit did not inject ``add_note = 1`` onto it.
# ``BaseException.add_note`` only exists on Python 3.11+, so assert on the injection
# site (the class ``__dict__``) rather than the inherited method, to stay
# version-agnostic.
assert "add_note" not in TransitionNotAllowed.__dict__
assert getattr(TransitionNotAllowed, "add_note", None) != 1
# A normal exception still constructs.
TransitionNotAllowed(None, set())
finally:
# Defensive: if a regression ever mutated the shared class, restore it so the
# rest of the suite is not corrupted.
Expand Down
Loading