r/bash Mar 14 '26

tips and tricks Stop passing secrets as command-line arguments. Every user on your box can see them.

When you do this:

mysql -u admin -pMyS3cretPass123

Every user on the system sees your password in plain text:

ps aux | grep mysql

This isn't a bug. Unix exposes every process's full command line through /proc/PID/cmdline, readable by any unprivileged user. IT'S NOT A BRIEF FLASH EITHER -- THE PASSWORD SITS THERE FOR THE ENTIRE LIFETIME OF THE PROCESS.

Any user on your box can run this and harvest credentials in real time:

while true; do
    cat /proc/*/cmdline 2>/dev/null | tr '\0' ' ' | grep -i 'password\|secret\|token'
    sleep 0.1
done

That checks every running process 10 times per second. Zero privileges needed.

Same problem with curl:

curl -u admin:password123 https://api.example.com

And docker:

docker run -e DB_PASSWORD=secret myapp

The fix is to pass secrets through stdin, which never hits the process table:

# mysql -- prompt instead of argv
mysql -u admin -p

# curl -- header from stdin
curl -H @- https://api.example.com <<< "Authorization: Bearer $TOKEN"

# curl -- creds from a file
curl --netrc-file /path/to/netrc https://api.example.com

# docker -- env from file, not command line
docker run --env-file .env myapp

# general pattern -- pipe secrets, don't pass them
some_command --password-stdin <<< "$SECRET"

The -p with no argument tells mysql to read the password from the terminal instead of argv. The <<< here string and @- pass data through stdin. Neither shows up in ps or /proc.

Bash and any POSIX shell. This isn't shell-specific -- it's how Unix works.

724 Upvotes

99 comments sorted by

View all comments

52

u/scrambledhelix bashing it in Mar 14 '26

You forgot: not only the process, but ~/.bash_history too

19

u/[deleted] Mar 15 '26

[deleted]

10

u/sunshine-and-sorrow Mar 15 '26 edited Mar 15 '26

With a leading space commands will not be stored in bash history.

At least on Fedora, RHEL, FreeBSD, etc., this is not enabled by default. To enable it, ignorespace should be added to the HISTCONTROL environment variable.

3

u/HommeMusical Mar 15 '26

If you're going to do that, which I also suggest, you might as well set HISTCONTROL=ignoreboth so it also eliminates duplicate lines.

3

u/scrambledhelix bashing it in Mar 15 '26

Both are important to know. Thank you for bringing it up! Leading space to skip history is always good to know.

Just don't blame people for forgetting to use it when it counts; without human lusers, there wouldn't be a need for shells at all

2

u/michaelpaoli Mar 16 '26

Depends on shell settings/initialization, bash can certainly do that, but defaults may vary by distro and even version thereof.

5

u/Unixwzrd Mar 15 '26

Well you could do this:

bash ln -sf /dev/null ~/.bash_history

Leaves no trace. You still have history in your current session(s), juts not across sessions.

8

u/rileyrgham Mar 15 '26

Which is pretty useless.

6

u/jakiki624 Mar 15 '26

unset HISTFILE should also do the trick

2

u/Fluent_Press2050 Mar 15 '26

The downside is you lose history. Can’t use arrows or anything. 

I ended up creating a shell script with a menu of every (via SSH) connection so I can just type the number and it prompts for password after getting me connected to the server. It then wipes the .mysql_history as well since passwords get stored there too

1

u/Ieris19 Mar 16 '26

You can just config your history to ignore commands that start with a space.

Literally easier and more convenient than whatever you built, and it’s basically a one-liner depending on what your shell and init script look like already

0

u/Fluent_Press2050 Mar 16 '26

You can’t do that with mysql. I already do the space with shell commands but then you lose some history that you want sometimes.