What this check reads
For MySQL the check asks the server SHOW DATABASES LIKE 'test' through the shared myq helper; any row at all means the installer's test database still exists. For PostgreSQL it runs SELECT has_schema_privilege('public','public','CREATE') through pgq, against the postgres maintenance database, and a returned value of exactly t means the PUBLIC pseudo-role can still create objects in the public schema. MongoDB has no probe: it is always recorded as "review by hand", because its default test database needs an interactive mongosh session. The verdict then aggregates across engines - one default found anywhere gives WARN and names the defaults alongside any engines that came back clean, while no defaults on at least one assessed engine gives PASS and lists both the clean engines and the ones that could not be audited.
When it applies
Runs per engine detected. The MySQL branch needs MYOK=1 (root socket access), the PostgreSQL branch needs PGOK=1; without them that engine is listed as "not audited" rather than judged. MongoDB is never assessed - it is always reported as needing a manual mongosh review - and Redis has no branch at all. The PostgreSQL probe runs only in the postgres maintenance database, so per-database public grants elsewhere in the cluster are not examined. A host with an engine present but nothing assessable produces SKIP, which is not a pass.
What each result means
| Result | When | What it means |
|---|---|---|
| WARN | `test` DB exists, or PUBLIC has CREATE on `public` | An installer default is still in place on an engine the check could query. The message names the engine and the default, and lists any engines that were clean. |
| PASS | Assessed engines are clean | Every engine the check could actually query is free of the default it looks for. Engines listed as not audited in the same message were not assessed at all. |
| SKIP | No engine, or none assessable | Nothing was verified: either no engine was detected, or the ones present could not be queried, so no default was ruled out. |
Why it matters
- MySQL's historical
testdatabase came with grants inmysql.dballowing any user (including anonymous) to create tables in any database namedtestortest_%.mysql_secure_installationremoves it. CIS MySQL "Ensure the 'test' database is not installed". - PostgreSQL before 15 let every role
CREATEinpublic. This enables search-path hijacking (CVE-2018-1058): a low-privilege role creates a function or operator that a superuser later calls. PostgreSQL 15 removed the default grant. CIS PostgreSQL "Ensure the public schema CREATE privilege is revoked".
Why it fails, and when it is wrong
- A database legitimately named
testin production triggers the MySQL branch. Rename it or accept. - The PostgreSQL probe runs in the
postgresmaintenance database only. Databases created before an upgrade to 15 keep the old grant even on a 15+ server, and those are not checked. Check each:psql -d <db> -c "SELECT has_schema_privilege('public','public','CREATE')". - Some ORMs/migrations tools assume they can create in
public; revoking requires granting CREATE to the application owner role explicitly.
How to fix it
-- MySQL
DROP DATABASE test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%'; FLUSH PRIVILEGES;
-- PostgreSQL (run in each database)
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
GRANT CREATE ON SCHEMA public TO app_owner; -- only the role that needs itVerify the fix
# MySQL - the test database must be gone, along with its grants:
sudo mysql -N -e "SHOW DATABASES LIKE 'test'" # expected: no output
sudo mysql -N -e "SELECT COUNT(*) FROM mysql.db WHERE Db LIKE 'test%'" # expected: 0
# PostgreSQL - check EVERY database, not just the one the check looks at:
sudo -u postgres psql -X -tAc \
"SELECT datname FROM pg_database WHERE datallowconn" | while read -r db; do
printf '%s: %s\n' "$db" "$(sudo -u postgres psql -X -tAd "$db" -c \
"SELECT has_schema_privilege('public','public','CREATE')")"
done
# expected: f for every databaseDebugging
Nothing matched the shared engine probe on this host.
An engine is installed but could not be queried (MYOK/PGOK are 0), or the only engine present is MongoDB, which this check never assesses. Test access with sudo mysql -N -e 'SELECT 1' / sudo -u postgres psql -X -tAc 'SELECT 1'.
Expected. The check probes only the postgres database. Databases created before an upgrade to PostgreSQL 15 keep the pre-15 grant; use the per-database loop above.
The MySQL branch matches the name only. Rename the database, or accept the finding; it is LOW severity for this reason.
The application role needs the privilege explicitly: GRANT CREATE ON SCHEMA public TO app_owner;. Revoking from PUBLIC is not the same as revoking from your app.
Sources
- MySQL 8.4: mysql_secure_installation (removes test DB)
- MySQL 8.4: Grant Tables,
mysql.db - PostgreSQL: Schemas and the public schema pattern
- PostgreSQL 15 release notes (public schema CREATE removed)
- PostgreSQL wiki: A Guide to CVE-2018-1058 Protect Your Search Path
- PostgreSQL: has_schema_privilege
- MongoDB: databases and the default
testdatabase
How the script reads this
Next
Re-run the Configuration & Hardening audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 7 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.
All 7 Configuration & Hardening fixes