Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
3f93728
Backport PR #30788: Fix typo in key-mapping for "f11" (#30791)
meeseeksmachine Nov 25, 2025
6a38e4e
Backport PR #30763: DOC: Add example how to align tick labels
rcomer Nov 29, 2025
6505281
Merge pull request #30802 from meeseeksmachine/auto-backport-of-pr-30…
rcomer Nov 29, 2025
ce64529
Backport PR #30817: Update sphinx-gallery header patch
timhoffm Dec 7, 2025
fef0747
Merge pull request #30819 from meeseeksmachine/auto-backport-of-pr-30…
timhoffm Dec 7, 2025
1957ba3
Zenodo v3.10.8
ksunden Dec 12, 2025
9d0781d
Backport PR #30858: DOC: reinstate "codex" search term
tacaswell Dec 15, 2025
1a730c9
Merge pull request #30861 from meeseeksmachine/auto-backport-of-pr-30…
timhoffm Dec 16, 2025
44431a6
Backport PR #30910: DOC: Improve writer parameter docs of Animation.s…
timhoffm Jan 2, 2026
b5e1c48
Merge pull request #30925 from meeseeksmachine/auto-backport-of-pr-30…
timhoffm Jan 2, 2026
a59d1d2
Backport PR #30960: SVG backend - handle font weight as integer
steveberardi Jan 14, 2026
58d5aea
Merge pull request #30964 from meeseeksmachine/auto-backport-of-pr-30…
timhoffm Jan 14, 2026
bc44318
Backport PR #30952: DOC: Tutorial on API shortcuts
timhoffm Jan 15, 2026
efb581c
Backport PR #30952: DOC: Tutorial on API shortcuts
timhoffm Jan 15, 2026
3c78c27
Merge pull request #30966 from meeseeksmachine/auto-backport-of-pr-30…
timhoffm Jan 15, 2026
521df0b
Merge pull request #30965 from meeseeksmachine/auto-backport-of-pr-30…
timhoffm Jan 15, 2026
8c7d40f
Backport PR #30969: DOC: Simplify barh() example
rcomer Jan 15, 2026
fe5ca06
Backport PR #30969: DOC: Simplify barh() example
rcomer Jan 15, 2026
df313af
Merge pull request #30971 from meeseeksmachine/auto-backport-of-pr-30…
rcomer Jan 15, 2026
1d79856
Merge pull request #30972 from meeseeksmachine/auto-backport-of-pr-30…
rcomer Jan 15, 2026
89a35d8
Backport PR #30985: MNT: do not assign a numpy array shape
scottshambaugh Jan 17, 2026
50d13e4
Merge pull request #30986 from meeseeksmachine/auto-backport-of-pr-30…
timhoffm Jan 17, 2026
2deb54c
Backport PR #31035: DOCS: Fix typo in time array step size comment
rcomer Jan 26, 2026
3039ff1
Backport PR #31035: DOCS: Fix typo in time array step size comment
rcomer Jan 26, 2026
15829cf
Merge pull request #31037 from meeseeksmachine/auto-backport-of-pr-31…
rcomer Jan 26, 2026
8db8148
Merge pull request #31036 from meeseeksmachine/auto-backport-of-pr-31…
rcomer Jan 26, 2026
313a44d
Backport PR #31075: Fix remove method for figure title and xy-labels
QuLogic Feb 4, 2026
b9d7e7f
Backport PR #31153: TST: Use correct method of clearing mock objects
ksunden Feb 13, 2026
cc7fe5a
Merge pull request #31154 from meeseeksmachine/auto-backport-of-pr-31…
timhoffm Feb 13, 2026
ef19bdc
Backport PR #31278: Fix `clabel` manual argument not accepting unit-t…
aman-coder03 Mar 11, 2026
f32af1d
Backport PR #31323: FIX: Prevent crash when removing a subfigure cont…
Vikash-Kumar-23 Mar 27, 2026
6aac3da
Backport PR #31401: BLD: Temporarily pin setuptools-scm<10
QuLogic Mar 27, 2026
9220b77
Merge pull request #31418 from rcomer/pin-setuptools-scm-backport
timhoffm Mar 29, 2026
3c91aec
Backport PR #31420: Fix outdated Savannah URL for freetype download
timhoffm Mar 29, 2026
b9163f8
Merge pull request #31421 from meeseeksmachine/auto-backport-of-pr-31…
rcomer Mar 29, 2026
51c85f7
Merge pull request #31411 from meeseeksmachine/auto-backport-of-pr-31…
rcomer Mar 29, 2026
69e6954
Backport PR #31437: mathtext: Fix type inconsistency with fontmaps
tacaswell Apr 2, 2026
cada119
Bump mimimum Ubuntu Version on Azure because 20.04 is EOL
ksunden Apr 3, 2026
45ac295
Merge pull request #31445 from ksunden/auto-backport-of-pr-31437-on-v…
QuLogic Apr 7, 2026
614abdd
Backport PR #31504: Re-order variants to prioritize narrower types
QuLogic Apr 16, 2026
6e203dc
Merge pull request #31511 from meeseeksmachine/auto-backport-of-pr-31…
ksunden Apr 17, 2026
2da812c
Backport PR #31020: DOC: Fix doc builds with Sphinx 9
timhoffm Jan 24, 2026
52059e4
Merge pull request #31520 from meeseeksmachine/auto-backport-of-pr-31…
ksunden Apr 21, 2026
851f769
Merge pull request #31280 from meeseeksmachine/auto-backport-of-pr-31…
ksunden Apr 21, 2026
e3fb541
Merge pull request #31078 from meeseeksmachine/auto-backport-of-pr-31…
ksunden Apr 21, 2026
acc6024
Merge pull request #31282 from scottshambaugh/tex_no_shell
WeatherGod Mar 13, 2026
b2ed196
Backport PR #31248: SEC: Remove eval() from validate_cycler
scottshambaugh Mar 27, 2026
063288d
Merge pull request #31544 from ksunden/backport-of-pr-31248-on-v3.10.x
timhoffm Apr 22, 2026
a4f57ab
Merge pull request #31545 from ksunden/backport-of-pr-31282-on-v3.10.x
timhoffm Apr 22, 2026
a83faac
Backport PR #31556: FIX: Inverted PyErr_Occurred check in enum type c…
ksunden Apr 23, 2026
1637932
Merge pull request #31558 from meeseeksmachine/auto-backport-of-pr-31…
ksunden Apr 23, 2026
d0e923a
Merge branch 'v3.10.8-doc' into v3.10.x
ksunden Apr 23, 2026
2fb1891
REL: Release prep v3.10.9
ksunden Apr 23, 2026
dd8d78b
REL: v3.10.9
ksunden Apr 23, 2026
58b6023
REL: Bump from v3.10.9
ksunden Apr 23, 2026
df001ac
Zenodo v3.10.9
ksunden Apr 23, 2026
04dcfbe
Merge branch 'v3.10.x' into merge_up_v3.10.9
ksunden Apr 24, 2026
cc8a687
Merge branch 'v3.10.9-doc' into merge_up_v3.10.9
QuLogic Apr 24, 2026
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
Prev Previous commit
Next Next commit
Backport PR #31248: SEC: Remove eval() from validate_cycler
  • Loading branch information
