What this check reads
The check lists five filenames - .my.cnf, .pgpass, .mongoshrc.js, .dbshell and .rediscli_auth - in /root and in every /home/* directory when it has root, or only in the audit user's own $HOME when it does not. If none of them exist there is nothing to inspect and the result is a SKIP. For each file that does exist it reads the octal mode with stat -c %a and requires both the group digit and the other digit to be 0, so 600, 400 and 700 pass, while any mode granting a single bit to the group or to the world, 640 included, is a FAIL.
When it applies
Always runs (no database engine needs to be installed - these are client-side files), but its scope depends on privilege. With root or sudo it globs /root/ and every /home/*/; without, it looks only in the audit user's own $HOME, so an unprivileged run can easily PASS while other users' credential files are world-readable. Five filenames are checked: .my.cnf, .pgpass, .mongoshrc.js, .dbshell, .rediscli_auth. Service accounts whose home is outside /home (/var/lib/mysql, /srv/app, …) are never scanned. Both the group and other digits must be 0 to pass, so 640 FAILs here - unlike the web-config check, which only tests the other digit.
What each result means
| Result | When | What it means |
|---|---|---|
| SKIP | None of the files exist | Nothing was verified. None of the five filenames existed in the directories this run could see, and an unprivileged run only sees your own home. |
| FAIL | Any file has non-zero group or other bits | At least one file holding a plaintext database password is readable beyond its owner, and the message lists each path with the mode it carries. |
| PASS | All are owner-only | Every credential file that was found is readable only by its owner, though an unprivileged run will have looked in your own home directory alone. |
Why it matters
These files hold plaintext passwords (.my.cnf [client] password=, .pgpass lines) or command history that may contain them (.dbshell, .mongoshrc.js). libpq refuses to use a .pgpass with group/other permissions; MySQL does not enforce it, so a 644 .my.cnf silently leaks the DBA password. OWASP Secrets Management: protect secrets at rest with filesystem permissions.
Why it fails, and when it is wrong
.my.cnfcreated with an editor underumask 022is644..dbshellis the legacy mongo shell history;mongoshuses~/.mongodb/mongosh/, which is not checked..rediscli_authis written byredis-cliwhenREDISCLI_AUTH/--passare used with history; it is uncommon.- Service accounts with homes outside
/home(e.g./var/lib/mysql,/srv/app) are not scanned. Enumerate withgetent passwd. - Directory-level
700on the home protects the file in practice, but the check still reports file-level mode. Fix the file mode anyway; it is cheap.
How to fix it
chmod 600 ~/.my.cnf ~/.pgpass ~/.mongoshrc.js ~/.dbshell ~/.rediscli_auth 2>/dev/nullPrefer not to store passwords on disk at all: mysql_config_editor (obfuscated .mylogin.cnf), PGPASSWORD via systemd credentials, Vault/agent injection, or socket auth for local admin.
Verify the fix
sudo ls -l /root/.my.cnf /root/.pgpass /home/*/.my.cnf /home/*/.pgpass \
/home/*/.mongoshrc.js /home/*/.dbshell /home/*/.rediscli_auth 2>/dev/null
# expected: every mode is -rw------- (600) or stricter
# libpq enforces this itself - a correct .pgpass produces no warning:
psql -h 127.0.0.1 -U app -c 'SELECT 1'
# a wrong mode gives: WARNING: password file "~/.pgpass" has group or world access; permissions should be u=rw (0600) or lessDebugging
None of the five filenames exist in the scanned homes. This is a genuine "nothing to check", but note the scope limit: if the audit ran unprivileged it only looked at $HOME. Re-run as root, or check other homes by hand, before treating it as clean.
Another user has their own copy. The message lists each offending path with its mode in brackets; fix every one listed, not just yours.
Its home is outside /home. Enumerate real homes with getent passwd | awk -F: '$3>=100{print $1, $6}' and check those directories manually.
Deliberate: this check requires owner-only (600/400/700), because these files hold plaintext passwords and no group needs them. Use 600.
The mode is only half the problem. Prefer removing the on-disk secret entirely: mysql_config_editor set --login-path=… (writes an obfuscated .mylogin.cnf), systemd credentials, a secrets manager, or socket auth for local admin work.
Sources
- PostgreSQL: The Password File (
.pgpass, 0600 requirement) - MySQL 8.4: Using Option Files (
~/.my.cnf) - MySQL 8.4: mysql_config_editor (login path file)
- MySQL 8.4: End-User Guidelines for Password Security
- MongoDB: mongosh configuration and history files
- Redis: redis-cli (REDISCLI_AUTH)
- systemd credentials (secure secret delivery to services)
- OWASP Secrets Management Cheat Sheet
How the script reads this
Next
Re-run the Credential Storage audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 4 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.
All 4 Credential Storage fixes