All rules Rule BC001 · PC
Credential rule · any database
critical
PC · Privileges & credentials
free in the CLI

Why should a migration never set a role password in plaintext?

Role password set in plaintext by the migration

Critical: a working credential committed to the repository; rotate it, git history keeps it.

What happens

CREATE ROLE … PASSWORD 'x' puts the database password into a file that is committed, reviewed in pull requests, printed by CI and kept in git history forever, and the server writes the statement to its own log when log_statement covers DDL. Rotating it later does not un-leak it. Postgres accepts an already-hashed SCRAM-SHA-256 verifier in the same position, and the rule stays silent on one; an md5 verifier is reported as a warning because it can be cracked offline.

Why it is dangerous on a populated table

Table size does not change this one: a committed credential is live from the first row, and git history keeps it after the fix.

Fires on

CREATE ROLE app_user LOGIN PASSWORD 'hunter2';

The safe pattern

Create the role without a password in the migration and set it out of band from your secret store at deploy time (psql \password, or ALTER ROLE … PASSWORD run by the deploy job, not committed). If the migration must carry it, carry a SCRAM-SHA-256 verifier computed elsewhere, the server stores exactly that, and it cannot be replayed to log in.

CREATE ROLE app_user LOGIN;
-- deploy job, from the secret store, never committed:
--   ALTER ROLE app_user PASSWORD 'SCRAM-SHA-256$4096:…$…:…';

Fixtures

The rule ships with these files and the test suite runs them on every change: the first set must fire, the second must stay silent.

Fires (3)

alter role plaintext
ALTER ROLE app_user WITH PASSWORD 'correct horse battery staple' VALID UNTIL '2027-01-01';
create role plaintext
CREATE ROLE app_user LOGIN PASSWORD 'hunter2';
md5 verifier
CREATE USER reporter WITH ENCRYPTED PASSWORD 'md5a3f2c0d9e8b7a6f5e4d3c2b1a0f9e8d7';

Stays silent (4)

no password
CREATE ROLE app_user LOGIN;
GRANT reporting TO app_user;
password null
ALTER ROLE app_user PASSWORD NULL;
placeholder
CREATE ROLE app_user LOGIN PASSWORD '${APP_USER_PASSWORD}';
scram verifier
CREATE ROLE app_user LOGIN PASSWORD 'SCRAM-SHA-256$4096:c2FsdHNhbHRzYWx0c2FsdA==$U3RvcmVkS2V5U3RvcmVkS2V5U3RvcmVkS2V5U3RvcmVkS2V5U3RvcmU=:U2VydmVyS2V5U2VydmVyS2V5U2VydmVyS2V5U2VydmVyS2V5U2VydmU=';

How to check locally

Catch this before it ships

This rule runs locally in the free CLI, or with the full corpus through the hosted service. No install, nothing leaves your machine:

npx bolvrk check migration.sql