You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
matplotlibrc files are autoloaded on import matplotlib. When matplotlib_fname searches for this file, the first candidate it yields is the bare filename 'matplotlibrc' in the current working directory. If an attacker can place a matplotlibrc file in a directory where a user runs Python (e.g., a cloned git repository, a shared project directory, a downloads folder), all rcParams in that file are applied automatically when import matplotlib or import matplotlib.pyplot executes.
Several of the rcparams can set up common calls such as figure creation (#31293) or latex text rendering (#31249) such that they can execute arbitrary code from the matplotlibrc file as a side-effect. This presents a potential security risk.
The two linked threads above talk a bit about how an attacker which can place a file on the PATH, could alternatively just place an executable which wraps some module and silently imports with arbitrary import-time side effects, and so this is a security boundary that matplotlib should not try to include. My take is that this sort of script would appear as code and be likely flagged in a manual or automatic security audit, whereas a matplotlibrc file is not directly executable and appears as data. Someone who assumes the common principle of separating data and code would not think that this file might be risky.
This is not a critical vulnerability, but represents an opportunity for security hardening.
How to Solve:
Stop auto-loading custom matplotlibrc files. Allow the user to opt-in to this via a function or setting the MATPLOTLIBRC environment variable.
Note that this may require some discussion to figure out the best solution, and I do not recommend this as a first-time contributor issue.
Proposed approach: Drop CWD from the default search path
The minimal-breaking-change fix here seems to be removing 'matplotlibrc' (bare CWD path) from the candidates yielded by matplotlib_fname(), while keeping ~/.config/matplotlib/matplotlibrc and the install-dir default intact.
Users who currently rely on a per-project matplotlibrc in CWD could migrate to either:
Setting MATPLOTLIBRC=./matplotlibrc explicitly
Calling matplotlib.rc_file('./matplotlibrc') at the top of their script
This mirrors how Python itself hardened sys.path by removing the implicit '' (CWD) entry — same class of attack, same fix pattern.
Questions before opening a PR:
Should this be a hard removal, or a deprecation warning cycle first (e.g. warn for one release, remove in the next)?
Is there appetite for a MATPLOTLIBRC_UNSAFE_CWD=1 escape hatch for users who truly need the old behavior?
Happy to open a PR implementing the CWD removal with a deprecation warning path if the approach looks reasonable to maintainers.
The reference I had in mind is PEP 687 (implemented in Python 3.11), which introduced the -P flag and PYTHONSAFEPATH environment variable to suppress prepending '' and other potentially unsafe paths to sys.path.
I should clarify my earlier phrasing Python didn't do a hard removal of '' from sys.path by default, but rather added an opt-in mechanism to disable the behavior. That's actually more analogous to what I was suggesting here: keep the old behavior available (via MATPLOTLIBRC env var or explicitrc_file()call), but stop doing it silently by default.
I think an env MATPLOTLIBNORC or MATPLOTLIBDISABLERC (I do not have a strong opinion on the exact name) which if set disables any automatic loading of matplotlibrc files is the best path here. It gives people/organizations that have a more restrictive posture a knob to turn, keeps the convenience for a vast majority of users, and does not require any deprecation cycle or change for anyone who is not already worried about this.
That makes sense an opt-in env var avoids any breaking change or deprecation cycle entirely, which is the right tradeoff for a library this widely used.
Happy to open a PR implementing MATPLOTLIBNORC (or whichever name you settle on). Would check for it in matplotlib_fname() before yielding any candidates, so the entire auto-load path is skipped. I'd also add a note to the docs/migration guide so security-focused deployments can discover it.
Let me know if you have a name preference and I'll get started.
The MATPLOTLIBRC environment variable can now point to any "file" path
This includes device files; in particular, on Unix systems, one can set MATPLOTLIBRC to /dev/null to ignore the user's matplotlibrc file and fall back to Matplotlib's defaults.
This means we already have an opt-out mechanism (or opt-in for disabling) via MATLPLOTLIBRC=/dev/null
However, this is not portable. I suggest
support empty and/or 0 value MATPLOTLIBRC= / MATPLOTLIBRC=0 for disabling. Setting to /dev/null seems too clever.
raiseRuntimeError("Could not find matplotlibrc file; your Matplotlib "
"install is broken")
Setting /dev/null will skip looking for the users matplotlib rc in any of the places it might be stashed, but we will still look in the CWD first. A null value disabling search vs other wrong values (e.g. a non-existent path) being silently skipped seems to be shoving too much into one ENV.
The last thing in the fallback list is a custom matplotlibrc that a packager might have shipped in our data directory. Do we want to still read that or really go with no matpltolibrc reading at al?
Hey, appreciate the writeup and willingness to implement this!
Looking at the gen_candidates() code @tacaswell shared the fallback chain ends with os.path.join(get_data_path(), 'matplotlibrc'), the packager-shipped default. Worth nailing down before a PR: should MATPLOTLIBNORC skip everything in that chain including the data-dir fallback, or only user/CWD-level candidates? That determines whether the env var means "no customization at all" vs "no user customization". @tacaswell raised this but it hasn't been settled yet.
On the MATPLOTLIBRC=/dev/null point @timhoffm raised the code still yield 'matplotlibrc' first (CWD candidate) before hitting the env var branch, so that doesn't actually protect against the CWD lookup. Supporting MATPLOTLIBRC= or MATPLOTLIBRC=0 as a clean disable seems like a natural thing to bundle in.
The PEP 687 analogy is a good framing btw worth keeping that in the migration guide/docs for security-conscious users.
@tacaswell We could make MATPLOTLIBRC=0 make also skipping the CWD check. In the end it comes down to the interpretation of MATPLOTLIBRC:
is ist just the one additional path in the list of possible locations? Then MATPLOTLIBRC=0 does not make sense.
is it a general configuration parameter controlling the loading of matplotlibrc files. Then we can support multiple values/types with different behaviors:
0: Completely disable loading of an external matplotlibrc file.
explicit file: load this file
explcit folder: look for matplotlibrc in this folder
(optionally - if we feel the need to separately control is) HOME - only look in the XDG_CONFIG_HOME/HOME config folders
I'm inclined to go with the second more general interpretation. To me this feels more natural than splitting into multiple parameters.
+1 to the second interpretation treating MATPLOTLIBRC as a general loading control is cleaner than adding a separate MATPLOTLIBNORC var.
On the open question of scope: I'd suggest MATPLOTLIBRC=0 skips everything except the packager data-dir fallback (get_data_path()/matplotlibrc), since that's a system-level config rather than a user/project-level one. That way it means 'no user customization' rather than 'completely broken defaults'.
Happy to open a PR once this is agreed the change in gen_candidates() would be straightforward.
My read is that this is trade off of "simpler" of only one key or "simpler" meaning of the values of multiple keys. I continue to think that two keys is simpler, but lets go with the consensus of one key with multiple meanings.
The sentinel should be "set but empty" as '0' is a valid file name and directory name.
My reading of the OP is that it is intended to specifically prevent using a file in the current directory and that actively opting in via MATPLOTLIBRC should still be allowed.
If a sysadmin sets MATPLOTLIBRC to the sentinel for everyone, is there anything to stop a user setting MATPLOTLIBRC to their own custom file? Then we still look in the current directory first, regardless of the sysadmin's intention.
probably no short-term path to changing default to "no matpltolibrc unless you opt in" (it is very long standing behavior and a very common pattern) so not worth bringing in a toggle ENV.
go with empty string as the sentinel on MATPLOTLIBRC= because it is not a valid filename and if it is empty opts out of all matplotlibrc (including the CWD version which technically has higher priority).
Activity
kx7m2qd commentedon May 4, 2026
Proposed approach: Drop CWD from the default search path
The minimal-breaking-change fix here seems to be removing 'matplotlibrc' (bare CWD path) from the candidates yielded by matplotlib_fname(), while keeping ~/.config/matplotlib/matplotlibrc and the install-dir default intact.
Users who currently rely on a per-project matplotlibrc in CWD could migrate to either:
Setting MATPLOTLIBRC=./matplotlibrc explicitly
Calling matplotlib.rc_file('./matplotlibrc') at the top of their script
This mirrors how Python itself hardened sys.path by removing the implicit '' (CWD) entry — same class of attack, same fix pattern.
Questions before opening a PR:
Should this be a hard removal, or a deprecation warning cycle first (e.g. warn for one release, remove in the next)?
Is there appetite for a MATPLOTLIBRC_UNSAFE_CWD=1 escape hatch for users who truly need the old behavior?
Happy to open a PR implementing the CWD removal with a deprecation warning path if the approach looks reasonable to maintainers.
rcomer commentedon May 4, 2026
Please could you provide a reference for this change?
kx7m2qd commentedon May 4, 2026
The reference I had in mind is PEP 687 (implemented in Python 3.11), which introduced the
-Pflag andPYTHONSAFEPATHenvironment variable to suppress prepending''and other potentially unsafe paths tosys.path.I should clarify my earlier phrasing Python didn't do a hard removal of
''fromsys.pathby default, but rather added an opt-in mechanism to disable the behavior. That's actually more analogous to what I was suggesting here: keep the old behavior available (viaMATPLOTLIBRCenv var or explicitrc_file()call), but stop doing it silently by default.tacaswell commentedon May 4, 2026
I think an env
MATPLOTLIBNORCorMATPLOTLIBDISABLERC(I do not have a strong opinion on the exact name) which if set disables any automatic loading of matplotlibrc files is the best path here. It gives people/organizations that have a more restrictive posture a knob to turn, keeps the convenience for a vast majority of users, and does not require any deprecation cycle or change for anyone who is not already worried about this.kx7m2qd commentedon May 4, 2026
That makes sense an opt-in env var avoids any breaking change or deprecation cycle entirely, which is the right tradeoff for a library this widely used.
Happy to open a PR implementing
MATPLOTLIBNORC(or whichever name you settle on). Would check for it inmatplotlib_fname()before yielding any candidates, so the entire auto-load path is skipped. I'd also add a note to the docs/migration guide so security-focused deployments can discover it.Let me know if you have a name preference and I'll get started.
timhoffm commentedon May 5, 2026
From https://matplotlib.org/stable/users/prev_whats_new/whats_new_3.1.0.html#the-matplotlibrc-environment-variable-can-now-point-to-any-file-path
This means we already have an opt-out mechanism (or opt-in for disabling) via
MATLPLOTLIBRC=/dev/nullHowever, this is not portable. I suggest
MATPLOTLIBRC=/MATPLOTLIBRC=0for disabling. Setting to/dev/nullseems too clever.tacaswell commentedon May 5, 2026
matplotlib/lib/matplotlib/__init__.py
Lines 640 to 683 in f4cc437
Setting /dev/null will skip looking for the users matplotlib rc in any of the places it might be stashed, but we will still look in the CWD first. A null value disabling search vs other wrong values (e.g. a non-existent path) being silently skipped seems to be shoving too much into one ENV.
The last thing in the fallback list is a custom matplotlibrc that a packager might have shipped in our data directory. Do we want to still read that or really go with no matpltolibrc reading at al?
kx7m2qd commentedon May 5, 2026
Hey, appreciate the writeup and willingness to implement this!
Looking at the gen_candidates() code @tacaswell shared the fallback chain ends with os.path.join(get_data_path(), 'matplotlibrc'), the packager-shipped default. Worth nailing down before a PR: should MATPLOTLIBNORC skip everything in that chain including the data-dir fallback, or only user/CWD-level candidates? That determines whether the env var means "no customization at all" vs "no user customization". @tacaswell raised this but it hasn't been settled yet.
On the MATPLOTLIBRC=/dev/null point @timhoffm raised the code still yield 'matplotlibrc' first (CWD candidate) before hitting the env var branch, so that doesn't actually protect against the CWD lookup. Supporting MATPLOTLIBRC= or MATPLOTLIBRC=0 as a clean disable seems like a natural thing to bundle in.
The PEP 687 analogy is a good framing btw worth keeping that in the migration guide/docs for security-conscious users.
timhoffm commentedon May 7, 2026
@tacaswell We could make
MATPLOTLIBRC=0make also skipping the CWD check. In the end it comes down to the interpretation ofMATPLOTLIBRC:MATPLOTLIBRC=0does not make sense.matplotlibrcfiles. Then we can support multiple values/types with different behaviors:0: Completely disable loading of an externalmatplotlibrcfile.matplotlibrcin this folderHOME- only look in the XDG_CONFIG_HOME/HOME config foldersI'm inclined to go with the second more general interpretation. To me this feels more natural than splitting into multiple parameters.
kx7m2qd commentedon May 7, 2026
+1 to the second interpretation treating
MATPLOTLIBRCas a general loading control is cleaner than adding a separateMATPLOTLIBNORCvar.On the open question of scope: I'd suggest
MATPLOTLIBRC=0skips everything except the packager data-dir fallback (get_data_path()/matplotlibrc), since that's a system-level config rather than a user/project-level one. That way it means 'no user customization' rather than 'completely broken defaults'.Happy to open a PR once this is agreed the change in
gen_candidates()would be straightforward.tacaswell commentedon May 7, 2026
My read is that this is trade off of "simpler" of only one key or "simpler" meaning of the values of multiple keys. I continue to think that two keys is simpler, but lets go with the consensus of one key with multiple meanings.
The sentinel should be "set but empty" as
'0'is a valid file name and directory name.rcomer commentedon May 7, 2026
My reading of the OP is that it is intended to specifically prevent using a file in the current directory and that actively opting in via
MATPLOTLIBRCshould still be allowed.If a sysadmin sets
MATPLOTLIBRCto the sentinel for everyone, is there anything to stop a user settingMATPLOTLIBRCto their own custom file? Then we still look in the current directory first, regardless of the sysadmin's intention.tacaswell commentedon May 7, 2026
We discussed this on the call:
MATPLOTLIBRC=because it is not a valid filename and if it is empty opts out of all matplotlibrc (including the CWD version which technically has higher priority).kx7m2qd commentedon May 8, 2026
Makes sense. Empty string is clean since it's not a valid path. I'll take a look at the implementation.