Skip to main content

SEC.PRIV.GRANT_WILDCARD_HOST_IN_MIGRATION — A grantee reachable from anywhere

  • Category: security
  • Severity: medium
  • Level: 0 — reachable at any strictness; the security suite is gated by security.min_severity
  • Confidence: deterministic
  • Stability: stable
  • Suites: lint, security
  • Applies to: MySQL

What it reports

A MySQL GRANT whose grantee carries a wildcard in its host half:

  • 'app'@'%' — the full wildcard: any host the server is reachable from.
  • 'app'@'10.0.%', 'app'@'%.internal' — a partial wildcard, reported with a different message.

localhost, an address, and a name without a wildcard are all silent.

Why

In MySQL an account is a pair — the user name and the host it may connect from — and the host half is a real access control rather than documentation. 'app'@'10.0.1.5' is an account that exists only for connections from that address.

Remove it and the password carries the whole defense on its own. That is what turns a leaked credential from an incident into a breach: with a host restriction the leak is exploitable from inside the network, without one from anywhere the server is reachable.

The partial wildcard is reported deliberately, and it is the one people trust too much. It reads like a restriction; MySQL evaluates it as a pattern. Somebody who wrote '%.example.com' may not have considered that any host whose name resolves into that pattern satisfies it.

What to do

Reported:

DB::statement("GRANT SELECT ON app.orders TO 'app_runtime'@'%'");

Not reported:

DB::statement("GRANT SELECT ON app.orders TO 'app_runtime'@'10.0.1.5'");

Name the host, subnet or container network the application connects from.

What it does not see

  • It cannot know what the network already prevents. An account reachable only through a private subnet or a proxy may be perfectly safe with %, and the statement says nothing about what sits in front of the server.
  • It reads the grant, not the account: a host restriction applied by a separate CREATE USER or RENAME USER is invisible to it.
  • A containerized or serverless deployment often has no stable client address to name. That is a real reason to accept this id with a reason, not a reason to narrow the rule.

Not reported on PostgreSQL

PostgreSQL grantees are roles, full stop — there is no @host half to be a wildcard. Its equivalent control lives in pg_hba.conf, which this suite judges through its own family of rules against a live server.

Suppressing it

// config/sqlens.php
'ignore' => [
['rule' => 'SEC.PRIV.GRANT_WILDCARD_HOST_IN_MIGRATION', 'reason' => 'why this project accepts it'],
],

A suppression needs a reason, and the reason is read by whoever inherits the project.