(release-3006.26)=
Removed the unmaintained linode-python package dependency to stop SyntaxWarnings during install for retired Linode API v3. #68992
Changed salt.returners.redis_return to enumerate the Redis keyspace
with SCAN instead of the blocking KEYS pattern command in both
get_jids and clean_old_jobs. KEYS walks the entire keyspace
synchronously and stalls the Redis server for the duration; on a
master with hundreds of thousands of jobs this can block all clients
of that Redis instance for seconds. SCAN is incremental and
non-blocking. Order of returned keys is no longer guaranteed (the
returner does not rely on order); operators with custom scripts that
read ret:* or load:* directly may see them in a different order. #69037
Fixed multi-line scalar variables loaded via import_yaml (or load_yaml) being rendered as literal \n instead of actual newlines when the loaded data is interpolated into a YAML state file (e.g. - context: {{ data }}). PrintableDict.__str__/__repr__ now emit string values containing newlines as YAML-safe double-quoted scalars rather than Python repr() so they round-trip correctly through the subsequent YAML render pass. #30690
Handle requisites correctly for empty SLS files #30971
Fixed win_pkg functions ignoring the saltenv setting in minion configuration. All public functions (refresh_db, genrepo, install, remove, list_pkgs, latest_version, upgrade_available, list_upgrades, list_available, version, get_repo_data, get_package_info) now fall back to __opts__["saltenv"] when saltenv is not passed explicitly, instead of always defaulting to base. #38551
dpkg_lowpkg no longer reads /var/lib/dpkg/available or /var/lib/dpkg/info/<package>.list directly. It now uses dpkg-query exclusively, addressing the lintian uses-dpkg-database-directly warning reported in #52605. lowpkg.info derives the package install time from dpkg's ${db-fsys:Last-Modified} field instead of the .list file mtime. #52605
Added encoding parameter to file.replace execution module and state to support UTF-16, UTF-32, and other multi-byte encoded files that would otherwise be incorrectly treated as binary. #52793
Fixed postgres._find_pg_binary ignoring postgres.bins_dir when a psql binary is also present on the system PATH, ensuring the configured bins_dir is always preferred over the system PATH. #53190
Percent-encode the user and password when adding HTTP basic auth to a URL so reserved characters no longer corrupt the result #55561
Fixed a SaltCacheError ("maximum recursion depth exceeded") raised by the
etcd data cache when listing an empty folder, which etcd reports as a child of
itself. The directory walk now stops at the self-referential entry instead of
recursing indefinitely. #57377
Fixed timezone.system state always returning result=False with "Failed to set UTC to True" on Windows. The hardware clock on Windows is always localtime and cannot be changed, so the UTC/hwclock block is now skipped entirely on Windows. #57754
Fixed archive.tar placing the -C <dest> option after the source/member operands, where tar ignores it. The directory-change option is now emitted before the operands so it takes effect in both create and extract modes. #57847
Fixed OSError: The operation completed successfully raised by CreateProcessWithTokenW on Windows when the underlying advapi32 call fails. The error code is now read from ctypes.get_last_error() (the ctypes-saved slot) instead of win32api.GetLastError() (the live Windows slot, which may be reset to 0 before it is read). #57848
Improved documentation for the runas and password parameters in cmd.run, cmd.script, and all salt.modules.cmdmod execution functions on Windows. The docs now accurately describe when a password is required: only when the salt-minion is not running as SYSTEM or as an elevated Administrator. Removed the inaccurate claim that the target user account must be in the Administrators group. Also changed cmd.script to log a warning instead of hard-failing when runas is used without a password on Windows, since a password is not always required. #57951
Fixed pkg.group_installed/pkg.group_info failing to expand a dnf environment group whose member groups have multi-word names (e.g. Group '@Common NetworkManager submodules' not found when installing Workstation on RHEL/AlmaLinux 8, 9 and 10). The member group is now resolved by its bare name when the @-prefixed lookup fails. This affects dnf4 only; dnf5 group handling is unchanged. #60276
Fix tls.create_csr log message path to use os.path.join instead of f-string interpolation so paths render correctly when csr_path has a trailing slash. #60877
Fixed the LDAP eauth group-membership lookup re-binding the user on every job
payload when auth.ldap.freeipa is enabled. The user is now only re-bound on
the first payload of a job, matching the standard LDAP code path, so single-use
2FA credentials (such as a FreeIPA OTP) are no longer consumed more than once. #61974
Fixed SSL: DECRYPTION_FAILED_OR_BAD_RECORD_MAC errors in the VMware cloud driver by reconnecting when a cached vCenter service instance is found to be stale or corrupted (for example when inherited across a fork by salt-cloud's parallel provider queries). #61983
Fix metadata grain so EC2 user-data is returned verbatim instead of being mangled by the = line-splitter, which previously corrupted any user-data payload containing = (e.g. cloud-init #cloud-config blocks). #62061
Fixed LGPO get_policy_info incorrectly returning a "multiple policies" error when duplicate ADMX policy definitions (e.g. TerminalServer.admx and TerminalServer-Server.admx) resolve to the same full path. #62732
Re-enable test_interrupt_on_long_running_job by removing the initial-onedir-rollout skip marker. #63627
Fix missing dns_plugin_propagate_seconds arg in acme state/module so DNS propagation timeout is actually forwarded to certbot. #63700
Improve PAM eauth diagnostics when salt-master runs as a non-root user. Previously, salt-master/salt-api running as the salt user (the 3006.x packaging default) silently failed every PAM authentication with only Pam auth failed for <user>: in the log; the cause is that the helper subprocess inherits the master's uid and PAM's unix_chkpwd refuses to validate other users without /etc/shadow access. The master now emits a one-shot CRITICAL log entry that names the cause and the two standard remediations (run as root, or add the master user to the shadow group on Debian-derived distributions), and the module documentation describes the constraint. #64275
Fixed incorrect minion presence events being sent out on hourly Maintenance process restarts #64505
Catch StrictUndefined in salt jinja custom filters. #64915
Stopped logging the misleading "An extra return was detected from minion ... this could be a replay attack" ERROR for benign duplicate returns (also fixes #65516). The local_cache returner now compares a duplicate return to the cached one and logs at DEBUG when the payloads match (the common retry-after-timeout or syndic re-forward case) and at WARNING -- without the "replay attack" wording -- when the payloads differ. #65301
Fixed non-root salt CLI access when publisher_acl or external_auth is configured. Since 3006.3 the master defaults to running as the salt user, which left sock_dir and cachedir mode 0o750 and blocked authorised non-root users from traversing into them to reach master_event_pub.ipc / publish_pull.ipc and their per-user .<user>_key. The master now adds the world-execute bit to those two directories when ACLs are configured, without exposing directory listings. #65317
Fixed salt.ext.tornado.netutil import on Python 3.12+ where ssl.match_hostname was removed and the unmaintained backports.ssl_match_hostname package is unavailable, which previously broke any Salt master-initiated job (e.g. test.ping, state.apply) on Fedora 39+/Ubuntu 24.04 masters. #65360
See #65301 -- the same fix to salt/returners/local_cache.py quiets the spurious "extra return ... replay attack" ERROR that appeared in multimaster and master-of-masters/syndic setups when the same return arrived more than once. #65516
Fix deadlock in parallel cmd.script states when the script is served by the master.
Same fork-inherited ZeroMQ socket race as the file.managed fix: a
cmd.script state with parallel: True downloads the script via
cp.cache_file in a forked child that inherited the parent's ZeroMQ
REQ socket, deadlocking the asyncio loop at ~100% CPU. Resolved by the
same os.register_at_fork handlers that drop inherited channel/socket
references in forked children. #65709
Fixed pip.uninstall rejecting the extra_args keyword argument, matching the behavior of pip.install. #65870
Fixed salt-ssh failing to fetch gitfs_remotes. salt.config.master_config
sets __fs_update = True to suppress fileserver refreshes done by FSChan
(the master daemon's maintenance thread handles them). salt-ssh inherits the
master config but has no maintenance thread, so its FSClient never refreshed
the fileserver backends and wrappers such as cp.list_states saw no gitfs
content until the user ran salt-run fileserver.update or manually
git fetched the cached repos. salt.client.ssh.SSH.__init__ now removes
the suppression flag before instantiating FSClient so gitfs is refreshed
once at startup. #66148
Fixed salt/version.py reporting the wrong major version on the 3006.x branch when built from a checkout that has no salt/_version.txt and no usable .git directory. SaltVersionsInfo.current_release() now returns the branch's own codename (Sulfur) instead of the next un-released codename in the table, so source builds and other tooling no longer leak 3007.0 into the reported version. #67061
Fixed saltutil.runner and saltutil.wheel running master-side functions
as the minion's user (typically root) instead of the master's configured
user (the packaged default since 3006 is salt). Running as the wrong user
left root-owned files in, and tripped git's safe.directory check on, the
salt-owned master cache -- breaking, for example, git_pillar.update invoked
via saltutil.runner. These functions now drop to the master's configured
user before executing when invoked from a more-privileged process. #67716
Fixed LocalClient.cmd_subset raising TypeError: argument of type 'bool' is not iterable when one or more targeted minions failed to respond to the sys.list_functions probe. Failed minions are now skipped during subset selection. #68103
Fixed slack_bolt engine crashing with UnboundLocalError when a Slack workflow or other bot posts a message to a monitored channel. Bot messages (subtype: bot_message) carry bot_id and username instead of a user field, and these are now used as fallbacks so the engine continues running. #68105
Fixed user.present to not fail with result: False in test mode when a referenced group does not yet exist; the state now reports the pending changes so users can preview states that depend on groups created by a group.present requisite in the same run. #68110
Fixed salt-minion and salt-proxy leaving a privileged (root) keepalive supervisor process at the head of an otherwise unprivileged minion process tree when user is set to a non-root account. The supervisor now drops privileges to the configured user once the keepalive child has been spawned. #68115
Fixed ValueError: Formatting field not found in record: 'colorlevel' errors when log_fmt_console uses custom color attributes such as %(colorlevel)s or %(colormsg)s. SaltLogRecord now always provides the color* attributes (uncolored by default) so that log records buffered by the temporary deferred stream handler can be formatted by a colorized console formatter once it is installed. #68129
Fixed salt-call silently ignoring --file-root, --pillar-root, and --states-dir when --local was not passed. These overrides only affect the local minion config and are clobbered by the master's values via the remote file client, so salt-call now emits a warning explaining that --local is required for the override to take effect. #68137
Fixed event signature verification failing under minion_sign_messages. The minion was signing the return load before salt.channel.client.AsyncReqChannel._package_load attached transport metadata (nonce, ts, tok, id), so the bytes the master re-serialized to verify did not match what was signed and every signed return was dropped. Signing is now performed inside _package_load after the metadata is attached, against the same bytes the master verifies. #68181
Fixed pkgrepo.managed honouring clean_file: True when the desired
repo line is already present in the managed file alongside unrelated stale
lines. Previously the state returned "already configured" and silently
skipped both the file truncation and the re-write, leaving the stale
entries (for example an obsolete bullseye-backports line in a file
managed for bookworm-backports) in place. The clean + reconfigure
path now runs whenever the managed file contains any non-comment,
non-blank content other than the desired repo line; when the file already
contains only the desired line the state remains idempotent. #68208
Fixed pkg.group_installed reporting failure on RPM-based systems when a package group's default or optional members are not available in any enabled repository. The state now only considers mandatory group members and explicitly requested include packages when checking for install failures, matching the behavior of yum/dnf group install (which reports "No match for group package" but still exits 0). #68210
Pass --disable-pip-version-check when pip.list, pip.freeze, pip.list_upgrades, pip.upgrade, and pip.list_all_versions invoke pip, so these calls no longer hang for ~20s per invocation on airgapped minions while pip tries to reach PyPI for its self-version check. #68214
Fixed archive.extracted failing to enforce user/group ownership on archives whose tar/zip members include no explicit directory entries (e.g. Oracle's GraalVM JDK tarballs). archive.list now derives the top-level directory from the common prefix of file and link members in addition to dir members, so ownership is applied to the extracted top-level directory in all cases. #68227
Fixed deltaproxy sub-proxies returning identical grain data for every controlled minion. subproxy_post_master_init now re-packs each sub-proxy's freshly loaded per-minion grains into its execution-module, returner, executor and proxy LazyLoaders so __grains__ inside loaded modules reflects that sub-proxy's device instead of the placeholder values captured during the first-pass grains load through the control proxy. #68248
Fixed the salt-minion (and salt-api, salt-cloud, salt-master, salt-syndic) Debian postinst scripts hanging or erroring with "Bad file descriptor" when run from a non-interactive Debian preseed late_command chroot, by tearing down the debconf protocol with db_stop and explicitly closing file descriptor 3 before the auto-generated #DEBHELPER# section runs. #68269
Fixed file.managed failing with WinError 123 on Windows when caching a remote URL whose path embeds another URL (e.g. an archive.org snapshot of an https://... resource). The URL-path portion of the extrn_files cache path is now sanitised the same way the network location already is. #68273
Fixed logrotate.set dropping the second endscript (and turning
embedded shell commands into bogus setting keys) when a stanza contained
multiple script blocks such as both prerotate and postrotate. Script
directives are now parsed as opaque multi-line bodies and round-trip with
their own endscript terminator each. #68293
Fixed the salt.state orchestrate state silently reporting only Run failed on minions: <minion> when a targeted minion returned False, no return at all, or a list of error strings. The orchestrate comment now includes the per-minion failure detail (the minion's actual return value or "did not return a state result") so operators can diagnose salt-run state.orchestrate failures without re-running with extra logging. #68326
Fixed worker process crash when salt is used outside CLI tools. #68332
Fixed clean_old_jobs in the default local job cache returner to use the jid file's modification time (st_mtime) instead of the inode change time (st_ctime). A package upgrade's chown -R /var/cache/salt/master resets st_ctime on every existing jid file, which previously made the maintenance process treat every pre-upgrade job as freshly created and prevented cleanup until keep_jobs_seconds had elapsed. On busy masters this exhausted the partition's inodes within a day. #68351
Fixed the proxmox salt-cloud driver raising Could not determine an IP address to use before the VM was created and started. The IP address is now determined after the VM is running, and the running VM's address reported by Proxmox is used as a fallback when neither a static ip_address nor agent_get_ip is configured. #68353
Changed KillMode in the shipped salt-minion.service systemd unit from process to mixed so that systemctl stop / systemctl restart salt-minion no longer leaves orphaned Minion._thread_return worker processes outside the cgroup. SIGTERM is still sent only to the main PID (so the job return scheduled by service.restart salt-minion from #68183 has time to finish), but any remaining children are reaped with SIGKILL after the main process exits or TimeoutStopSec elapses. #68406
Fixed task.edit_task on Windows rejecting restart_count=999 even though the documented and error-message-stated maximum is 999. The validation now accepts the full 1..999 range. #68419
Fixed win_task.add_trigger so that repeat_duration="Indefinitely" actually produces an indefinite repetition pattern. Previously the empty string from the internal duration lookup was assigned to Repetition.Duration, which the Windows Task Scheduler treats as "0 seconds" and silently disables repetition. The Duration property is now left at its default for the "Indefinitely" case, which is the documented way to repeat forever. #68420
Fixed user.setpassword on Windows reporting success (retcode: 0) when the target user does not exist. The execution module now returns False and logs an error in that case, so callers and the user.present state correctly detect the failure instead of swallowing the Win32 "user name could not be found" message as a successful return. #68428
Fixed user.present on Windows so it actually updates a user's password
when the existing password differs from the one specified in the state.
Previously the state reported "User is already present and up to date" and
left the password unchanged. #68429
Stop salt-ssh state runs from clobbering the master-side fileclient cachedir with the on-target thin_dir cachedir. The state fileserver cache for salt-ssh state runs is now written under the configured master cachedir (e.g. /var/cache/salt/master/) instead of under the minion's thin_dir path on the master filesystem. #68458
Fixed pkg.add_repo_key and pkgrepo.managed so APT keyring files that target an .asc destination keep their ASCII armor instead of being dearmored, matching the apt-secure(8) convention and allowing armored keyfiles that bundle multiple keys to be installed even when the gpg binary is not available on the minion. #68464
Fixed jobs.list_jobs search_metadata so it matches jobs whose metadata
was passed as a CLI keyword argument (e.g. state.apply metadata={...})
and is therefore carried inside the job's Arguments rather than at the
top of the job payload. #68481
Fixed lgpo.set state reporting "Failed to set the following policies" on subsequent runs of policies with sub-elements (e.g. Storage Sense thresholds). The state compared a user-supplied dict keyed by element id with a current dict keyed by the ADML display name; both forms now normalize to the canonical element id before comparison so the state is idempotent. #68489
Fixed minion rejecting the master with "Invalid master key" after restart when the cached minion_master.pub differs from the master's payload pub_key only in trailing whitespace. AsyncAuth.verify_master now normalizes both sides through clean_key before comparing and caches the normalized form on first contact. #68493
Fixed TypeError: 'NoneType' object is not iterable raised from AsyncReqMessageClient._send_recv when a per-message timeout completes the future before the send/receive coroutine catches a transient transport exception, which aborted the minion's connect loop and prevented it from connecting to the master. #68506
Fixed docker_network.present recreating networks on every run against Docker 29+. Docker 29 added an empty IPRange field to every IPAM Config entry; docker.compare_networks now drops empty/None placeholder values before comparing pools, and the state's default-pool short-circuit treats the empty field as absent. #68518
Fixed pkg.installed verification on x86_64 hosts that mix x86_64 and x86_64_v2 packages (e.g. AlmaLinux 10.1). salt.utils.pkg.rpm.resolve_name and salt.modules.yumpkg.normalize_name now treat x86_64_v2 as compatible with x86_64 instead of appending the arch suffix, so installed packages match the names Salt records. #68540
Fixed mysql_grants.present reporting "Failed to execute" when granting ALL PRIVILEGES on *.* against MySQL 8.4+, where the server's privilege set drifted from Salt's hard-coded list (SET_USER_ID removed, many dynamic privileges added). grant_exists now derives the expected privilege set from the connected server's SHOW PRIVILEGES output instead of a static list. #68567
Fixed cp.get_template raising AttributeError: 'NoneType' object has no attribute 'get' when the Jinja template uses {% from '...' import ... with context %}. The cp module's loader-backed __opts__ is now unwrapped to a plain dict before the SaltCacheLoader instantiates the file client and channel that fetch the imported template. #68572
Fixed ImportError: cannot import name 'wait' from partially initialized module 'multiprocessing.connection' raised during salt-master/minion shutdown when a reentrant SIGTERM hit ProcessManager.kill_children() mid Process.join(0). salt.utils.process now eagerly imports multiprocessing.connection so the module is fully initialised before any signal handler can trigger its lazy import. #68573
Fixed cmd.script on Windows raising Invalid user: <runas> when runas is a domain account (DOMAIN\user, user@DOMAIN, or a SID). The pre-execution user.info check is backed by NetUserGetInfo which only resolves local-machine accounts and returns empty for many valid domain users; the missing lookup is now logged as a warning and execution continues so the underlying win_runas machinery can authenticate the account. #68578
Fixed pkg.install on Windows silently downgrading the salt-minion when a numeric version= argument was passed (e.g. version=3007.10 was YAML-parsed to the float 3007.1 and then matched the wrong winrepo entry). When the numeric version uniquely matches a string-keyed winrepo entry it is now resolved to that entry; when it is ambiguous (e.g. both 3007.1 and 3007.10 are in the winrepo) the install is refused with a clear error pointing the user at the quoted-version syntax. #68620
Fixed the loader masking failure reasons when multiple modules declare the same __virtualname__ and each __virtual__() returns False, so users now see every reason (e.g. both x509 v1's "Superseded, using x509_v2" and x509_v2's "Could not load cryptography") instead of only the first one recorded. #68625
Fix NetapiClient.runner raising TypeError when timeout arrives as a string from the salt-api HTTP form. #68653
Fixed master_job_cache: redis_return raising KeyError: 'redis_return.prep_jid' by registering the redis returner under both redis and redis_return virtual names, matching the documented --return redis_return usage and the module's file name. #68663
Fixed ini.options_present with strict: True to remove sections that are present in the ini file but absent from the supplied sections mapping. #68673
Handle SaltDeserializationError in grains cache loading so a corrupted cache file no longer propagates as CRITICAL during minion startup. #68678
Fixed network.interfaces on Windows systems falling back to WMI (i.e. .NET older than 4.7.2): the default gateway is now reported under gateway instead of being mistakenly emitted as broadcast. #68692
Fixed file.managed (and other template-rendering callers) silently overwriting user-supplied slspath, sls_path, slsdotpath and slscolonpath values in defaults/context with values regenerated from the caller's sls key. #68754
Fixed env_order not being honored when merging pillar data across environments. Pillar.render_pillar now iterates matched environments in the configured env_order so that, with top_file_merging_strategy: merge_all, the last environment in env_order wins on conflicting pillar keys instead of the result depending on dict insertion order. #68785
Improved the "Malformed topfile" error from HighState.verify_tops to name the saltenv and the matcher whose state declarations were not formed as a list, so users can locate the offending entry in their top.sls. #68792
Removed orphaned GnuPG dotlock files (.#lk<addr>.<host>.<pid>) from gpg_keydir before each decrypt in the gpg renderer so they no longer accumulate when a gpg subprocess is killed mid-operation. #68869
Fix pkg.installed idempotency on FreeBSD when with_origin=True causes
pkg.list_pkgs to return per-package dicts instead of version lists; extract
the version list before version-string comparison so a second state run no
longer falsely reports packages as changed. #68886
Fix gen_signature() signing raw pub key content instead of clean_key'd content, causing master_use_pubkey_signature verification to always fail. #68930
Fixed spurious FileLockError: lock_fn ... exists and is not a file raised by salt.utils.files.wait_lock and salt.utils.files.await_lock (and therefore by state.apply queue locking) when another process removed the lock file between the two separate os.path.exists / os.path.isfile stats. The pre-check now uses a single os.stat call so a transient regular-file lock no longer trips the "not a file" branch. #68931
Fixed pkg.installed(update_holds=True) for APT multiarch packages by preserving arch-qualified package names through install target parsing and verification. #68932
Fix deadlock in parallel file.managed states when source is served by the master.
Forked parallel-state children previously inherited the parent's ZeroMQ
REQ socket and asyncio loop from salt.fileclient.RemoteClient,
salt.crypt.AsyncAuth/SAuth, and salt.utils.event.SaltEvent. Multiple
sibling children racing those handles deadlocked the asyncio loop with
~98% CPU and never completed. Salt now registers os.register_at_fork
handlers on those classes that drop inherited channel/socket references
in any forked child; the next use rebuilds them fresh. #68940
Fixed grain and pillar targeting matching minions whose data cache entry was missing. CkMinions._check_cache_minions now excludes accepted minions that have no cached grains/pillar data from greedy target results, instead of silently including them as candidates. #68976
Avoid AttributeError on a closed IPCClient when the connect coroutine resolves after close(). #68993
Fixed salt.utils.network.sanitize_host stripping colons from IPv6 addresses, which broke network.ping and any other caller that passed an IPv6 host. #68995
Added support for MAINTAIN (m) privilege introduced in PostgreSQL 17 to salt.modules.postgres and salt.states.postgres_privileges #69003
Fixed redis.get_master_ip silently dropping the password argument. The
function was forwarding its arguments positionally to _connect, but
_connect's third positional slot is db, not password, so the
caller's password landed in the database-index argument and the actual
password fell through to config.option("redis.password"). Arguments
are now passed by keyword. #69029
Fixed salt.modules.redismod._connect rejecting valid db=0. The helper
used a truthy check (if not db) to decide whether to fall back to
config.option("redis.db"), but not 0 is True, so an explicitly
supplied db=0 was silently replaced by the configured value. The check
is now if db is None, matching the pattern already used by the sibling
_sconnect helper in the same module. Other arguments keep their
truthy-check semantics on purpose. #69030
Fixed two distinct bugs in the salt.engines.redis_sentinel engine that
together prevented it from being usable. start() no longer raises
AttributeError: 'dict_values' object has no attribute 'pop' on Python 3
(the dict.values() result is now wrapped in list(...)). Listener and
start() now accept an optional password argument and forward it to
the redis client, allowing the engine to authenticate against a Sentinel
that requires AUTH; the default of None keeps existing configurations
working unchanged. #69031
Fixed salt.returners.redis_return silently ignoring the documented
redis.password configuration option. The returner now reads
redis.password from config (in both regular and proxy modes) and
forwards it to both the single-server redis.StrictRedis and the
StrictRedisCluster constructors. Operators with auth-protected Redis
no longer lose every job return to a hidden NOAUTH Authentication required failure; deployments without a password are unaffected. #69032
Fixed three closely-related bugs in salt.cache.redis_cache that
together broke hierarchical-bank semantics:
_build_bank_hier now registers each child bank name in both the
parent's $BANK_ set (consumed by flush() tree traversal) and the
parent's $BANKEYS_ set (consumed by list_()); _get_banks_to_remove
now decodes the bytes returned by smembers and skips the "."
placeholder, so recursive flush() of a parent bank actually descends
into sub-banks instead of corrupting the path; and flush(bank) of a
sub-bank now removes the flushed bank's own reference from its
parent's index sets so list_(parent) no longer reports it as
present. Together these fixes restore cache.list("minions"),
salt-run manage.present and salt-run manage.up for masters
configured with cache: redis. #69033
Fixed salt.tokens.rediscluster being unable to retrieve any eauth
token. The cluster client was created with decode_responses=True,
which caused redis_client.get() to return str and broke
salt.payload.loads (msgpack rejects str); it also caused
redis_client.keys() to return str and broke
[k.decode("utf8") for k in ...] (str has no .decode). Both
errors were swallowed by broad except Exception handlers, so eauth
appeared to silently reject every token. decode_responses=True is
removed; values now round-trip as bytes through msgpack as the rest
of the module already expected. #69035
Fixed salt.returners.redis_return leaking <minion>:<fun> last-jid
pointer keys indefinitely. The pointer was written with pipeline.set
and no ex= TTL, so any (minion, fun) pair that stopped running stuck
in Redis forever -- O(minions × distinct funcs) keys accumulating over
the lifetime of the master. The pointer now expires on the same TTL
as the rest of the returner data (keep_jobs_seconds). Operators with
external scripts reading these keys directly may observe them
expiring; the documentation never promised they would not. #69038
Fixed salt.returners.redis_return.get_fun always returning an
empty dict. The function read return data from a <minion>:<jid>
key that no other code in the module ever wrote -- a leftover from
an older storage schema. It now reads from the canonical
ret:<jid> hash via HGET ret:<jid> <minion>, matching the
storage layout that returner actually produces and the read
pattern that get_jid already uses. #69039
Fixed salt.returners.pgjsonb writing database errors to sys.stderr
instead of Salt's logger. Errors from _get_serv, _purge_jobs and
_archive_jobs are now reported via log.exception, so they reach
the configured log_file / syslog destination on a daemonized master,
including a full traceback. The unused import sys is also dropped. #69048
Fixed salt.returners.pgjsonb.returner letting any non-connection
psycopg2.DatabaseError propagate to the caller — including the
syndic-aggregate publish path in salt/master.py which had no outer
catch — so a single bad row could escape into a master subprocess.
event_return had no error handling at all and a database failure
during a flush propagated similarly. Both functions now catch
SaltMasterError and psycopg2.DatabaseError locally, log a
contextual message (jid/id for returns, batch size for events), and
drop the affected payload. While here, fix event_return passing
the events list as the positional ret argument to _get_serv,
which was a copy-paste leftover from returner(ret). #69058
Fixed salt-api's /events endpoint accepting eauth tokens via query
string (?token=... or ?salt_token=...). Tokens supplied that
way end up in HTTP access logs, the browser Referer header, log-
aggregation systems and shell history; the token retains validity for
token_expire (12h by default), so any party reading those logs can
replay the token. The endpoint now rejects query-string tokens with a
400 error pointing at the X-Auth-Token header (for non-browser
clients) or the session cookie established by /login (for browser
EventSource clients) as the supported channels. X-Auth-Token
header support is added; cookie-based auth continues to work
unchanged. #69071
LoadAuth.get_tok now distinguishes between corrupt token blobs (removed from the store) and transient backend errors such as Redis connection drops or NFS hangs (token kept, request treated as not-authenticated). Previously a single backend hiccup could log every authenticated user out by deleting valid tokens. #69073
cmd.run and friends no longer include the env and stdin arguments in the CommandExecutionError raised when the underlying subprocess fails to start (typically ENOENT / binary not found). Both fields routinely carry credentials passed in by the caller (env={"DB_PASSWORD": "..."}, password piped via stdin), and the error message ends up in master/minion logs and in event-bus return data visible to the API caller. #69075
Lowered the "Cache version mismatch clearing" log message in salt.utils.cache.verify_cache_version from WARNING to DEBUG; the cache is rebuilt as part of normal operation after upgrades or when an ephemeral cache directory has been removed, and does not warrant user attention. #69106
Relenv 0.22.14
Update sqlite to 3.53.2.0
Update openssl to 3.5.7 #69129
Surface the real cause of a proxymodule load failure in salt-proxy's abort message. The misleading "Proxymodule X is missing an init() or a shutdown() or both" wording is now only used when init/shutdown really are missing from a loaded module; if the module failed to load (for example because its __virtual__ returned False), the underlying reason is included in the error. #69139
Fixed pkg.hold and pkg.list_holds on dnf5 systems (e.g. Fedora 42+):
pkg.hold now calls dnf5 versionlock add <pkg> (the bare
versionlock <pkg> form was rejected by dnf5), and pkg.list_holds
reads /etc/dnf/versionlock.toml directly so pkg.installed with
hold: true is again idempotent. #69181
Fixed Salt-SSH syncing internal modules as extmods #69199
Fixed lgpo_reg.value_absent failing when the Registry.pol entry was already absent but the registry value still existed. lgpo_reg.delete_value was returning early before reaching the registry cleanup code, causing the state to see no changes and report failure. The registry value is now removed regardless of whether the pol entry was present. #69203
Fixed postgres_local_cache.save_load raising psycopg2.errors.UniqueViolation when more than one master in an active-active multi-master cluster persists the same JID; the INSERT is now idempotent via ON CONFLICT (jid) DO NOTHING on PostgreSQL >= 9.5, and the duplicate is tolerated on older servers. #69214
Fixed Windows MSI self-upgrade via pkg.install failing with error 1603. The old product's DeleteConfig_DECAC custom action was unconditionally deleting ROOTDIR\var during RemoveExistingProducts, destroying the MSI that pkg.install had cached to ROOTDIR\var\cache before launching the upgrade. Users who had REMOVE_CONFIG=1 persisted in the registry (from checking "On uninstall" at install time) hit a worse variant where the entire ROOTDIR was deleted. The fix checks UPGRADINGPRODUCTCODE — set by Windows Installer whenever an uninstall is triggered by a major upgrade — and skips all ROOTDIR deletion during upgrades, matching the behaviour of the NSIS installer which has always preserved ROOTDIR during upgrades. #69219
Fixed TypeError: string indices must be integers in the minion when the master returns a bare string error response (e.g. "bad load", "Some exception handling minion payload") for a pillar request. The minion now raises a clean AuthenticationError instead of crashing, allowing the caller to retry or fail gracefully. #69228
pkg.list_patches in yumpkg.py parses tdnf output on Photon OS #69229
Fix git.tag so that the documented message argument is actually forwarded to git tag, creating an annotated tag with the supplied message instead of silently producing a lightweight tag. #69298
Fixed salt.auth.pam conversation callback so it answers PAM_PROMPT_ECHO_ON prompts with the supplied username; previously only PAM_PROMPT_ECHO_OFF prompts were answered, which caused pam_authenticate to silently fail (and salt-api to return 401) against PAM stacks that re-prompt for the user. #69304
Ensure multiple masters have their own job/state queues #69308
Fixed loading private keys from PKCS#12 containers with x509_v2 #69312
Fixed creating self-signed PKCS#12-encoded certificates #69319
Fixed minion state queue replacing the master-assigned JID on queued state runs, so returns now come back tagged with the JID the master actually published. #69386
Made the salt user's home directory and the relenv extras-<py-ver> directory configurable in the Linux packaging. The DEB preinst scripts now source /etc/default/salt-setup (and /etc/sysconfig/salt-minion-setup for cross-distro parity with RPM) before applying the SALT_HOME/SALT_USER/SALT_GROUP/SALT_NAME defaults, mirroring the long-standing RPM behavior. A new SALT_EXTRAS_DIR override is honored by both stacks so the extras tree can be relocated outside /opt/saltstack/salt and its ownership is correctly restored on upgrade. #69402
Fixed minion worker threads hanging or crashing when returning job results
to the master. The main process now fires an error event back to the worker
when req_channel.send() times out, so workers wake up immediately rather
than waiting out their full timeout. Replaced the bare TimeoutError raised
in _send_req_sync with SaltReqTimeoutError so _return_pub's existing
handler catches it correctly. The worker's wait timeout is now derived from
return_retry_timer_max * return_retry_tries to ensure it always outlasts
the main process's retry budget. #69416
Fixed zsh completion by using the proper python3 instead of python2. #69419
Fixed Photon OS Arm64 FIPS CI by re-enabling the OpenSSL default provider after installing openssl-fips-provider, working around the disabled-default-provider bug in openssl-fips-provider <= 3.1.2-3.ph5 on the lagging Photon aarch64 mirror. #69449
Add regression test for changelog template multi-line rendering and harden template with indent filter so continuation lines are correctly indented under the bullet (defensive backport of #69458 to 3006.x). #69454
Fixed minion not honoring SIGTERM while stuck in the master DNS retry loop, which caused systemd to escalate to SIGKILL after 90 seconds. #69466
Fixed lgpo_reg module and state functions failing on Windows Domain Controllers with Access is denied when writing to HKLM\SOFTWARE\Policies\ subkeys. The set_value, disable_value, and delete_value execution module functions now accept a write_registry parameter (default None) that auto-detects Domain Controllers via the ProductType registry key and skips the direct registry write when one is detected, instead relying on the Group Policy engine to apply the policy on the next refresh. An explicit True or False overrides auto-detection. A refresh_policy parameter (default False) has been added to all three functions to trigger an in-process userenv.RefreshPolicy call immediately after the Registry.pol file is updated. The corresponding state functions value_present, value_disabled, and value_absent expose the same parameters. A standalone lgpo_reg.refresh_policy execution function and lgpo_reg.refresh_policy state have been added to allow a single Group Policy refresh to be issued after a batch of policy writes. is_domain_controller has been added to salt.utils.win_functions and refresh_policy has been added to salt.utils.win_lgpo_reg. #69468
Fixed 3006.x Windows nightly CI by pinning the runner-host Python to 3.14.6 (OpenSSL 3.5.7); the setup-python default 3.14 was resolving to a cached 3.14.5 build whose OpenSSL 3.0.20 rejected the cert pypi.org currently serves. #69486
Fixed 3006.x Windows nightly CI Deps by dropping a sitecustomize hook into the salt onedir's Lib/site-packages that applies the cpython#104135 iter-and-skip patch before pip touches TLS; the prior runner-host Python pin in #69486 targeted the wrong interpreter (the failing pip runs in a venv created from the relenv-bundled Python 3.10) and is reverted. #69490
Fixed lgpo_reg failures on Windows when Registry.pol is temporarily locked by the Group Policy service or other processes. Salt now uses EnterCriticalPolicySection / LeaveCriticalPolicySection from userenv.dll — the same synchronization primitive used by the GP engine — to serialize read-modify-write access to Registry.pol. A retry loop with configurable attempts and delay is also applied for non-GP lockers such as antivirus scanners or VSS snapshots that do not participate in the GP critical section handshake. #69492
Added shadow.verify_password to salt.modules.win_shadow, which
validates a Windows user's password via LogonUser with
LOGON32_LOGON_NETWORK (Microsoft's recommended approach per
KB180548 <https://support.microsoft.com/en-us/help/180548>_) without
creating an interactive session. If the check causes an account lockout,
the account is automatically unlocked. Updated user.present on Windows
to use shadow.verify_password so the password is only changed when it
differs from the current value, matching the idempotent behaviour on other
platforms. #41347
Added ability to configure the pillar destination for the netbox ext_pillar via destination_pillar_key #65531
Migrate Salt documentation to the PyData Sphinx theme. This update modernizes the documentation UI, improves navigation with a persistent sidebar tree, and fixes issues with embedded video playback. #69185
fix etcdv3 module authentification when using etcd3-py lib #69202
Added lgpo_reg.get_rsop_value to query the Resultant Set of Policy (RSoP) for a registry key/value and detect whether it is managed by a Domain Group Policy Object. The lgpo_reg module functions set_value, disable_value, and delete_value now log a warning when a Domain GPO is detected for the target value. The lgpo_reg state functions value_present, value_disabled, and value_absent append the same warning to the state comment so it is visible in state output. #69205