Ensure 'Skip_show_database' database flag for Cloud SQL MySQL instance is set to 'on'
On a Cloud SQL for MySQL instance, any account that can log in can run SHOW DATABASES, and the statement returns the names of the schemas that account holds some privilege on. The control requires the skip_show_database database flag to be set to on, which restricts the statement to accounts holding the SHOW DATABASES privilege.
What the benchmark asks for
Every Cloud SQL for MySQL instance carries skip_show_database=on in its databaseFlags collection. The flag is off by default, so an instance created without an explicit flag setting fails the control, and so does an instance whose flag was dropped by a later patch.
The check is automated. It reads the instance's database flags and looks for an entry named skip_show_database with the value on. Nothing about the check depends on how the instance is used, so the control applies to every MySQL instance in the project, from a single-schema application database to a shared instance hosting dozens of tenants.
The recommendation applies to MySQL database instances. Cloud SQL for PostgreSQL and Cloud SQL for SQL Server instances are therefore out of scope for this control.
How it works
skip_show_database changes who may run SHOW DATABASES, not what the statement returns to a user who is allowed to run it.
With the flag off, every account may run the statement, and MySQL filters the output down to the schemas the account has some privilege on. With the flag on, an account without the SHOW DATABASES privilege gets an error instead of a list, and an account that holds the privilege sees every schema name on the server. The privilege is global, granted with GRANT SHOW DATABASES ON *.* TO ..., so turning the flag on splits accounts into two groups. Those that see the whole server and those that see nothing.
That split is the point. Without the flag, a low-privilege account that has been granted access to one schema can still enumerate the names of every other schema it has a privilege on, and the filtered output leaks structure whenever privileges are broader than intended. A GRANT SELECT ON *.* handed out for a reporting job, or a stale grant left behind by a migration, turns the filtered list into a full list. With the flag on, the account has to hold a privilege that an administrator granted deliberately.
The flag also governs the information_schema.SCHEMATA table and the SHOW SCHEMAS alias, which are the same lookup under different names. It does not restrict anything else.
Cloud SQL applies the flag as a MySQL server system variable. Setting, removing, or changing any database flag on an instance can restart the database, and skip_show_database is one of the flags that does.
What the control does not cover
It does not change who can reach the instance, and it does not change what a reachable account can do once it logs in. A public IP with an authorized network of 0.0.0.0/0, or a MySQL root account with a weak or shared password, leaves an instance exposed whether or not the flag is on.
Schema names are not usually secret in a single-application instance. If the instance hosts one application database plus the MySQL system schemas, every account on it already belongs to that application, and the flag hides a list those accounts could reconstruct from the application's own queries. The control still fails such an instance, and setting the flag costs almost nothing there, but the risk it removes is close to zero.
The flag earns its keep on shared instances. Where one instance hosts schemas for several tenants, teams, or environments, a schema name tells an attacker who else is on the box, and names are often predictable enough to guess at the remaining structure once one is known. A customer_acme schema sitting next to customer_globex tells a compromised Acme account exactly who to look for next.
Tooling is the other consideration. Anything that calls SHOW DATABASES to discover what it is connected to stops getting a list once the flag is on, unless the account it uses holds the privilege.
What it costs to comply
The change restarts the instance. Applying the flag through any interface triggers a database restart, which closes every open connection and makes the instance unavailable for the length of the restart. Applications with connection pools see errors until the pool reconnects, and applications without retry logic see failed requests. Schedule the change in a maintenance window and tell whoever owns the application.
On a replication source, the restart spreads. If the instance has read replicas, a replica is also restarted so that its configuration matches the new flag value on the primary. An instance serving reads through replicas therefore loses both tiers rather than one, which matters when the replicas were the thing keeping the application up during primary maintenance.
The flag collection is replaced, not merged. A gcloud sql instances patch with --database-flags sets exactly the flags listed and resets every flag not listed to its default. The same is true of the Admin API: a PATCH carrying a databaseFlags array replaces the whole collection, so a later automated patch that sets one unrelated flag will silently remove skip_show_database and break compliance. Read the current flags before you write new ones, and prefer a declarative tool that holds the whole set.
The running cost after the change is the privilege grants. Accounts that legitimately need the full schema list need an explicit GRANT SHOW DATABASES ON *.*, which is granted server-wide rather than per schema. Expect to find them among administrator accounts, monitoring agents, and schema migration tools.
The case for not paying the cost is narrow. An instance scheduled for deletion, or one where a restart during the available window is more expensive than the enumeration risk on a single-schema database. Both are deferrals rather than exemptions, since the control will keep failing.
How to check your environment
List the instances in the project:
gcloud sql instances listThen read the flags on each MySQL instance and look for the entry inside the databaseFlags collection. The applied value lives in the instance settings, and either gcloud sql instances describe or the instance's own view in the Google Cloud console will show it:
gcloud sql instances describe INSTANCE_NAME --format=json | jq '.settings.databaseFlags[] | select(.name=="skip_show_database")|.value'The check passes only on on. off fails. So does empty output, which means nobody has set the flag on that instance.
The instance settings report what Cloud SQL has been asked to apply. To confirm the value the MySQL server is actually running with, connect with the mysql client and read the system variable:
SHOW VARIABLES LIKE 'skip_show_database';The two disagree only in the window between a patch being accepted and the restart completing, so a difference between them usually means the restart has not finished.
While you are connected, check which accounts already hold the privilege, since those accounts are unaffected by the flag:
SELECT user, host FROM mysql.user WHERE Show_db_priv = 'Y';How to implement it
Set the flag with a patch that lists every flag the instance should end up with. The gcloud command takes them as one comma-separated --database-flags argument:
gcloud sql instances patch INSTANCE_NAME --database-flags skip_show_database=on,general_log=on,wait_timeout=200000Run gcloud sql instances describe first and carry the existing flags into that list. Anything left out goes back to its default value.
To start from a clean state on an instance whose flags have drifted, clear them and apply the set you want. The CLI warns that the instance will restart before it goes ahead:
gcloud sql instances patch INSTANCE_NAME --clear-database-flagsFor instances managed as code, Terraform expresses the flag as its own database_flags block on the google_sql_database_instance resource, with name and value as separate arguments:
settings {
database_flags {
name = "skip_show_database"
value = "on"
}
}That form is what stops new instances from failing tomorrow. Because the provider holds the complete set of blocks, it does not have the overwrite hazard of a hand-written patch, and a flag removed outside Terraform comes back on the next apply. Put the block in whatever module your teams use to create MySQL instances so the flag is on from the moment the instance exists.
Through the Admin API, send a PATCH to the instance resource with the flag as an entry in the databaseFlags array. There is no endpoint for an individual flag, so include every flag you want to keep:
{
"settings": {
"databaseFlags": [
{ "name": "skip_show_database", "value": "on" }
]
}
}After the restart, grant the privilege to the accounts that need the full list, and to no others:
GRANT SHOW DATABASES ON *.* TO 'migration_runner'@'%';Then test the application path before the maintenance window closes.
Further reading
- Configure database flags. The supported flag list for Cloud SQL for MySQL, which flags restart the instance, and how to set them with gcloud, Terraform, the Admin API, and the console.
