Database audit fix

How to Secure Database Client Credentials on Linux

Database client credential files such as .my.cnf and .pgpass hold a database password in plain text in a user's home directory, so any mode looser than owner-only hands it to other local accounts. Set each of them to 600.

Hiren KalariyaLast reviewed: Aug 30, 2026Check client-credential-files
High
severity
Yes
needs root or sudo
3
results it can return
4 of 4
checks in this audit

Every threshold on this page is transcribed from the database-security-credential-storage audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 4 Credential Storage checks, or how the audit runs.

High severityNeeds sudo

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 thresholds this check applies
ResultWhenWhat it means
SKIPNone of the files existNothing was verified. None of the five filenames existed in the directories this run could see, and an unprivileged run only sees your own home.
FAILAny file has non-zero group or other bitsAt least one file holding a plaintext database password is readable beyond its owner, and the message lists each path with the mode it carries.
PASSAll are owner-onlyEvery 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.cnf created with an editor under umask 022 is 644.
  • .dbshell is the legacy mongo shell history; mongosh uses ~/.mongodb/mongosh/, which is not checked.
  • .rediscli_auth is written by redis-cli when REDISCLI_AUTH/--pass are used with history; it is uncommon.
  • Service accounts with homes outside /home (e.g. /var/lib/mysql, /srv/app) are not scanned. Enumerate with getent passwd.
  • Directory-level 700 on 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/null

Prefer 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 less

Debugging

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

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
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Start instantly· No credit card· No sneaky autorenewals