scottshambaugh authored and ksunden committed Apr 21, 2026
commit b2ed1969191a03ec8927f96573664474662ab4c1
9 changes: 9 additions & 0 deletions doc/api/next_api_changes/deprecations/31248-SS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Arbitrary code in ``axes.prop_cycle`` rcParam strings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``axes.prop_cycle`` rcParam accepts Python expressions that are evaluated
in a limited context. The evaluation context has been further limited and some
expressions that previously worked (list comprehensions, for example) no longer
will. This change is made without a deprecation period to improve security.
The previously documented cycler operations at
https://matplotlib.org/cycler/ are still supported.
14 changes: 14 additions & 0 deletions doc/users/next_whats_new/cycler_rcparam_security.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
``axes.prop_cycle`` rcParam security improvements
-------------------------------------------------

The ``axes.prop_cycle`` rcParam is now parsed in a safer and more restricted
manner. Only literals, ``cycler()`` and ``concat()`` calls, the operators
``+`` and ``*``, and slicing are allowed. All previously valid cycler strings
documented at https://matplotlib.org/cycler/ are still supported, for example:

.. code-block:: none

axes.prop_cycle : cycler('color', ['r', 'g', 'b']) + cycler('linewidth', [1, 2, 3])
axes.prop_cycle : 2 * cycler('color', 'rgb')
axes.prop_cycle : concat(cycler('color', 'rgb'), cycler('color', 'cmk'))
axes.prop_cycle : cycler('color', 'rgbcmk')[:3]
83 changes: 60 additions & 23 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from matplotlib._enums import JoinStyle, CapStyle

