(release-3006.26)=

Salt 3006.26 release notes#

Changelog#

Removed#

  • Removed the unmaintained linode-python package dependency to stop SyntaxWarnings during install for retired Linode API v3. #68992

Changed#

  • 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#

  • 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#

  • 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