(release-3008.3)=
Salt 3008.3 release notes#
Upgrading a Master to 3008.x#
:warning: Reminder:
If your master is being upgraded from 3006.x/3007.x, see the 3008.0 release notes about the minion data cache reorganization — runsalt '*' saltutil.refresh_grainson minions after the upgrade.
Changelog#
Removed#
Removed the unmaintained
linode-pythonpackage dependency to stop SyntaxWarnings during install for retired Linode API v3. #69455
Changed#
Documented that upgrading a master from 3006.x/3007.x to 3008.x requires running
saltutil.refresh_grainson minions due to the minion data cache reorganization (grains/pillar/mine split into dedicated cache banks). #68030Upgrade the bundled onedir Python from 3.10.20 to 3.11.15 on the 3007.x branch. Python 3.10 reaches end of security support in October 2026, while Salt 3007.x must ship security fixes past that date. Users upgrading from a previous 3007.x package will need to reinstall any Salt extensions installed via
salt-pipbecause the onedirextras-3.10directory is replaced byextras-3.11. #70063Bump cryptography (>=48.0.1 on py>=3.10), pyopenssl (drop <26.2.0 cap; salt.modules.tls now refuses to load on pyOpenSSL 26+ where its legacy X509Extension / X509Req / PKCS12 / CRL / load_crl APIs were removed -- use salt.modules.x509 instead), msgpack (>=1.2.0), requests (>=2.34.2), and setuptools (>=82.0.1) to current LTS floors on the salt-onedir Python stack. Python 3.9 pins retained (cryptography 48+ drops 3.9.0/3.9.1; msgpack 1.2.1 drops 3.9). #70130
Fixed#
Fixed pkg.installed to honour allow_updates for packages installed via sources, so a newer installed version is no longer reinstalled or downgraded on every run. #35385
Added a per-file
#jinja2:header that overrides Jinja environment options (such astrim_blocksandlstrip_blocks) for a single template, so individual states or third-party formulas can opt in or out without changing the globaljinja_env/jinja_sls_envsettings (which apply to every template). The header takes a JSON object and is honored on the first line, or on the line immediately following a renderer shebang (e.g.#!jinja|yaml). #35398Fixed grain_pcre and glob matching against dictionary-valued grains so patterns are applied to dict keys, not only list members. #35567
Fixed a race in the rest_tornado event listener so a single event is delivered to every websocket client waiting on a matching tag instead of only some of them #35798
ini.set_option now preserves indented options in other sections instead of deleting them. #36354
Report a failure when a PostgreSQL database exists but cannot be removed instead of claiming it is not present. #37506
Fixed the pyenv.install_pyenv state so it installs pyenv itself instead of raising a traceback. #37648
Documented in
doc/ref/states/vars.rstthatslspath,tpldir, and friends are render-time variables of the state compiler and are not available inside templates rendered throughfile.managed/template: jinja; the correct way to use them in such templates is to pass them viadefaults/context. #41195Fixed thorium reg.list handling of a non-string, non-list
addvalue: a scalar (such as an integer) is now treated as a single key instead of raising AttributeError, and a type that cannot be used as event-data keys (dict, tuple, set) is rejected with a clear SaltInvocationError rather than crashing or silently adding nothing. #43364Fixed the iptables module rendering the SYNPROXY (mss, wscale, sack-perm, timestamp), CT (zone-orig, zone-reply), SET (map-set) and SNAT/MASQUERADE (random-fully) jump-target options before -j instead of after it, so the generated rules are now valid. #46616
Allow the Debian ip module to accept rh_ip-style ipv6addr/ipv6addrs (and bare addr/addrs) as aliases for the address/addresses interface settings. #46618
Include the offending path in the "A valid directory was not specified" error raised by file.readdir and file.rmdir #47707
Fixed logrotate.set failing on stanzas that list multiple log paths on separate lines and on conf files without an include directive #48125
Fixed
cmd.scriptwithbg=Truedeleting the temporary script before the background process could execute it, which causedNo such file or directoryon POSIX. Background runs now use a self-cleaning wrapper so the child removes the tempfile after exit. Refs #50273 #69959 #50273salt-ssh: fix minionfs raising when minions cache dir is missing #50351
Fixed saltclass leaving a literal
^list-override marker in the merged pillar when a list is overridden by a single class and no existing list is present to override. #50755Added
encodingandencoding_errorsparameters to the file.comment, file.append, and file.prepend states, mirroring file.managed. A file whose bytes are not valid in the system encoding can now be handled by settingencoding_errors: replace(or a matchingencoding) instead of the state aborting with a UnicodeDecodeError while building the change diff. #50903Suppress noisy ERROR log messages when git.is_worktree probes a directory that is not a git repository. #51157
Fixed postgres.privileges_list raising ValueError on an emptied ACL so postgres_privileges.present can re-grant privileges after they were revoked #51450
Added a "Requisites truth table" section to
doc/ref/states/requisites.rstthat documents the resolution of recursiverequireandprereqchains, so authors can predict the outcome of a multi-level dependency graph without reading the compiler source. The accompanying functional tests verify the documented behavior. #51839Corrected the execution module documentation to clarify that a custom module overrides a stock module only when its filename matches the stock module filename; a custom module with a different filename only adds new functions under the shared virtual name. #52521
Fixed a TypeError in file.recurse/file.directory with clean when a require requisite is a bare state ID string containing the substring "file"; such requisites are now ignored instead of crashing. #53692
Added a "Where should
file_rootslive?" section todoc/ref/file_server/file_roots.rstexplaining why/srv/saltis the recommended default (FHS, sibling to/srv/pillar, separate from package-managed/etc/salt) and when other paths are reasonable. Updated thenetconfig.managedandnapalm_networkdocstring examples to use/srv/saltinstead of/etc/salt/statesso the inline example matches the recommendation. #53746Fixed zenoss.monitored state raising "'Changes' should be a dictionary." by returning an empty changes dict instead of None on the already-monitored and failed-add paths. #53966
Fixed grain precedence so a custom grain (from
extension_modules/_grains) overrides a built-in grain of the same name, matching the documented behaviour. Previously the built-in non-core grains were evaluated after custom grains and won, so a custom grain could not override, for example, theinterfacesgrain. #54694Added a NetworkManager provider for
network.managedso it works on RedHat-family systems that use NetworkManager (RHEL/CentOS/Alma/Rocky 8+, Fedora). The legacyrh_ipprovider writesifcfg-*files and brings interfaces up withifup/ifdownfrom thenetwork-scriptspackage, which is not installed by default on EL8+ (and removed on EL10), sonetwork.managedfailed withNo such file or directory: 'ifdown'and configured nothing. The newnm_ipmodule writes NetworkManager keyfiles under/etc/NetworkManager/system-connections/and applies them withnmcli. It claims theipvirtual when NetworkManager is managing the system without the legacy ifup/ifdown tooling, andrh_ipdefers to it in that case (hosts that still havenetwork-scriptsinstalled keep the legacy behavior). Also addresses #68252 and #62844. #54791Fixed mysql.db_remove so it correctly refuses to drop the information_schema system database, which was previously misspelled as information_scheme. #54938
Added a "salt.state options reference" to
doc/topics/orchestrate/orchestrate_runner.rstenumerating every option accepted bysalt.states.saltmod.state(targeting, environment, failure semantics, concurrency, return handling, salt-ssh) grouped by concern. #55021Serialized concurrent access to a shared NAPALM device connection. An always-alive proxy minion runs without multiprocessing, so jobs executing at the same time are threads that share a single device object and its one command channel; their driver calls could interleave and corrupt each other's output. Each device now carries a reentrant lock that
salt.utils.napalm.callholds for the duration of a call, so calls on the same device are serialized. #55332Fix seed.apply_ to use shutil.move so relocating the minion config and keys works across filesystems (avoids OSError EXDEV / cross-device link). #55348
Documented how
requireand theexcludeSLS directive interact indoc/ref/states/requisites.rstanddoc/ref/states/include.rst, including the fact that a requisite pointing at an excluded ID is a hard error at compile time. #55550Fixed
saltutil.refresh_grainsbeing a no-op whengrains_cacheis enabled; it now invalidates the on-disk grains cache before reloading so refreshed grain values take effect. #55667Clarified the supported remote URL formats in the
git_pillarmodule docstring, including the scp-styleuser@host:pathSSH form and the requirement for the colon between host and path. The walkthrough now lists HTTPS,ssh://, scp-style, andfile://URLs explicitly to avoid the "Failed to resolve address" and "Unable to exchange encryption keys" errors that result from a typo'd host portion. #56127Documented the actual code path of
wheel.key.delete_dictinsalt/wheel/key.py: the function iterates the supplied dict by status (minions,minions_pre,minions_rejected,minions_denied) and silently skips entries that are not present under the requested status. To delete a key whose status is unknown, usewheel.key.deletewith a glob match instead. #56208Fixed
wheel.key.gen/gen_accept(used by the salt-apirest_cherrypyPOST /keysendpoint) erroring on a stringkeysize; the value is now coerced to an integer and the documented 2048-bit minimum is enforced. #56425Fixed salt-ssh crashing with an uncaught UnicodeError when a long
-E/--pcretarget produces an overlong IDNA label inis_reachable_host#57207Fixed the
saltCLI exiting 0 in batch mode when the target matched no minions; it now exits 2 ("No return received"), matching the non-batch behavior. #57357Rewrote the standalone-minion introduction in
doc/topics/tutorials/standalone_minion.rstto give a concrete description of what a standalone minion is, when to use one, and the practical differences from a master-connected minion (targeting, file/pillar roots, ext-pillar, mine/jobs availability, two operating modes). #57488Fixed
salt '*' napalm.junos_cli(and other Junos calls) raisingTypeError/RuntimeErrorwhen no timeout was requested.napalm.junos_cliforwardsdev_timeout=Noneby default, and the Junos_timeout_decorator/_timeout_decorator_cleankwargswrappers treated that as a real value, somax(None, 0)raised (and setting the connection timeout toNoneis rejected by junos-eznc). The wrappers now coalesceNoneto0and only override the connection timeout when a real (>0)dev_timeout/timeoutis given. #58108Corrected the cp.push transfer-failure error message to reference the real master setting
file_recv_max_sizeinstead of the non-existentfile_recv_size_max. #58121Proxy minions now update
__pillar__for already-loaded proxy modules whensaltutil.refresh_pillarruns, so proxy modules see refreshed pillar data without restarting the proxy. Deltaproxy sub-proxies are refreshed individually with their own pillar. #58197Fixed
salt['match.compound'](and other execution modules called from pillar templates) matching against the master's id instead of the target minion's id during master-side pillar compilation. #58407Documented the availability of
__salt__and__pillar__for chained execution-module calls indoc/topics/development/modules/developing.rst, including the rule that__salt__is fully populated for any function call but is unreliable inside__virtual__and at import time. #58420Terminate the stdin piped to
atwith a trailing newline so distro-patchedat(Fedora/RHEL) no longer concatenates its job delimiter onto the last command #58510Stopped zypperpkg search functions from logging a spurious ERROR when zypper exits with code 104 (nothing found); the 104 exit code is now whitelisted for search-style calls. #58551
Cleaned up a batch of state and execution-module docstrings to match actual behavior. Addressed reports from #58845 (slack_notify.call_hook documented the configuration key as
identifierrather thanhook), #67074 (file.seek_read usedseekinstead ofsizein the description), #67911 (file.find listeduserfilter but the option isowner), #54802 (pkgrepo.managed saidenabled=Falseassumesdisabled=Falseinstead ofTrue), #61671 (pkgrepo.managed had no note about thehkp://keyserver scheme), #62002 (wheel.key__func_alias__aliases were not documented), #56729 / #65756 (virtualenv state docstring referred tovirtualenv_modand did not point atvirtualenv_mod.createfor unmapped kwargs), #61886 / #59666 (aptpkg and groupadd state/module docstrings did not surface theaptandgroupvirtual names), #55916 / #50568 / #64075 / #60773 (file state docstrings forrename,copy,blockreplaceand the octal-mode warning), #34929 / #57606 / #60784 / #63852 (service.runningsigspecial-character handling, missingreloadandfull_restartdocs, and the systemd daemon-reload note), #57505 / #57949 (cmd.runrunasprivilege drop semantics and Windows password requirement), #61689 (user.present Windows-unsupported uid/gid/allow_* arguments), #64021 (win_pki available certificate stores), #56182 (netmiko_pxkeepalivevs.always_alive), #51213 (postgres_privilegesmaintenance_dbcopy-paste), #57405 (file_tree pillar example mismatched the rendered pillar tree), #63364 (saltcheck duplicate "Example with jinja" section and unclear assertion definition), #61405 (file.chown broken-symlinklchownfallback), #60406 (jobs.last_run runner description and parameters), #55881 (docker_container.runningcommandaccepts list as well as string), #56956 (docker_image.presentslsdoes not accept a YAML list), and #66409 (docker_container.running hostname does not fall back toname). No behavior changes; documentation only. #58845Added a "Highstate Output" reference to
doc/ref/states/highstate.rstenumerating everystate_outputvalue (full,terse,mixed,changes,filter, and their_idvariants) and the relatedstate_verbose,state_output_diff,state_output_pct,state_output_profile,state_tabularandstate_compress_idsoptions, with guidance on when to use each. #59166Rebuild a proxy minion's execution-module loaders after the pillar rebind in
pillar_refresh, so exec modules see the freshly compiled__pillar__instead of the previous refresh's value #59393Fixed archive.extracted appending "Output was trimmed to False number of lines" when trim_output was left at its default and no output was actually trimmed. The message is now only added when trimming really occurs. #59570
Documented the keyword arguments accepted by
http.querydirectly in the execution module's docstring (salt/modules/http.py), grouping them by request, headers, authentication, TLS, cookies, response decoding, streaming, output capture, form data, transport and error handling. Addedtests/pytests/unit/modules/test_http_documented.pythat asserts every documented kwarg name exists as a real parameter ofsalt.utils.http.queryso the documentation cannot silently drift from the implementation. #59930Fixed
pkgrepo.managedwithdisabled: Trueon plain Debian (non-Ubuntu/Mint). Thekwargs["disabled"]normalization was gated on__grains__["os"] in ("Ubuntu", "Mint"), so on Debian the state compared the requesteddisabledvalue against the parsed apt source's default (False), found them equal, and silently short-circuited to "already configured" without commenting the repo line out. Widened the predicate to__grains__["os_family"] == "Debian"so all apt-based distros normalize the flag consistently. #60184Documented the interaction between the
retrystate option and requisites indoc/ref/states/requisites.rst, and added a documented truth-table reference covering how each requisite responds to the four possible target outcomes (skipped, failed, succeeded-no-change, succeeded-with-changes). A new functional test (tests/pytests/functional/modules/state/requisites/test_documented_truth_table.py) asserts each documented cell to keep the documentation honest. #60246Added a GitLab subsection to the Git Fileserver Backend Walkthrough's Authentication section covering deploy tokens, project access tokens, personal access tokens, and SSH deploy keys. Documents the typical 401 failure modes (expired tokens, missing
read_repositoryscope) so that operators do not chase Salt-side configuration when the cause is GitLab-side. #60809Fixed a race in
tests/pytests/integration/cli/test_salt.py::test_interrupt_on_long_running_jobthat intermittently failed on slow CI hosts (Photon OS 5 Arm64, both tcp(fips) and zeromq(fips)). The test used a fixedtime.sleep(2)before sendingSIGINT, but on slow hosts the salt CLI had not yet published its job (pub_data["jid"]was still unset), so the signal handler emitted onlyExiting gracefully on Ctrl-cwithout a jid and theThis job's jid isassertion failed. The test now waits on the master'ssalt/job/*/newevent viaevent_listenerto guarantee the job has been published before interrupting the CLI. #60963Fixed
grains.filter_by(andpillar.filter_by/match.filter_by) failing to match lookup keys that contain fnmatch glob metacharacters such as[and](for example GPU/PCI model strings); keys are now matched exactly before being treated as a glob. #60976Documented in
doc/topics/orchestrate/orchestrate_runner.rsthowsalt.state's aggregateresultis computed, how to useallow_failto express "succeed if at least N minions returned ok", and how to compute N dynamically from the matched-minion count. #60979Fixed _gen_keep_files so the require filter only matches dict requisites; a bare-string requisite ID containing "file" no longer raises "string indices must be integers". #61042
Replaced the broken slots example in
doc/topics/slots/index.rstwith a runnable example usingtest.echoandgrains.get, and added a documented limitations section. The new functional testtests/pytests/functional/test_slots_documented.pyrenders the example throughstate.applyand asserts the slot-resolved values land in the state arguments. #61073Fixed minion crashing on startup when the
grainsconfig option was present but not a mapping (e.g.grains:with no value, an empty string, or a scalar), which previously caused aTypeError: 'NoneType' object is not iterableand similar. Any non-dict value is now silently defaulted to an empty dict, and the required shape of thegrainsoption is documented in the minion configuration reference. #61321Fixed managing users on NAPALM (proxy) minions.
netusers.managedno longer raisesAttributeError: 'NoneType' object has no attribute 'update'when the state declares nodefaults, andusers.set_users/users.delete_usersno longer fail withLocal file source set_users does not exist. The bare template names these functions pass tonet.load_templatestopped resolving when native NAPALM template support was removed in the Sodium release (that removal was meant to spare thenetusersstate module); they now resolve the NAPALM-shipped per-driver template to an absolute path and render it through the Salt pipeline.netusers.managedalso now refuses to proceed when it would manage an empty set of users, rather than removing every account on the device. #62170Fix salt-api hanging when an eauth
/loginrequest omitspasswordorusername.salt.auth.LoadAuth.__auth_callnow catches theSaltInvocationErrorraised bysalt.utils.args.format_callfor malformed payloads and returnsFalseinstead of letting the exception escape into the ZeroMQ transport, which previously caused the client to wait for the full request retry cycle (~3 minutes) and blocked salt-api workers. #62188Added a netplan provider for
network.managedso it manages the netplan YAML under/etc/netplan/on netplan-based systems (Ubuntu 18.04+ and Debian where netplan is the active renderer) instead of writing/etc/network/interfaces, which netplan ignores. The newnetplan_ipmodule claims theipvirtual when thenetplancommand and/etc/netplanare present, anddebian_ipdefers to it in that case. #62219Refreshed the Git Fileserver Backend Walkthrough to drop EOL platform notes (Ubuntu 14.04, Debian Wheezy, RHEL 7.3-era CFFI quirks) and recommend the pygit2/GitPython versions that match
requirements/base.txtand the CI lockfiles (pygit2 1.13.1+/1.19.2+ and GitPython 3.1.50+). Salt's runtimeGITPYTHON_MINVER/PYGIT2_MINVERfloors are unchanged. #62260Fixed a race in concurrent state/orchestration renders where the active-HighState stack was shared on the class, so parallel reactor renders corrupted one another and failed with
IndexError(empty pydsl render stack) orKeyError: '__env__'(spurious conflicting-ID). The stack and the cached pydsl top-file matches are now isolated per execution context. #63056Fixed
Cloud.vm_config()to deep-mergevm_overridesinto the profile so nested keys such asdevices.diskare preserved instead of being replaced by a shallowdict.update. #63351Fixed
sql_baseext_pillar withas_json: Truecrashing withTypeError: Cannot update using non-dict types in dictupdate.update()when the database driver returns JSON columns asstrorbytes(for example MySQLdb and some PyMySQL configurations). The row is now JSON-decoded before merging. #63684Do not allow runas env retrieval to block. #63901
Fixed returner option parsing so that configured falsy values (
0,0.0,False,[]) are no longer silently replaced by the returner's default value. #63980Fixed
grains.append(and by extensiongrains.list_present) leaking acollections.defaultdictinto persisted grain state, which caused siblinglist_presentcalls under a shared nested path to fail with "not a valid list". #64017Fixed
salt.modules.linux_shadowandsalt.modules.solaris_shadowfailing on Python 3.13, where the standard-libraryspwdmodule has been removed. Both modules now parse/etc/shadowdirectly. #64264Fixed
selinux.port_get_policyraisingAttributeError: 'NoneType' object has no attribute 'group'whensemanage port -loutput cannot be parsed (e.g. Fedora 38+); it now raisesCommandExecutionErrorinstead. #64583Fixed deltaproxy sub-proxies sharing the control minion's
scheduleandbeaconsdicts.subproxy_post_master_initbuilds each sub-proxy's opts with a shallowopts.copy(), so every sub-proxy'sopts["schedule"](andopts["beacons"]) was the same dict object as the control minion's. The schedule/beacon helpers mutate those dicts in place, so each sub-proxy'sadd_job("__proxy_keepalive", ...)overwrote the same key and only one of N sub-proxies kept a keepalive job (per-sub-proxy beacons collided the same way). Each sub-proxy now gets its own schedule and beacon storage. #65088Documented SLS include resolution and ordering in
doc/ref/states/include.rst, including how the depth-first include walk, the role of requisites and theorderglobal state argument together determine execution order, with a worked example. #65229Modernized
tests/pytests/unit/utils/test_thin.pyto use thetmp_pathfixture andtests.conftest.CODE_DIRinstead ofRUNTIME_VARS, addressing review feedback on #65373. #65373Fixed
junos.rpc(used bynapalm.junos_rpc) so the reserved__kwarg__marker carried in through__pub_argis stripped before the request is sent to the device. Previously aget-configcall with afilterwould fail after upgrading from 3004, because the marker leaked into the RPC options. #65867Fixed MasterKeys.gen_signature signing raw PEM bytes instead of the clean_key()-normalized form, causing master_use_pubkey_signature verification to always fail against the pub_key transmitted in the auth reply. #66259
Fixed error handling when the returner configured as
master_job_cachefails to load; the error dict returned by_prep_jidis now propagated back toLocalClientas a proper error instead of being passed through as the jid and blowing up infire_eventwithTypeError: expected str, bytes, or bytearray not <class 'dict'>. #66457Removed the temporary Fedora 40 skips from
tests/pytests/integration/master/test_peer.py::test_peer_communicationandtests/pytests/integration/modules/grains/test_append.py::test_grains_remove_add. Fedora 40 reached end-of-life on 2025-05-13 and the tests now pass without the workaround. #66540Serialize
set_umask/get_umaskwith a lock. The umask is process-global, so concurrent calls from different threads could restore a stale value and leave the process umask permanently changed — salt-api under rest_cherrypy would get stuck at0o277and return 500 for everyclient=sshrequest until restarted. #66607pkg.add_repo_key/pkgrepo.managed(withaptkey: False) now write keyring files under/usr/share/keyrings/or/etc/apt/keyrings/with world-readable permissions (0644), regardless of the process umask. Previously, on systems hardened with a restrictive umask (e.g. 077), the keyring file ended up readable only by root, causingapt-get updateto fail withNO_PUBKEYerrors since the unprivileged_aptuser could no longer read it. #66731Added a "Pillar Merge Strategies" section to
doc/topics/pillar/index.rstsummarising every value accepted bypillar_source_merging_strategy(smart,recurse,aggregate,overwrite,none) and howpillar_merge_listsandpillar_includes_override_slsaffect the merged result, with a worked example. #66733Fix a crash on startup on FreeBSD when /var/run/dmesg.boot contains non-UTF8 characters. #66764
Fixed the
fileserver.updaterunner raisingPassed invalid arguments: update() got an unexpected keyword argument '__pub_user'when invoked throughsaltutil.runneror an orchestration, by stripping publisher__pub_*metadata from the kwargs before forwarding them to the fileserver backends. #66793Remove usage of spwd #67119
Added back support for init.d service scripts #67765
Fixed a race in the minion's
AsyncAuth._authenticatethat raisedAttributeError: 'AsyncAuth' object has no attribute '_creds'and silently severed master communication when a siblingAsyncAuthpopulatedcreds_mapbetween construction and the coroutine'skey not in creds_mapcheck. #67947Fixed the
slack.post_messageexecution module and state so calls no longer fail withlegacy_custom_bots_deprecated. Thefrom_nameandiconarguments are now optional and, when omitted, the deprecatedusername/icon_urlfields are no longer forwarded to Slack'schat.postMessageAPI. Configure the display name and icon in the Slack app settings instead. #67948Fixed
pkg.group_listandpkg.group_infoon dnf5 systems (Fedora 41+, RHEL/AlmaLinux 10). dnf5 changed thegroup list/group infooutput format, which the yum/dnf parser did not understand, so the group functions (andpkg.group_installed) returned empty or incorrect data. The group name column is now tokenized so a name containing the word "yes" or "no" is no longer mistaken for the installed column. #67975Fixed
pkg.installedwith asources:entry pointing at a missingsalt://URL to raise a clearCommandExecutionErrornaming the source, rather than propagating aFalsefromcp.cache_filethat later crashed with a crypticTypeErrorindpkg_lowpkg.bin_pkg_info. #68002Fix
saltbatch mode incorrectly treating transport-level error payloads as minion IDs, preventing spuriousMinion 'error' failed to respondmessages and hardening duplicate return handling. #68672Fixed a winrm detection bug in salt-cloud. #68768
Fixed
salt.utils.systemdusingsubprocess.run(capture_output=True), which is Python 3.7+, so the module remains importable and callable on the Python 3.6 targets that salt-ssh's thin still advertises support for. Replaced with the equivalentstdout=subprocess.PIPE/stderr=subprocess.PIPEform instatus()and_pid_to_service_systemctl(). #68778Fix
pip.installedstate reinstalling packages on every run even when the correct version is already present:pip.list_freeze_parsenow normalizes package names (lowercase, hyphens) consistent withpip.list, so that packages whosepip freezename uses underscores or mixed case (e.g.requests_oauthlib) are correctly detected as already installed when looked up by their normalized name.The post-install check in
pip.installednow also recognizes"Requirement already satisfied:"(modern pip ≥ 10.0) in addition to the old"Requirement already up-to-date:"message, preventing packages confirmed as already present from being falsely reported as changed. #68784
Fixed
salt.utils.state.get_sls_optsclobbering the configuredpillarenvwithNonewhenpillarenv_from_saltenvis enabled but the caller does not pass explicitsaltenv/pillarenvkwargs. A barestate.highstate/state.apply(or in-templatepillar.getcalls that trigger a pillar refresh) on a minion whose config sets bothpillarenv: <env>andpillarenv_from_saltenv: truenow correctly honors the configured environment. #68791Fixed an issue in chocolatey.installed state where packages were always reinstalled. #68827
Fixed
mac_brew_pkg.homebrew_prefix()triggering asupassword prompt (orsu: Sorryerror) on every invocation when thebrewbinary is owned by the current user. The probe now only passesrunas=tocmdmod.runwhen the brew binary owner differs from the current process user, avoiding the unconditionalsu -lwrap on macOS. #69027Fixed
salt.returners.pgjsonb.prep_jidandget_jidsraisingAttributeErrorwhen thesalt.utils.jidsubmodule was not loaded transitively by another import. The pgjsonb module now importssalt.utils.jidexplicitly. #69042Fixed
salt.returners.pgjsonbwriting database errors tosys.stderrinstead of Salt's logger. Errors from_get_serv,_purge_jobsand_archive_jobsare now reported vialog.exception, so they reach the configuredlog_file/ syslog destination on a daemonized master, including a full traceback. The unusedimport sysis also dropped. #69048Fixed
salt.returners.pgjsonb._purge_jobsand_archive_jobsdeleting or archiving the parentjidsrow as soon as a singlesalt_returnsrow for that jid was older than the cutoff, even when newer rows for the same jid existed. For long-running jobs whose minions answer at staggered times, this orphaned the recentsalt_returnsrows in the source table and produced an inconsistent archive. The predicate now keeps the parent until everysalt_returnsrow for the jid is older than the cutoff (EXISTS ... AND NOT EXISTS ...antijoin). #69060Fixed
salt.returners.pgjsonb.get_funraising a SQL syntax error on PostgreSQL because of MySQL-style backtick quoting (MAX(`jid`)) left over from a copy-paste of themysqlreturner. The query now uses unquoted identifiers, which is valid on PostgreSQL. #69062Fixed
salt.returners.pgjsonb.get_funreturning the wrong row per minion when jids are not lexicographically sortable as timestamps. The previous SQL usedMAX(jid)to pick the "latest" return, which was correct only for Salt's default jid format (YYYYMMDDHHMMSSffffffand thenanovariant). Deployments that overridemaster_job_cache.gen_jid(custom prep_jid emitting UUIDs, snowflake ids, or any non-sortable scheme) -- or that hold rows written under different jid formats from a past config change -- got a silently wrong answer. The query now orders byalter_time DESCand picks one row per minion viaDISTINCT ON, so "latest" is determined from the timestamp Postgres populates viaDEFAULT NOW(). #69064Fixed
salt-api'sLogoutendpoint not revoking the underlying Salt eauth token.Logout.POSTonly expired the CherryPy session cookie and regenerated the server-side session id, leaving the Salt token in the configuredeauth_tokensbackend (localfs/redis/etc.) valid until itstoken_expire(12 hours by default). Anyone who had observed the token value could keep using it as a bearer credential throughX-Auth-Token: <token>even after the user thought they had logged out. The endpoint now callssalt.auth.LoadAuth(self.opts).rm_tokenon the session token before expiring the cookie, so logout actually invalidates the bearer credential. If the token backend is unreachable the failure is logged and the cookie is still expired, so the user-visible logout flow always completes. #69067Fix
AttributeError: 'NoneType' object has no attribute 'set_result'raised fromsalt.transport.tcp._TCPPubServerPublisher._connectwhen the publisher'sclose()runs concurrently with an in-flight_connect()task.close()now resolves the in-flight connect future with aClosingErrorbefore nulling it, so callers thatawaitthe future returned byconnect()get a definitive answer instead of hanging on an orphan. #69187Fixed
salt.exceptions.AuthenticationError: message authentication failederrors seen roughly everypublish_sessioninterval on minions in a Salt Master Cluster with a shared cachedir (e.g. GlusterFS). Each master's in-memorysessionscache is now invalidated when a peer master rotates the sharedsessions/<minion>file, so the request-server no longer serves stale session keys after another master has rotated them on disk. #69193Fix
x509.certificate_managedfailing with "Bad decrypt" when the signing policy is sourced from pillar by unmasking pillar values inx509_v2._get_signing_policy. #69253Fixed
SerializerExtension.load_yamlraisingAttributeErrorinstead of aTemplateRuntimeErrorwhen YAML parsing fails under PyYAML's libyaml (C) loader, which leavesproblem_mark.bufferunset. #69533Fixed
manage.status,manage.up, andmanage.downreporting unresponsive minions as up. Since 3007.0manage._pinggatheredtest.pingreturns withget_cli_event_returns(expect_minions=True), whose per-target timeout placeholders were counted as returns, so every key-accepted minion landed inupanddownwas always empty._pingnow requests only real returns (expect_minions=False), so dead minions are correctly reported as down. #69582Fixed
saltutil.runnerandsaltutil.wheelraisingKeyError: "getpwnam(): name not found: 'sudo_<user>'"when an orchestration (salt-run state.orchestrate) was launched undersudoand the rendered SLS calledsalt.saltutil.runnerfrom Jinja.state.orchestrateoverwrites__opts__["user"]with the publishing user (salt.utils.user.get_specific_user(), which returns"sudo_<login>"undersudo), and the post-#67716 privilege-drop path then tried tochugidto that non-existent account. The privilege-drop helper now validates the candidate against the passwd database and skips the drop when the configureduseris not a real account, falling back to the historical in-process behavior. #69600Fixed
pkg.installedon RPM (yum/dnf) wrongly reportingNo version matching '<ver>' found for package '<name>.<arch>' (available: none)for an already-installed, architecture-qualified package (e.g.foo.x86_64) passed viapkgs. Since #68932 the preflight runs withsplit_arch=Falseand no longer normalizes the name, butpkg.list_pkgsis keyed by the arch-stripped name, so the package was mistaken for missing. The preflight now falls back to the normalized name, matching the existing_verify_installbehavior; APT multiarch names (foo:amd64) are unaffected. #69604Fixed
pkg.list_holdsreturning an empty list on dnf5 systems even when packages are held._list_holds_dnf5parsed/etc/dnf/versionlock.tomlthroughsalt.serializers.tomlmod, which depends on the third-partytomllibrary that is not bundled in the onedir packages; the parse failed silently andpkg.installedwithhold: Truere-held packages on every run. It now parses with the standard-librarytomllib(available once the onedir ships Python 3.11 in 3006.27, see #69526), falling back to thetomlserializer on older interpreters where it is installed. #69607Fixed the etcd cache
lsreturning nested leaf key names for a bank instead of the bank's immediate children. It now returns only the direct children of the bank, matching thelocalfscache, so grain (-G) targeting works withcache: etcd. #69616Fixed the
saltutil.runner/saltutil.wheelprivilege-drop child (added for #67716) hanging forever when the child died before returning a result (OOM kill,os._exit, or a segfault in a C extension such as libgit2), failing runners/wheels that spawn their own processes such as an orchestration containing aparallel: Truestate, and flattening the child's exception type toCommandExecutionError(which stoppedsaltutil.wheel'sSaltInvocationErrorhandling from working). #69618Restore Rocky Linux 9
unit zeromq 4CI green after the 3006.x→3007.x merge-forward pulled in 3006.x-only regression tests that don't fit the 3007.x runtime APIs. Adapt thetest_verify_master_*,test_authenticate_*_69442,test_maintenance_duration,test_minion_manager_stop_unblocks_resolve_dns_69466, andtest_event_unpack_with_SaltDeserializationErrortests to the 3007.xcrypt.write_keys()/MasterKeys.gen_signature/io_loop.create_task/LoadAuthinit / debug-log-on-skip contracts; skip thetest_gen_signature_signs_clean_keyvariants because the 3007.x cache-refactoredMasterKeys.gen_signaturesignspub.public_bytes()and cannot exhibit the #68930 whitespace-drift bug. #69624Fixed
HighStateandStateinit leaking their fileclient (and its ZeroMQ transport) when a later step in the constructor raises, which producedTransportWarning: Unclosed transport!messages duringsalt-call state.apply. #69637Fixed
salt.returners.get_returner_optionsso that attributes not present in the config now fall through to the supplieddefaultsvalue instead of being returned asNone. #69654Fixed minion-driven RPM upgrades getting SIGKILLed mid-transaction. The
%pre minionscriptlet's blockingsystemctl stop salt-minion.servicedeadlocked when the upgrade was driven by the running minion itself (viapkg.installedorpkg.install): the stop waited for every process in theKillMode=mixedcgroup to exit, including the salt worker executing the state, which was waiting ondnf, which was waiting on%pre. AfterTimeoutStopSecsystemd SIGKILLed the whole cgroup and the state run's return was lost.%pre minionnow walks the scriptlet's parent process chain, detects when the transaction was initiated from insidesalt-minion.service, and skips the in-scriptlet stop;%postand%posttransleave the still-running minion alone so the state completes normally and thecmd.run bg: Truerestart pattern from the FAQ can perform the actual restart in a detached child. #69656Fixed SLS rendering failure when a Jinja-interpolated
PrintableDictvalue contained a multi-line string longer than ~80 columns inside a YAML block scalar. The YAML double-quoted scalar emitted for such values is no longer folded across physical lines. #69658Fixed
onchanges/onchanges_anyrequisites treating a failed target state as a hard failure. Per the documented requisites truth table, a failedonchangestarget should be treated the same as a target with no changes: the dependent state does not run, but reportsresult=Truewith emptychanges, instead of hard-failing with a "One or more requisite failed" comment.Fixed
IndexErrorinState.__eval_slotwhen a slot expression has no dotted post-)accessor, and fixed quoted append operands (e.g.~ "/suffix") not having their surrounding quotes stripped before being concatenated to the slot result. #69661Fixed
salt.utils.vt.setwinsizeandgetwinsizeto passtermios.TIOCSWINSZ/TIOCGWINSZthrough tofcntl.ioctlunchanged, instead of sign-flipping the macOS value to a negative literal. Python 3.14 rejects negative ioctl request values withErrno 25, which brokesalt-sshon the 3008.x macOS onedir becausesetwinsizeruns inside every spawned pty child'spreexec_fn. #69705Fixed file.serialize (dataset_pillar) and file.decode (contents_pillar) writing the pillar redaction placeholder (
**********) into the managed file instead of the real values on 3008 and later, where pillar.get masks by default. #69709Fixed several execution modules reading pillar values without
unmask=Trueon 3008 and later, wherepillar.getmasks by default, so they received the redaction placeholder (**********) instead of the real value:gpgand the deb/rpm pkgbuild modules (signing passphrase and key names),x509andssh_pki(signing policies),tls(certificate extensions),oracle(connection data), and the pyobjectsMaprenderer (merge pillar). #69711Fixed the intermittent
duplicate HTTP post method definitionfailure in the -W parallel docs builds (Prepare Release and Documentation jobs) by marking the HTTP routes documented on the rest_tornado and rest_wsgi pages with:noindex:, leaving rest_cherrypy as the single indexed instance of each shared route. #69724Added the missing
POST /tokenandGET /appsections to the rest_cherrypy REST API reference; their docstrings were never rendered because the page lacked autoclass entries for the Token and App handlers. #69726Fixed the Rocky Linux 9 integration tcp/zeromq CI jobs failing most PR runs: the startup_states and salt_call ownership test fixtures left their extra minions' accepted keys on the shared session master after stopping the minions, so later netapi tests targeting
*matched dead minions (wrong minion lists and 30 second timeouts). The fixtures now delete their minion keys at teardown. #69728Fixed the master logging
Event iteration failed with exception: 'list' object has no attribute 'items'for every failing state compilation: the return of a failed compile is a list of error strings, not a mapping of state results, and the event tagger assumed a dict. #69730Fixed
cp._clientraisingLoaderError(surfaced asKeyError: '__file_client__') when the executing loader has not packed a__file_client__context. It now falls back to building a file client from__opts__, socp.cache_fileand othersalt://fetches work under loaders that do not pack a file client. #69734Fixed the flaky ssh test_renderer_file: salt-ssh slsutil.renderer does not ship a rendered file's jinja imports (map.jinja) to the target, so the renderer tests only passed when an earlier state test had warmed the salt-ssh file cache. Prime the cache in the fixture so they are deterministic. #69738
Fixed
localfscache leaking temporary files and raisingFileNotFoundErrorwhen the cache key contained a path separator (e.g. apillarenvwith/in it).localfs.store()now creates the parent directory of the target file and always removes itstempfile.mkstempscratch file on failure. #69741Fixed
docker_network.presentreporting spurious changes and recreating a network on every run when asubnetwas specified without agateway. Docker auto-assigns the subnet's first host address as the gateway and reports it on inspect, while Salt's desired config omits the key entirely;docker.compare_networksnow ignores a one-sided gateway only when it matches that auto-assigned default, so an explicitly added, removed, or changed gateway is still detected as a real change. #69746Fix
Nonce verification erroron scheduled highstate under concurrency (crossed responses between forked minion siblings colliding on ZMQ ROUTER identity, and mid-flight session_crypticle re-resolve). #69753Fixed NTP, SNMP and RPM-probe configuration on NAPALM (proxy) minions.
ntp.set_peers/set_servers/delete_peers/delete_servers,snmp.update_config/remove_configandprobes.set_probes/delete_probes/schedule_probesno longer fail withLocal file source set_ntp_peers does not exist. Likeusers.set_users(see #62170), these functions passed bare template names tonet.load_template, which stopped resolving when native NAPALM template support was removed in the Sodium release. They now resolve the NAPALM-shipped per-driver template to an absolute path and render it through the Salt pipeline. #69793Fixed several bugs in the
netsnmpandnetntpNAPALM states.netsnmpno longer crashes withAttributeError: 'NoneType' object has no attribute 'update'when nodefaultsare declared, no longer raisesTypeErroron a dict-form SNMP community, and no longer silently drops (and reports success for) a changedlocation/contact/chassis_id.netntpnow actually converts domain-name peers/servers to IP addresses instead of discarding the resolved values, and no longer reports a device-retrieval failure as "Device configured properly.". #69794Fixed two bugs in the
napalm_networkexecution module.net.load_templateno longer crashes withAttributeError: 'NoneType' object has no attribute 'startswith'when rendering an inlinetemplate_source(notemplate_name), and_config_logicnow honourscommit_atwhen scheduling a commit instead of passingcommit_infor both times. #69795Fixed three bugs in the shared NAPALM support code.
salt.utils.napalm.get_device_optsno longer crashes onoptional_args: nulland no longer mutates the caller's opts/pillar;force_reconnectno longer raisesKeyError: 'proxy'on a straight (non-proxy) NAPALM minion; and the NAPALM proxy's shutdown error log no longer renders the port as a tuple. #69796Fixed four bugs in the
napalm_modandnapalm_formulaexecution modules.napalm.rpcnow honours a user-suppliednapalm_rpc_mapoverride instead of letting the built-in defaults clobber it;napalm.netmiko_argsraises a clear error (rather than a rawKeyError) for anosgrain with no Netmiko device type;napalm_formula.container_pathnow honours itskey/container/delimarguments; andnapalm_formula.render_fieldno longer raisesKeyErrorwhen theosgrain is absent. #69797Fix Codecov CLI installation step by replacing dead keybase.io PGP key URL. #69800
Fix loader race that could randomly mark OS-specific virtual modules (e.g.
postgres) as unavailable when a sibling implementation (e.g.deb_postgres) was evaluated first and poisoned the shared__virtualname__in the missing-modules cache. #69806Fixed
state.apply queue=Trueallowing more than one concurrentstate.*execution when the new job's JID sorted lexically higher than an already-running job's JID.check_prior_running_statesnow blocks on any real running state.* process regardless of JID ordering, while still allowing the state queue processor to dequeue the oldest queued placeholder without deadlocking on younger queued siblings. #69825Fixed several master, minion and salt-api resource leaks observed under sustained load:
salt-master'sMWorkerQueueno longer leaks a file descriptor persaltCLI invocation from the master host (a stable ZMQ routing identity is now applied when the current process was launched via a salt CLI entry point, in addition to the existing__role-based gate), and the TCP transportMessageClientnow tears down synchronously onclose()-- cancelling any pending request futures withSaltReqTimeoutErrorand clearing the reconnect race that kept_stream_returnrunning past shutdown -- sosalt-apino longer accumulates orphanedMessageClientgraphs under CherryPy request churn. #69847Updated the pip shipped in Salt's packaged onedir builds from 25.2 to 26.1.2. This removes the need for Salt's temporary hand-patch of pip's vendored urllib3 (CVE-2025-66418, CVE-2026-21441), since pip 26.1.2 already ships a genuine, upstream-fixed urllib3 2.6.3. #69852
Deferred OpenTelemetry imports in
salt.utils.tracingandsalt.utils.metricsso daemons no longer pay the ~15 MB per-process OTel import cost whentracing.enabled/metrics.enabledare false (the default). On a stress-tested salt-master container (~15 Python processes) this reclaims ~225 MB per subsystem — restoring the pre-3008.x baseline. Public API is unchanged; the imports happen on firstconfigure(...)/start_span(...)/counter(...)call once the enabled flag is set. #69855Fixed unbounded socket accumulation in the master's
EventPublisherprocess (observed at 7500+ open sockets / 150 GB anon RSS after 24 h uptime on 3008.2). The 3008.xPubServernow registers a stream close callback so subscribers are pruned fromPubServer.clientsthe instant the peer disconnects (mirroring 3006.x'sIPCMessagePublisher.handle_connection). In addition,SaltEvent.__del__now emits aResourceWarningwhen the event bus is garbage-collected without an explicitdestroy()/withcontext, so callers that inadvertently leakMasterEvent/SaltEventinstances (e.g. inlinesalt.utils.event.get_master_event(opts, sock_dir).fire_event(...)) surface loudly rather than silently accumulatingmaster_event_pull.ipc/master_event_pub.ipcsockets.__del__deliberately does not close the sockets — the explicit-cleanup contract added by commit0c3f53d9172stays in place. #69857The release workflow now fails immediately with a clear error message if more than one draft release exists for the target version, preventing silent publication of the wrong artifact set. #69861
Skip the PyPI upload step for patch releases (versions containing a
-Nsuffix, e.g.3008.1-1) since those are RPM-specific packaging revisions and the base Python package is already on PyPI. #69862The release workflow no longer publishes the draft GitHub release when the PyPI upload step fails. #69863
Fix master-cluster peer traffic honoring
cluster_pool_portinstead of falling back to hardcoded55596;cluster_portaccepted as deprecated alias with a warning. #69877Per-resource-type execution loaders (
salt.loader.resource_modules) no longer include stocksalt/modules/*— the loader is now deny-by-default and exposes only modules discovered underresources/<rtype>/modules/override directories. Managing-minion access remains available via the__minion__escape hatch. Restores the documented Resources safety contract:salt <resource-id> cmd.run …(orgrains.setval,file.remove, etc.) now returns "Function '…' is not supported for resource type '…'" instead of silently executing on the managing minion. #69881Fixed stateful management of PKCS#7 certificates with appended chain using
x509_v2.certificate_managed. Also fixed loading of PKCS#7-encoded certificate bundles withsalt.utils.x509.load_cert. #69893Fixed
x509_v2.certificate_manageddeleting symlinks in test mode iffollow_symlinkswas explicitly set tofalse#69895Fixed traceback when
signing_certwas not passed tox509_v2.crl_managedorx509_v2.create_crl. It has always been required. #69896Fixed some tracebacks being thrown instead of errors being reported in
x509_v2. Fixed a typo in the rendered output ofissuingDistributionPointandcertificatePoliciesextensions. Fixed rendered prefix of anRFC822Name. #69898Added support for
otherNamedefinitions inx509_v2, e.g. inside asubjectAltNamesextension. #69900Include PyYAML manylinux wheel in Linux onedir builds so
yaml.CSafeLoader(and the libyaml-backed emitter) are available. Previously the--no-binary=:all:pip invocation forced a PyYAML source build under the relenv toolchain, which lacks libyaml headers; PyYAML silently fell back to the pure-Python parser, significantly slowing config, pillar, and state parsing on large deployments. #69907Fixed the master event bus keeping a broken pusher connection after a failed send, which caused every subsequent job return on that worker to fail and silently drop the job return instead of reconnecting. #69914
Fixed large HTTP(S) downloads (over 100MiB) via
cp.cache_file/fileclient.get_urlbeing silently truncated, which could leavewinrepo_nginstallers (and other largesalt://-adjacent HTTP downloads) incomplete without raising an error. Tornado's HTTPClient enforces a defaultmax_buffer_sizeof 100MiB independently ofmax_body_size; when a server doesn't send aContent-Lengthheader, Salt read the response until the connection closed, hitting that limit and truncating the download.max_buffer_sizeis now passed alongsidemax_body_sizeso both track thehttp_max_bodyoption.fileclient.get_urlnow also compares the number of bytes received against any advertisedContent-Lengthand raises a clear error instead of caching a partial file if they don't match, and therequestsbackend now streams responses viaiter_contentand catchesrequests.exceptions.RequestException, so a connection dropped mid-download is reported the same way as other HTTP errors instead of crashing with an unhandled exception. #69916Give each daemon
AsyncReqMessageClienta per-instance UUID as its ZMQIDENTITY, so the master ROUTER's routing-id entry maps 1:1 to a client whose lifecycle Salt itself owns. Replaces the earlier process-wide_REQ_IDENTITY_SLOTcounter whose state was inherited acrossfork()and produced colliding identities in forked minion children (root cause of #69753). #69920Memoized the
SaltStackVersionconstruction insidesalt.utils.versions.warn_until()so hot paths that fire deprecation-warning calls per event (for example theTCPPubClient/TCPReqServer/MessageClientdeprecated aliases) no longer allocate two freshSaltStackVersion(and, transitively,packaging.version.Version) objects on every call. Measured on a 4h stress rig, the master'sEventPublisherwas allocating ~1.4MVersionobjects (2.5 GB of transient allocation churn) per 90 s window; after the patch, the same 10 000-call loop makes zeroSaltStackVersionconstructions on the repeated-argument path (100% reduction). Per-process RSS impact on the WebSocket-transport master:EventPublisherpeak dropped from 271 MB to 214 MB (-57 MB / -21%). #69921Relenv 0.22.18
Fix pip 26.2 compatibility in InstallRequirement.install/install_wheel wrappers - #314
Fix Windows 3.10 native builds failing on find_python.bat's EOL fallback - #315
Preserve caller cwd in macOS shebang launcher - #311
Share Linux build deps via artifact, not cache - #310 #69928
Wired the
ipc_write_buffermaster option through to the TCP transport in 3008.x. The option remained in the config schema after the legacysalt.transport.ipcmodule was removed but was no longer read by any code path, so setting it inmaster.confhad no effect. It now caps the per-stream Tornado outboundmax_write_buffer_sizeon bothPubServer(event-bus subscribers, plaintext and SSL-delayed paths) andSaltMessageServer(request/reply clients), matching the semantics of the legacy IPC module's per-connection cap. The default (unset /0) preserves the existing unlimited-buffer behavior; operators opt in by setting an explicit byte value. #69930Removed the dead
salt.utils.versions.reqs.msgpack > "0.5.2"guard insidesalt.utils.msgpack._sanitize_msgpack_unpack_kwargs. The guard could never be false on any supported install (3006.x pinsmsgpack>=1.1.2, 3007.x/3008.x pinmsgpack>=1.1.0, and even the ancient CentOS 7 EPELpython-msgpackwas 0.5.6) but its per-callRequirement.__gt__walk allocated two freshpackaging.version.Versionobjects on everyunpackb/packb. Under stress this fired ~4 million times per 60 s in the master'sEventPublisheralone, cutting the process's total transient allocation churn by more than half once eliminated. #69931Update bootstrap script to v2026.08.03 #69935
Fixed
salt.utils.optsdict.OptsDict.__len__to compute the key count directly instead of callingiter(self), which materialized a fresh temporary dict of every key/value in the copy-on-write chain, cleared the underlying dict, and re-inserted every entry -- all just to returndict.__len__(self). Everylen(opts)call was therefore O(N) allocations plus 2 × O(N) dict mutations. The new implementation counts via_get_all_keys()minus_DELETEDsentinels in_local(no value walk, no dict rebuild); Python'slen()slot dispatches through the override, so the previous underlying-dict sync (a side effect of__iter__) was never required forlen(). #69938Cache the libcrypto-backed RSAX931 verifier / signer objects on
salt.crypt.PublicKeyandPrivateKeyinstances and routePublicKey.from_filethrough an mtime-keyed path cache. Eliminates thousands of redundant PEM parses and libcryptoBIO/RSAallocations per minute in a busy master'sMWorkerprocesses.PublicKey.verifyandPublicKey.decryptfall back to a one-shot reload-and-retry when a cached key doesn't validate, preserving the pre-cache behavior for on-disk rotations that don't bump mtime. #69940Restore mtime-based cache eviction on
salt.crypt.get_rsa_keyso a rotated private key on disk is reloaded without requiring a process restart. #69941Fixed
x509_v2.certificate_managed_wrapperswallowing arguments incertificate_managedintended forfile.managed#69954Fixed
cmd.scriptdeleting the temporary script before a background (bg=True) process could run it. This caused PowerShell-File"does not exist" errors on Windows and "No such file or directory" on POSIX. Background runs now use a self-cleaning wrapper so the child removes the tempfile after exit. Refs #69959 #50273 #69959Corrected 25 docstring
:param:fields that named an argument the callable does not take. #69966Fixed
pem_fingerso a PEM key string fingerprints the same as the same key on disk.master_fingernow matchessalt-key -F. #69970Fixed
whitelist_modulesso it only restricts what remote callers can invoke. Whitelisted modules can now compose with non-whitelisted modules via__salt__[...], so a minion configured withwhitelist_modules: [test, mycompany, saltutil]refusessalt '*' cmd.run 'rm -rf /'from the master whilemycompany.deploy(which internally calls__salt__["cmd.run"](...)) still works. #69983Fix MWorker deadlock caused by nested
SyncWrapperrecursion intcp.PublishServer.publish. Whenfire_eventinvokedpublishinside a running io_loop, the outerSaltEvent.pusherSyncWrapper's thread spawned another SyncWrapper which deadlocked onthreading.Thread.join(), wedging all MWorkers. Only triggered whenmaster_async_mworkeris enabled; the deadlock cannot occur on the default sync MWorker path. #69986Fix
MWorkerQueueaccumulating dead-peer state under sustained connect/disconnect churn. The pooledRequestServerROUTER now sets ZMTP heartbeat, TCP keepalive,ROUTER_HANDOVER, and aLINGERtimeout so libzmq detects and reaps dead peers instead of retaining them in_anonymous_pipes. #69987Fix master
PubServerwedge caused by a single slow TCP subscriber. Rewrotepublish_payloadto fire-and-forget each write with a per-subscriberpublish_drain_timeout(default 60s) enforced viaasyncio.wait_for. Slow subscribers are closed and removed fromself.clientsinstead of blocking every subsequent publish. #69988Cache libcrypto
RSAX931Verifier/RSAX931Signerbridge objects onPublicKey/PrivateKeyinstances and cachePublicKey.from_fileresults keyed on file mtime. Under sustained master loadmemrayshowed ~5000RSAX931Verifier.__init__calls per 60 s against a matchingPublicKey.decryptcount -- fully eliminated. Complements upstream6cf49f5364eand the existing_get_key_with_evictmemoize which cache at the private-key file layer. #69989Cache
DictProxy/ListProxywrappers inOptsDict.__getitem__keyed on the underlying object'sid(). Prevents massive object churn on hot-path reads likeopts["file_roots"]under sustained load. Cache is invalidated in__setitem__/__delitem__. #69990Fix ~451 leaked socketpair FDs per minion under sustained re-auth churn.
zeromq.RequestClient.close()now schedules an async graceful-drain task that awaits_send_recv_exit_futurebefore tearing down the ZMQ socket and context, mirroring the pattern fromAsyncReqMessageClient(#68637). AddsSyncWrapper.__del__that emitsResourceWarningfor wrappers GC'd without an explicitclose()(mirrorsSaltEvent.__del__atsalt/utils/event.py) to surface future missed-close bugs rather than silently leaking event loops and their held resources. #69991Set
PIP_DISABLE_PIP_VERSION_CHECK=1insalt-pipso every invocation no longer triggers pip's periodic "A new release of pip is available" HTTPS check against a packager-pinned onedir pip. Operators can opt back in by exportingPIP_DISABLE_PIP_VERSION_CHECK=0. #70024Fixed handling of several
x509_v2GeneralNames: nameConstraints URI/IP definitions, encoding of URI path segments with non-ASCII characters, URI IPv6 hostnames, URI without authority/scheme, DNSNames with non-standard wildcards, and others. #70041Fixed handling of
x509_v2basicConstraintspathlenwhen issuer certificate has an explicitpathlen: We now validate the requestedpathlenagainst the issuer certificate and default it to one lower if unspecified #70042Made
salt.utils.x509.load_pubkey'sget_encodingparameter work as expected #70046Fix minion graceful-stop path: signal in-flight job children in
Minion.subprocess_list(they were missed bykill_children), run registered finalize callbacks so<cachedir>/proc/<jid>is removed even whenSignalHandlingProcess._handle_signalsfiresos._exit, and emitsd_notify(STOPPING=1)on entry tostop_async. #70051Fixed a race between
RequestClient.close()and its_send_recvcoroutine in the ZeroMQ transport: closing the socket and terminating the context while_send_recvwas still midpoll()/recv()on it aborted the process inside libzmq on Windows (zmq.cpp errno_assert,EINVAL/EAGAIN), breaking every Windows integration test and packaged install/upgrade test that spawns asalt-call/saltCLI.close()now signals_send_recvwith a shutdown sentinel and, when called from a different thread than the one running the transport's event loop, waits for it to actually exit before tearing down the socket and context -- the same graceful-drain pattern already used to fix the related file-descriptor leak inRequestClient(#69991). Also normalizes the event loop passed tozmq.asynciowhen spawning_send_recv's task, since handing it atornado.ioloop.IOLoopwrapper instead of the underlyingasyncioloop is a documented cause of the same Windows libzmq abort.Scoped the
pyzmq<26cap inrequirements/zeromq.txt(added to work around a pyzmq 27.x memory leak on Arm64 CI runners) to non-Windows platforms. That cap left Windows on pyzmq 25.1.2, whose bundled Windows libzmq 4.3.4 build independently aborts inside libzmq on ordinaryRequestClientsend/recv -- the same symptom above, but not something the transport-level fix alone can resolve since it's a bug in that specific wheel. Windows now resolves to pyzmq>=27.1.0 (currently 27.2.0), which does not exhibit either the Arm64 leak (Arm64 CI runners are Linux/macOS, not Windows) or the abort. #70063Fix
SaltClientError: Invalid master keyon minions connecting through a load-balancer (HAProxy, F5, etc.) to a master cluster running withcluster_isolated_filesystem: True. The cluster join-reply handler now refreshes the master-keys cache (used by bothlocalfs_keyandmmap_keydrivers) and reloads the in-memorycluster_keyafter installing the wire-deliveredcluster.pem/cluster.pub, so every peer serves the same cluster public key to minions instead of the local pre-join placeholder that_setup_keyshad generated during startup. #70090Fix silent broadcast abort in
salt.transport.tcp.PubServer.publish_payload. OnlyStreamClosedErrorwas previously caught in the fan-out loop; a synchronoustornado.iostream.StreamBufferFullErrorfrom one subscriber (raised when the per-stream write buffer cap set byipc_write_bufferis exceeded) propagated out of the loop and every subscriber after the offender silently missed that payload. The buffer-full case is now handled identically to a closed stream: the offending peer is discarded and the broadcast continues to the rest. #70097Extend the
ipc_write_bufferoutbound write-buffer cap insalt.transport.tcpto every client-sideIOStreamwhere writes accumulate:_TCPPubServerPublisher(MWorkerfire_event->EventPublisherpull),MessageClient(minion return / master req),PublishClient(SUB channel), andRequestClient. Previously only server-accepted streams (PubServer.handle_streamandSaltMessageServer) honored the opt; the client-side streams defaulted tomax_write_buffer_size = None(unbounded), so a wedged consumer let the sender's tornado write buffer grow without limit and drove RSS climb until the process was OOM-killed. Opt-in, unset preserves prior behavior. #70098Route unclosed-resource
ResourceWarningfinalizers through Salt's logger in addition to Python'swarningsmodule. Python filtersResourceWarningby default, so a barewarnings.warn(..., ResourceWarning)from a__del__finalizer is silently dropped in production and callers that missed aclose()/destroy()/ context-manager contract never see the migration signal. Addssalt.utils.resource_warnings.warn_until_closewhich emits both theResourceWarningand a WARNING-level log record, and wires it into the 8 finalizers insalt/utils/event.py,salt/utils/asynchronous.py,salt/transport/tcp.py, andsalt/transport/ws.py. Fixes a real production incident where SSEAPE's fire-and-forgetget_master_event(...).fire_event(...)pattern leaked one unix socket per event once__del__-based cleanup was removed in commit0c3f53d9172; the intendedResourceWarningnever surfaced because the operator's logs run at WARNING or higher and Python's default filter dropped it. Also fixes fourwarnings.warnsites intcp.py/ws.pywhere a missingfprefix rendered{self!r}as a literal instead of interpolating.On this LTS branch the GC-time
destroy()fallback that commit0c3f53d9172had removed fromsalt.minion.MasterMinion,salt.runner.RunnerClient,salt.wheel.WheelClientandsalt.utils.event.SaltEventis restored inside__del__alongside the new loud warning, so callers that historically relied on GC-time cleanup do not silently leak sockets while migrating to explicitdestroy()/ context-manager use. A companion change onmaster(Potassium) drops the fallback and requires callers to be explicit; the WARNING-level log record here is the migration signal for that upcoming change. #70100Updated the pip shipped in Salt's packaged onedir builds from 26.1.2 to 26.2. This fixes CVE-2026-44432 (urllib3 decompression-bomb bypass), since pip 26.2 vendors a fixed urllib3 2.7.0.
pip 26.2 also completed its deprecation of applying
PIP_CONSTRAINTto PEP 517 build environments, sotools/pkg/build.pynow also setsPIP_BUILD_CONSTRAINTwherever it setsPIP_CONSTRAINT, keeping build-time dependencies (e.g. theCython<3.3pin needed for pyzmq) constrained during onedir/package builds. #70109Fixed inconsistent process title for the master's
FileserverUpdateprocess. It was previously registered asFileServerUpdate(capital S) on the initial fork and asFileserverUpdate(lowercase s) after a respawn, breaking log and process-title correlation. #70111Pin Cython<3.3 for pyzmq source builds broken by Cython 3.3.0. #70118, #70121
Fix
TypeError: default_int_handler expected 2 arguments, got 1insalt.utils.process.ProcessManager._handle_signalswhen SIGTERM is delivered to a forked child that inherited the handler.MasterPubServerChannel._publish_daemonand any other subprocess using this handler now shut down cleanly instead of crashing with an unhandled exception. #70123Preserve
EventPublisherprocess title acrossMasterPubServerChannel._publish_daemonrespawns so operator monitoring keyed on the process title continues to work after ProcessManager restarts the daemon. #70124, #70126Relenv 0.22.23
Fix Verify Builds on Python 3.14 (cffi 2.0.0 for 3.14, swig PyPI shim collision) - #316
Various native-build platform hardening across releases 0.22.19 - 0.22.23 #70133
Fix the
Combine Code Coveragejob on 3007.x by fetching the Codecov uploader signing key fromhttps://uploader.codecov.io/verification.gpg(thekeybase.io/codecovsecurityURL returns HTTP 404 and gpg exits non-zero underbash -e). #70136Relenv 0.22.25
Fix 2^n slowdown in wrap_sysconfig by making it idempotent (fixes Salt highstate hangs on long-lived Python 3.13+ onedir minions) - #321 / #325
Update openssl to 3.5.8 (0.22.24) #70142
Cap in-flight drain tasks per subscriber in
salt.transport.tcp.PubServer.publish_payloadby serializing each subscriber's writes through a dedicated writer coroutine reading from a boundedasyncio.Queue(default 500, configurable via the newpub_server_write_queue_sizemaster opt). Under a bursty producer the previous fire-and-forget path allocated oneasyncio.Taskper (subscriber × event) with no cap -- a 100k-event burst against 8 subscribers drove RSS to 2.9 GB and starved the io_loop._discard_slow_clientnow also cancels the writer task so the captured payload bytes are released immediately rather than pinned for up topublish_drain_timeoutseconds. #70147Declared the OS tools (
coreutils,grep,findutils,getent,sed,systemd,openssl) used by thesaltandsalt-minionRPM%pre/%post/%preun/%postun/%posttransscriptlets as scriptlet-scopedRequires, so package managers can correctly resolve install ordering instead of silently failing on minimal or air-gapped installs. #70149Fixed
salt-pipleaking the parent process'sPYTHONPATHinto the pip subprocess it spawns.salt-pipnow always runs pip withPYTHONPATHset to only salt's ownextrasdirectory, isolating it from the rest of the system as documented. Previously an inheritedPYTHONPATHpointing at an unrelated Python installation was prepended ontoextrasrather than replaced, so pip (especially with--force-reinstall) could find and uninstall packages belonging to that unrelated environment. #70151Reap pending
asyncio.Taskobjects onSyncWrapper._targetafter tornado'sio_loop.run_syncreturns. Any task scheduled on the wrapper-owned asyncio loop that outlives therun_syncwindow -- e.g. pyzmq future-based sockets and tornado's asyncio bridge fire tasks on the current asyncio loop -- was left pending, pinning its coroutine +contextvars.Contextuntilclose(). Long-lived driver processes (EventReturn,BatchManager) that don't callclose()in steady state accumulated ~18k retainedTask/coroutine/Contexttriples over 26 hours (~0.36 MB/hr). #70169Fixed leak of the minion's local
PublishServergraph (event_publisher->pub_sockSyncWrapper ->_TCPPubServerPublisher) when the minion exits throughcli.daemons.Minion.shutdown(KeyboardInterrupt, SaltSystemExit, early-exit guards) orMinionManagerGC.MinionManager.destroynow closesevent_publisherand destroysevent-- previously only the SIGTERMstop_asyncpath did, so non-SIGTERM shutdown paths triggered the three-warning cascade in issue #70175.PublishServer.closealso now callspub.close()on every_TCPPubServerPublishercached in_async_pub_by_loop(previously it closed the underlying stream only, leaving_closing = Falseand letting the publisher's__del__emit the "unclosed publisher client" warning at GC).PubServer._discard_on_closenow cancels the pending_stream_readTask and force-closes the Subscriber so the coroutine frame (with its 1 MiB msgpack Unpacker buffer) is released the moment a subscriber disconnects, closing the per-job leak path on the steady-state per-job path. Fire-and-forgetMinionEventcallers insalt.modules.event.fire_masterandsalt.modules.mine._mine_sendare now wrapped inwithblocks so thePublishServer/_TCPPubServerPublisher/SyncWrappertriad is torn down synchronously instead of leaking one bundle per invocation onto the minion event bus.salt.utils.error.fire_exceptionis also wrapped in awithblock so the temporarySaltEventit constructs closes both pusher and subscriber synchronously -- previously the helper (called fromsalt/minion.py:_thread_returnjob-exception path and fromsalt/metaproxy/{proxy,deltaproxy}.py) dropped theSaltEventreference immediately afterfire_event, so cleanup ran only when GC invokedSaltEvent.__del__and each finalization emitted the three-warning triad from the underlying transport chain.salt.utils.asynchronous.SyncWrapper.__del__,salt.transport.tcp.PublishServer.__del__, andsalt.transport.tcp._TCPPubServerPublisher.__del__also now fall back toclose()as a GC-time safety net -- mirroring the pattern onsalt.utils.event.SaltEvent.__del__-- while still emitting theResourceWarningso leaky callers can be surfaced for tracking pre-Potassium.salt.transport.tcp.TCPPuller.handle_streamalso no longer spins the tornado io_loop when aValueError('fd %s added twice')(fromIOLoop.add_handlervia tornado'sIOStream._add_io_state) orAssertionError('Already reading')(the modern tornado surface for the "prior read still outstanding" state, historically namedStreamAlreadyReadingError) is raised inside the reader loop under heavy master/minion connection churn. The historical broad-except swallowed the error and the outerwhile not stream.closed()loop immediately re-invokedstream.read_byteson the same broken fd, driving the tornado io_loop to 77-119% CPU and growing the log to hundreds of MB in seconds until the CI step timed out (deterministic repro on a 32-CPU Rocky 9 container running the 4-master cluster tests, probabilistic in CI).handle_streamnow narrow-catches those state errors, closes the stream, and breaks out of the reader loop so the accept handler is free to service the next connection._TCPPubServerPublisher.closealso best-effort callsstream.io_loop.remove_handler(fd)before closing the stream so a fd left partially registered by a failed connect() doesn't resurface as anotherfd added twicethe next time the fd is reused.PubServer._stream_readalso now releases its 1 MiBmsgpack.Unpackerand clearsclient._read_taskin atry/finallyso refcount collection reclaims the coroutine frame immediately on stream close, rather than surviving theclient -> _read_task -> coroutine frame -> clientreference cycle until the next cyclic-GC pass -- and_discard_on_close._cb()now pops the per-subscriberself._writers(asyncio.Queue, drain-Task)tuple and cancels the drain task, matching what_discard_slow_clientalready did on the timeout branch. Together these drop retained per-job Python memory from ~85 kB/job to ~1.8 kB/job under sustained per-subscriber churn (measured via tracemalloc on a live 200-job burst), addressing the underlying VSZ growth that had been triggering GHA-runner SIGKILLs onscenarios-grp1. #70175Fixed a file-descriptor leak in the salt-master's supervised subprocesses (
FileserverUpdate,Maintenance,EventReturnand every other process the ProcessManager restarts on its own cycle).ProcessManager.restart_processwas dropping the dead child'sProcessreference without callingProcess.close(), so the twomultiprocessing.popen_fork.Popenpipe fds (parent_r/parent_w) leaked on every restart. On a master with a stockfileserver_interval=3600, the parent leaked ~+2 fds per subprocess-restart cycle and those fds were inherited by every subsequent forked child, showing up as ~+4 FD/hr growth onsalt_master_process_fds{process="FileserverUpdate"}until the master hitmax_open_files. #70185Stopped embedding the master's own config (
__master_opts__) in the relenv minion config shipped tosalt-sshtargets. It was master-side-wrapper-only data that nothing on the remote target ever read, and since the underlying master opts object is mutated across nestedSingle/wrapper calls during a single state run, embedding it caused the (otherwise fixed-size) minion config to grow unbounded until it exceeded the kernel'sARG_MAX, failing withArgument list too long. #70186Extend the
whitelist_modulestwo-loader model to the state loader and the state engine itself so trusted salt-core-authored code (shipped Python state modules,compile_high_data'sconfig.optionreads, retrytest.sleep,event.fire_master,saltutil.refresh_modules) composes with non-whitelisted execution modules while every user-facing path -- wire dispatch, Jinja renderer context (bothsalt['cmd.run']andsalt.cmd.run),salt.states.module.run/.function,__slot__:salt:...references, and SLSunless/onlyif/check_cmdshell hooks -- stays whitelist-gated. Closes the attribute-style Jinja escape hatch that let{{ salt.cmd.run('...') }}bypass the filter that{{ salt['cmd.run']('...') }}already enforced. #70192Fix
salt.modules.aptpkg.add_repo_keyreturning False when the/etc/apt/keyrings/directory (Debian 11 / Ubuntu 22.04 convention) does not exist on the target: create the directory root-owned with mode 0755 on the operator's behalf, matching the apt-secure(8) guidance. Also fixtests.pytests.functional.modules.test_aptpkg:: test_add_del_repo_keymasking anyadd_repo_keyfailure withUnboundLocalErrorfrom afinally-block reference to atry-scoped variable. #70195Updated stale
vmware.comreferences left over from the VMware acquisition by Broadcom:Replaced dead/broken VMware documentation links (vSphere API reference pages, ESXCLI docs, the Tanzu/VMware Salt product page, the privacy policy link) with their current
broadcom.com/developer.broadcom.com/techdocs.broadcom.comequivalents.Removed a dead 2012 VMware blog link and a dead VMware Flings deep link, keeping the surrounding explanatory text.
Removed personal
@vmware.comaddresses from:codeauthor:docstring attributions, keeping the author names.Switched the packaging automation email used in changelog generation and most CI workflows to
saltproject.pdl@broadcom.comgoing forward (historical changelog/spec entries are left untouched as a record of what was true at the time). The release workflow, which GPG-signs commits/tags, keepssaltproject-packaging@vmware.comuntil it's confirmed the signing key has a UID for the new address, to avoid losing GitHub's commit verification.In
tools/changelog.py, also renamed the changelog author fromSalt Project PackagingtoSalt Project(there's no longer a separate packaging distro). #70202
Stopped proxy minions logging
Error during asyncio shutdown: The future belongs to a different loop than the one specified as the loop argumenton Python 3.14.asyncio.gathertakes noloopargument any more and resolves the loop from the calling context, butSyncWrapper.close()runs outside the loop it is tearing down, so gathering that loop's pending tasks was rejected and they were never drained. #70226Extend the
whitelist_modulestwo-loader model to Salt-internal subsystems -- beacons, engines, mine, schedule, and the sys.doc error-path lookups on caller / minion / metaproxy -- so shipped beacons (salt.beacons.status,.sh,.load), engines (salt.engines.slack,.webhook,.sqs), and scheduler bookkeeping (timezone.get_offset,config.merge, internal__mine_interval/__master_alive_*jobs) compose with their helper execution modules regardless ofwhitelist_modules. User-configured scheduled jobs, beacon overrides, and wire dispatch stay on the outer whitelist-filtered loader. Also mirror thepillar_refreshrebind into the inner loader'spack["__pillar__"]soconfig.mergedispatched through the inner loader sees the freshly compiled pillar and pillar-injected beacons actually activate on refresh. #70250Add a 5-second timeout to the
lspcishell-out in the GPU grain collector so a hunglspci(e.g. in a container without a live PCI bus) can no longer leak orphan child processes on every grains refresh. On timeout the grain returns empty (matching thelspci-not-found path) and logs a warning. #70251Relenv 0.22.26
Update expat to 2.8.4 #70254
Silence spurious
unclosed publisher client/unclosed publish server/unclosed publish subscriber/unclosed tcp pullerResourceWarnings emitted by transport publisher and publish-server objects when a forked child inherits the parent's live socket via copy-on-write. The parent process still owns the underlying file descriptors, so children now short-circuit__del__whenos.getpid()does not match the creator PID recorded in__init__-- the shared FDs are left untouched (closing them would break the parent's transport) and no leak warning is emitted for objects the child does not own. #70259Fixed
salt-keylogging[WARNING ] unclosed WheelClient ...on every invocation, which corruptedsalt-key --out jsonoutput for downstream consumers. TheKeyCLI(and theWheelClientit eagerly creates in__init__) is now destroyed deterministically via a context manager insalt/cli/key.py:SaltKey.run()instead of relying on__del__'s GC-time safety net. This is thesalt-keysibling of #70174 / #70177. #70260Patch tornado for GHSA-8423-8fgw-73vq #70269
Bumped relenv to 0.22.27, which brings openssl to 3.5.9 (fixing CVE-2026-84782 plus 9 lower-severity CVEs), expat to 2.8.5 (fixing CVE-2026-93990 UTF-16 surrogate-pair smuggling), xz to 5.8.4 (fixing GHSA-5qpq-xqfv-j9pg), and libtirpc to 1.3.8. #70335
Fix DEB and RPM package signing during staging and release builds. Commit
350ae7032cdrenamed theSIGNING_GPG_KEY/SIGNING_PASSPHRASEsecret references inbuild-packages.ymltoNIGHTLY_SIGNING_GPG_KEY/NIGHTLY_SIGNING_PASSPHRASEon the assumption thatbuild-packages.ymlis only reachable fromnightly.yml. It is also reachable fromstaging.ymlandrelease.yml, where those nightly-suffixed secrets are not defined -- signing failed withgpg: no valid OpenPGP data foundat package build time. Pick the secret based oninputs.environmentso nightly runs still use the nightly keys and everything else (staging, release, ci) uses the production keys. #70339Fixed pillar output masking (
salt.utils.secret.serial) only redacting string values — truthyint/float/booland non-emptybytespillar values were returned unmasked throughpillar.getand related functions even with masking enabled. Masking of these types is now consistent with how they were already redacted inrepr/stroutput. #98852
Added#
Expanded the NetworkManager keyfile provider (
nm_ip) so it covers more of thenetwork.managedschema and reaches closer parity withrh_ip:mtuis now emitted for bond, bridge and vlan interfaces (via a separate[ethernet]/ 802-3-ethernet section on the connection), not just ethernet. Previously it was silently dropped on those types.hwaddrnow pins a connection to a NIC's permanent MAC ([ethernet] mac-address, or[bridge] mac-addressfor bridges), honouring theauto/nonesentinels.macaddrsets the in-use MAC ([ethernet] cloned-mac-address) and is mutually exclusive withhwaddr.The
autoneg,speedandduplexethtool link parameters now map to[ethernet] auto-negotiate/speed/duplexinstead of being rejected; offload/channel/advertise ethtool knobs (which have no keyfile equivalent) are still refused.Bond options are now passed through to
[bond]from the full kernel bonding set (ad_select,fail_over_mac,primary_reselect,arp_validate,all_slaves_active,min_links, ...) rather than a fixed ten-key list.dns_searchis now written under[ipv6]as well as[ipv4], so search domains are no longer lost on IPv6-only hosts.vlan
reorder_hdr/gvrp/loose_bindingare folded into the[vlan] flagsbitmask, andwolmaps to[ethernet] wake-on-lan.
The keyfile is now created with 0600 permissions before any content is written, and the NetworkManager provider-selection check is shared with
rh_ipvia a singlesalt.utils.network.nm_managedhelper. #5479Added possibility for the minion to reconnect to the master on it's IP address change with using ZeroMQ #66760
Added Fedora 43 to test CI, and dropped Fedora 40, in accordance with Fedora OS support policy. #67182
Added os_family mappings for additional Linux distributions. #68715
Added an optional
returner.pgjsonb.connect_timeoutconfiguration option (in seconds) for the pgjsonb returner. When set, the value is forwarded topsycopg2.connect(connect_timeout=...)so a stalled PostgreSQL connect attempt cannot block the master event loop. The option has no default and the existing connect behaviour is preserved for deployments that do not set it. #69050virtualenv.createand thevirtualenv.managedstate can now build an environment with a specific interpreter's standard libraryvenvmodule:venv_bin: venvhonours thepythonargument (running<python> -m venvinstead of always using the interpreter running the minion), and a python interpreter may be passed directly asvenv_bin. Thepromptargument is now passed through on the venv path as well, instead of being rejected. This makes it possible to manage e.g. python3.11 environments on EL8, where the distro virtualenv is 15.1.0 bound to python 3.6. #69679Added
winrepo_installer_cache_expireminion config option to automatically remove cached winrepo installer/uninstaller files older than a configurable age each timepkg.refresh_dbruns, preventing the minion cache from growing unbounded. Disabled by default. #69817Added opt-in
minion_memory_headroomandminion_memory_maxminion config options with cgroup v1 / v2 detection so the queue-admission memory check can be tuned on large hosts and cgroup-limited minions. Defaults preserve the existing 95%-of-system-RAM behavior. #69884Support a per-host
relenv: Trueentry in the salt-ssh roster so that individual targets can use the relenv (Salt+Python bundled) deployment without forcing every host reached by a wildcard match to download the onedir tarball. Equivalent to the--relenvCLI flag but scoped to a single roster entry. #69885Add
master_async_mworkeropt-in flag (defaultFalseon 3008.x) that dispatchesAESFuncs/ClearFuncs/AuthFuncshandlers asynchronously, offloads blocking work to a thread executor, and gives each MWorker its own IPC socket for fair PoolRouter dispatch. With the flag off (the LTS default) MWorker handlers and IPC routing are byte-for-byte identical to Argon v3008.2 and earlier. #69986Optimized
EventPublisherfan-out:TCPPullernow forwards the raw wire bytes toPubServervia a newraw_payloadkeyword, letting the fan-out skip a redundantmsgpack.dumpsper event.MasterPubServerChannel.publish_payloaduses a bytes-level tag peek (load.partition(TAGEND)) and only callssalt.payload.loadson the event body when the tag matches one of thecluster/runner/*special-cases. On non-cluster masters (the >99 % case), the fullSaltEvent.unpackon the fan-out hot path is now skipped entirely, eliminating the transient dict/list tree that dominatedEventPublisherallocation churn under highstate-return bursts. Measured: -28 % peak Python allocation under sustained stress, +55-111 % return throughput ceiling. #70052Added a required
branchinput to3006.x'snightly-stress-test.ymlworkflow, along withenable_metricsandworker_threadsinputs that let a run toggle OpenTelemetry metrics and override the salt-master worker pool size before the stress test starts. Lets this workflow be dispatched against any branch, not just3006.x. #70099Added debug-level logging to the
rootsfileserver backend and the generic fileserver dispatcher. The dispatcher now logs which backend is attempted for eachfind_file()call, androots.find_file()now logs whether it located or failed to locate the requested path. This mirrors thesseapi(SaltStack Enterprise) backend's existing tracing, making the fullfileserver_backendfallthrough sequence visible in the master log regardless of which backend ultimately serves a request. #70106Add
master_mworker_max_inflightoption to bound the number of concurrent request handlers per MWorker process whenmaster_async_mworkeris enabled. Default0preserves the existing unlimited behavior; a positive value caps each MWorker with its ownasyncio.BoundedSemaphoreso the effective total across the worker pool ismaster_mworker_max_inflight * worker_threads. Requests block on the semaphore rather than erroring, so backpressure propagates naturally through the TCP task queue / ZMQ HWM. #70129Added five minion config options to control
salt-pip's environment:saltpip_use_pythonpathlets a site opt back into the pre-#70151 behavior of inheritingPYTHONPATHfrom the calling process (defaultFalse, i.e. isolated).saltpip_no_deps,saltpip_no_index, andsaltpip_disable_pip_version_check(all defaultFalse, matching current behavior) let an operator forcesalt-pipto never resolve dependencies, never query a package index, and never check for a newer pip release, so it can be locked down to never reach out to PyPI.saltpip_allow_find_links(defaultTrue) additionally lets an operator strip any inheritedPIP_FIND_LINKS, independent ofsaltpip_no_index, since pip treats--find-linksas independent of the index by design. #70151Add
whitelist_state_modulesminion option to restrict which state modules can be loaded, complementingwhitelist_modules. #70193Add
SALT_ONEDIR_HARDEN=1opt-in on 3006.x that relocates each salt daemon's writable state under per-daemon/var/lib/salt/<daemon>/directories so the/opt/saltstack/saltonedir tree staysroot:root 0755. The default on 3006.x is unset (legacychown -R salt /opt/saltstack/saltbehavior preserved); the default flips to hardened on 3009.0.salt-pipand_salt_onedir_extras.pyhonorSALT_EXTRAS_DIRat runtime so the relocated extras tree stays importable by the daemon. #70208Added the
pillar_mask_outputmaster/minion config option. When set toFalse, changespillar.items's default (when the caller doesn't passunmask) to return unmasked pillar values, for sites relying on the pre-maskingpillar.itemsbehavior. Defaults toTrue(masked, matching existing behavior) and does not affectpillar.get/item/raw/ext,no_logstate output, or general CLI output, which keep redacting by default regardless of this setting. #98852
Security#
Bumped GitPython in six
requirements/static/ci/py3.*/lint.lockfiles from vulnerable==3.1.50to==3.1.60(matching the rest of the lock chain) and aligned the CI-static lower bound inrequirements/static/ci/{common,darwin}.txtfrom>=3.1.50to>=3.1.59. Fixes CVE-2026-78676 (GHSA-284h-m62q-gf8w) — GitPython re-serialization corrupts a dormant multi-line quoted config value into an executable directive (e.g.core.hooksPath), enabling RCE via crafted config files. Lint environments installed from these six lock files were within the vulnerable range; the runtime lock files ({cloud,darwin,docs,freebsd,linux,windows}.lock) and base pin were already at>=3.1.60and unaffected. #70265