# Don't let the original cycler collide with our validating cycler
from cycler import Cycler, cycler as ccycler
from cycler import Cycler, concat as cconcat, cycler as ccycler


@_api.caching_module_getattr
Expand Down Expand Up @@ -759,11 +759,62 @@ def cycler(*args, **kwargs):
return reduce(operator.add, (ccycler(k, v) for k, v in validated))


class _DunderChecker(ast.NodeVisitor):
def visit_Attribute(self, node):
if node.attr.startswith("__") and node.attr.endswith("__"):
raise ValueError("cycler strings with dunders are forbidden")
self.generic_visit(node)
def _parse_cycler_string(s):
"""
Parse a string representation of a cycler into a Cycler object safely,
without using eval().

Accepts expressions like::

cycler('color', ['r', 'g', 'b'])
cycler('color', 'rgb') + cycler('linewidth', [1, 2, 3])
cycler(c='rgb', lw=[1, 2, 3])
cycler('c', 'rgb') * cycler('linestyle', ['-', '--'])
"""
try:
tree = ast.parse(s, mode='eval')
except SyntaxError as e:
raise ValueError(f"Could not parse {s!r}: {e}") from e
return _eval_cycler_expr(tree.body)


def _eval_cycler_expr(node):
"""Recursively evaluate an AST node to build a Cycler object."""
if isinstance(node, ast.BinOp):
left = _eval_cycler_expr(node.left)
right = _eval_cycler_expr(node.right)
if isinstance(node.op, ast.Add):
return left + right
if isinstance(node.op, ast.Mult):
return left * right
raise ValueError(f"Unsupported operator: {type(node.op).__name__}")
if isinstance(node, ast.Call):
if not (isinstance(node.func, ast.Name)
and node.func.id in ('cycler', 'concat')):
raise ValueError(
"only the 'cycler()' and 'concat()' functions are allowed")
func = cycler if node.func.id == 'cycler' else cconcat
args = [_eval_cycler_expr(a) for a in node.args]
kwargs = {kw.arg: _eval_cycler_expr(kw.value) for kw in node.keywords}
return func(*args, **kwargs)
if isinstance(node, ast.Subscript):
sl = node.slice
if not isinstance(sl, ast.Slice):
raise ValueError("only slicing is supported, not indexing")
s = slice(
ast.literal_eval(sl.lower) if sl.lower else None,
ast.literal_eval(sl.upper) if sl.upper else None,
ast.literal_eval(sl.step) if sl.step else None,
)
value = _eval_cycler_expr(node.value)
return value[s]
# Allow literal values (int, strings, lists, tuples) as arguments
# to cycler() and concat().
try:
return ast.literal_eval(node)
except (ValueError, TypeError):
raise ValueError(
f"Unsupported expression in cycler string: {ast.dump(node)}")


