What happened?
Description
When a cmd.run (or any of cmd.*) invocation fails to start the underlying subprocess — typically a routine ENOENT because the binary path has a typo, but any spawn-time errno triggers it — the handler in salt/modules/cmdmod.py builds a CommandExecutionError whose message dumps the entire subprocess kwargs dict:
try:
proc = salt.utils.timed_subprocess.TimedProc(cmd, **new_kwargs)
except OSError as exc:
msg = "Unable to run command '{}' with the context '{}', reason: {}".format(
cmd if output_loglevel is not None else "REDACTED",
new_kwargs, # <-- contains env and stdin
exc,
)
raise CommandExecutionError(msg)
new_kwargs includes two fields that routinely carry secrets:
env — the run environment. Callers pass credentials via cmd.run "..." env='{"DB_PASSWORD": "..."}', or states pull a credential out of pillar and set it as an env var for a child binary. This is documented usage.
stdin — the bytes piped to the command. The standard pattern for feeding a password to a CLI without putting it on the argv (where it would show up in ps) is to write it on stdin. mysql -p, gpg --batch --passphrase-fd 0, every interactive sudo-friendly CLI expects this.
The resulting CommandExecutionError then leaks through three channels:
- minion log —
CommandExecutionError is logged at error level by the calling state / runner.
- master log — in many invocation modes the error is forwarded back to the master and logged there too.
- event-bus return data —
salt-api and salt-run callers see the full message in the job return, so any user with cmd.run access via eauth can read another user's env-passed secrets just by mistyping the binary name.
ENOENT is not a rare condition. A typo in a binary path, a missing package on a particular minion, an os.execvp-style PATH miss — anything triggers it. A typo should not exfiltrate credentials.
This is the textbook anti-pattern in OWASP A09:2021 (Security Logging and Monitoring Failures) / CWE-532 (Insertion of Sensitive Information into Log File).
Setup
Affects every Salt deployment. cmd.run is one of the most-used execution modules and is invoked from a large fraction of states.
Steps to Reproduce the behavior
# Trigger an OSError by pointing cmd.run at a binary that does not
# exist, while passing a credential through env (a documented
# pattern). The CommandExecutionError raised will contain the
# credential value.
salt-call cmd.run /no/such/binary env='{"DB_PASSWORD": "leak-me-into-the-log"}'
Then look at the minion log: the credential value is right there in the error message. Same applies to stdin="..." — the stdin payload is dumped verbatim.
Expected behavior
The error message should retain the debugging context that is actually useful (the command, cwd, shell, executable, timeout) and should not include env or stdin, both of which contain user-supplied data that routinely includes credentials.
Versions Report
Type of salt install
Official deb
Major version
3006.x, 3007.x
What supported OS are you seeing the problem on? Can select multiple. (If bug appears on an unsupported OS, please open a GitHub Discussion instead)
debian-11, debian-12
salt --versions-report output
salt --versions-report
Salt Version:
Salt: 3007.13
Python Version:
Python: 3.10.19 (main, Feb 5 2026, 07:05:38) [GCC 11.2.0]
Dependency Versions:
cffi: 2.0.0
cherrypy: unknown
cryptography: 42.0.5
dateutil: 2.8.2
docker-py: Not Installed
gitdb: Not Installed
gitpython: Not Installed
Jinja2: 3.1.6
libgit2: 1.9.1
looseversion: 1.3.0
M2Crypto: Not Installed
Mako: Not Installed
msgpack: 1.0.7
msgpack-pure: Not Installed
mysql-python: Not Installed
packaging: 24.0
pycparser: 2.21
pycrypto: Not Installed
pycryptodome: 3.19.1
pygit2: 1.18.2
python-gnupg: 0.5.2
PyYAML: 6.0.1
PyZMQ: 25.1.2
relenv: 0.22.3
smmap: Not Installed
timelib: 0.3.0
Tornado: 6.5.4
ZMQ: 4.3.4
Salt Extensions:
saltext.vault: 1.5.0
Salt Package Information:
Package Type: onedir
System Versions:
dist: debian 12.13 bookworm
locale: utf-8
machine: x86_64
release: 6.12.73+deb12-amd64
system: Linux
version: Debian GNU/Linux 12.13 bookworm
What happened?
Description
When a
cmd.run(or any ofcmd.*) invocation fails to start the underlying subprocess — typically a routineENOENTbecause the binary path has a typo, but any spawn-time errno triggers it — the handler insalt/modules/cmdmod.pybuilds aCommandExecutionErrorwhose message dumps the entire subprocess kwargs dict:new_kwargsincludes two fields that routinely carry secrets:env— the run environment. Callers pass credentials viacmd.run "..." env='{"DB_PASSWORD": "..."}', or states pull a credential out of pillar and set it as an env var for a child binary. This is documented usage.stdin— the bytes piped to the command. The standard pattern for feeding a password to a CLI without putting it on the argv (where it would show up inps) is to write it on stdin.mysql -p,gpg --batch --passphrase-fd 0, every interactivesudo-friendly CLI expects this.The resulting
CommandExecutionErrorthen leaks through three channels:CommandExecutionErroris logged at error level by the calling state / runner.salt-apiandsalt-runcallers see the full message in the job return, so any user withcmd.runaccess via eauth can read another user'senv-passed secrets just by mistyping the binary name.ENOENTis not a rare condition. A typo in a binary path, a missing package on a particular minion, anos.execvp-style PATH miss — anything triggers it. A typo should not exfiltrate credentials.This is the textbook anti-pattern in OWASP A09:2021 (Security Logging and Monitoring Failures) / CWE-532 (Insertion of Sensitive Information into Log File).
Setup
Affects every Salt deployment.
cmd.runis one of the most-used execution modules and is invoked from a large fraction of states.Steps to Reproduce the behavior
Then look at the minion log: the credential value is right there in the error message. Same applies to
stdin="..."— the stdin payload is dumped verbatim.Expected behavior
The error message should retain the debugging context that is actually useful (the command,
cwd,shell,executable,timeout) and should not includeenvorstdin, both of which contain user-supplied data that routinely includes credentials.Versions Report
Type of salt install
Official deb
Major version
3006.x, 3007.x
What supported OS are you seeing the problem on? Can select multiple. (If bug appears on an unsupported OS, please open a GitHub Discussion instead)
debian-11, debian-12
salt --versions-report output
salt --versions-report Salt Version: Salt: 3007.13 Python Version: Python: 3.10.19 (main, Feb 5 2026, 07:05:38) [GCC 11.2.0] Dependency Versions: cffi: 2.0.0 cherrypy: unknown cryptography: 42.0.5 dateutil: 2.8.2 docker-py: Not Installed gitdb: Not Installed gitpython: Not Installed Jinja2: 3.1.6 libgit2: 1.9.1 looseversion: 1.3.0 M2Crypto: Not Installed Mako: Not Installed msgpack: 1.0.7 msgpack-pure: Not Installed mysql-python: Not Installed packaging: 24.0 pycparser: 2.21 pycrypto: Not Installed pycryptodome: 3.19.1 pygit2: 1.18.2 python-gnupg: 0.5.2 PyYAML: 6.0.1 PyZMQ: 25.1.2 relenv: 0.22.3 smmap: Not Installed timelib: 0.3.0 Tornado: 6.5.4 ZMQ: 4.3.4 Salt Extensions: saltext.vault: 1.5.0 Salt Package Information: Package Type: onedir System Versions: dist: debian 12.13 bookworm locale: utf-8 machine: x86_64 release: 6.12.73+deb12-amd64 system: Linux version: Debian GNU/Linux 12.13 bookworm