# A validator dedicated to the named legend loc
Expand Down Expand Up @@ -814,25 +865,11 @@ def _validate_legend_loc(loc):
def validate_cycler(s):
"""Return a Cycler object from a string repr or the object itself."""
if isinstance(s, str):
# TODO: We might want to rethink this...
# While I think I have it quite locked down, it is execution of
# arbitrary code without sanitation.
# Combine this with the possibility that rcparams might come from the
# internet (future plans), this could be downright dangerous.
# I locked it down by only having the 'cycler()' function available.
# UPDATE: Partly plugging a security hole.
# I really should have read this:
# https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
# We should replace this eval with a combo of PyParsing and
# ast.literal_eval()
try:
_DunderChecker().visit(ast.parse(s))
s = eval(s, {'cycler': cycler, '__builtins__': {}})
except BaseException as e:
s = _parse_cycler_string(s)
except Exception as e:
raise ValueError(f"{s!r} is not a valid cycler construction: {e}"
) from e
# Should make sure what comes from the above eval()
# is a Cycler object.
if isinstance(s, Cycler):
cycler_inst = s
else:
Expand Down Expand Up @@ -1101,7 +1138,7 @@ def _convert_validator_spec(key, conv):
"axes.formatter.offset_threshold": validate_int,
"axes.unicode_minus": validate_bool,
# This entry can be either a cycler object or a string repr of a
# cycler-object, which gets eval()'ed to create the object.
# cycler-object, which is parsed safely via AST.
"axes.prop_cycle": validate_cycler,
# If "data", axes limits are set close to the data.
# If "round_numbers" axes limits are set to the nearest round numbers.
Expand Down
24 changes: 17 additions & 7 deletions lib/matplotlib/tests/test_rcparams.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,16 +272,23 @@ def generate_validator_testcases(valid):
cycler('linestyle', ['-', '--'])),
(cycler(mew=[2, 5]),
cycler('markeredgewidth', [2, 5])),
("2 * cycler('color', 'rgb')", 2 * cycler('color', 'rgb')),
("2 * cycler('color', 'r' + 'gb')", 2 * cycler('color', 'rgb')),
("cycler(c='r' + 'gb', lw=[1, 2, 3])",
cycler('color', 'rgb') + cycler('linewidth', [1, 2, 3])),
("cycler('color', 'rgb') * 2", cycler('color', 'rgb') * 2),
("concat(cycler('color', 'rgb'), cycler('color', 'cmk'))",
cycler('color', list('rgbcmk'))),
("cycler('color', 'rgbcmk')[:3]", cycler('color', list('rgb'))),
("cycler('color', 'rgb')[::-1]", cycler('color', list('bgr'))),
),
# This is *so* incredibly important: validate_cycler() eval's
# an arbitrary string! I think I have it locked down enough,
# and that is what this is testing.
# TODO: Note that these tests are actually insufficient, as it may
# be that they raised errors, but still did an action prior to
# raising the exception. We should devise some additional tests
# for that...
# validate_cycler() parses an arbitrary string using a safe
# AST-based parser (no eval). These tests verify that only valid
# cycler expressions are accepted.
'fail': ((4, ValueError), # Gotta be a string or Cycler object
('cycler("bleh, [])', ValueError), # syntax error
("cycler('color', 'rgb') * * cycler('color', 'rgb')", # syntax error
ValueError),
('Cycler("linewidth", [1, 2, 3])',
ValueError), # only 'cycler()' function is allowed
# do not allow dunder in string literals
Expand All @@ -295,6 +302,9 @@ def generate_validator_testcases(valid):
ValueError),
("cycler('c', [j.__class__(j).lower() for j in ['r', 'b']])",
ValueError),
# list comprehensions are arbitrary code, even if "safe"
("cycler('color', [x for x in ['r', 'g', 'b']])",
ValueError),
('1 + 2', ValueError), # doesn't produce a Cycler object
('os.system("echo Gotcha")', ValueError), # os not available
('import os', ValueError), # should not be able to import
Expand Down
Loading