MySQL privilege change auditing: GRANT, REVOKE, and unexpected SUPER
Unexpected privilege changes are high-impact, low-frequency events. A spike in Com_grant, an unknown account with SUPER, or a GRANT ALL outside a change window can signal privilege escalation, misconfigured automation, or migration debt. This guide covers how to detect, diagnose, and remove unauthorized grants and deprecated SUPER privileges in production.
What this means
MySQL increments status counters for GRANT, REVOKE, CREATE USER, ALTER USER, and DROP USER. The counters are cumulative; compute deltas over fixed intervals instead of reading absolute values.
Current global privileges live in mysql.user. Database-, table-, and column-level grants live in mysql.db, mysql.tables_priv, and mysql.columns_priv. In MySQL 8.0+, role relationships are in mysql.role_edges and mysql.default_roles.
mysql.user.password_last_changed is non-NULL for accounts using built-in authentication plugins. It updates on CREATE USER, ALTER USER, and SET PASSWORD, giving a coarse recency filter even when logs are disabled.
SUPER is deprecated since MySQL 8.0 and will be removed. It bundles administrative capabilities now exposed as granular dynamic privileges. Any privilege change outside a documented change window is at minimum an operational ticket. An unexpected GRANT ALL or SUPER grant from an unknown actor is a security incident.
Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Unauthorized privilege escalation | Unknown account with SUPER or ALL PRIVILEGES; sudden Com_grant spike | mysql.user sorted by password_last_changed DESC |
| Misconfigured automation | Identical grants rolled out across many hosts in a short window | Audit or general log for the service account |
Deprecated SUPER migration debt | Compliance scanner flags SUPER on non-admin users | mysql.user where Super_priv = 'Y' |
Implicit user creation via GRANT | Legacy scripts creating accounts without CREATE USER | Logs for user creation events and matching CREATE USER entries |
Quick checks
-- Cumulative privilege-modification counters
SHOW GLOBAL STATUS
WHERE Variable_name IN ('Com_grant','Com_revoke','Com_create_user','Com_alter_user','Com_drop_user');
-- Accounts with recent password or privilege changes
SELECT user, host, password_last_changed
FROM mysql.user
WHERE password_last_changed IS NOT NULL
ORDER BY password_last_changed DESC;
-- Current SUPER holders against your baseline
SELECT user, host
FROM mysql.user
WHERE Super_priv = 'Y';
-- Is general logging available?
SHOW GLOBAL VARIABLES LIKE 'general_log';
SHOW GLOBAL VARIABLES LIKE 'log_output';
-- Recent grant activity from the general log
-- Warning: this query scans a high-volume table and can impact performance.
SELECT event_time, user_host, argument
FROM mysql.general_log
WHERE argument LIKE 'GRANT%'
OR argument LIKE 'REVOKE%'
OR argument LIKE 'CREATE USER%'
OR argument LIKE 'ALTER USER%'
OR argument LIKE 'DROP USER%'
ORDER BY event_time DESC
LIMIT 50;
How to diagnose it
flowchart TD
A[Com_grant spike] --> B{Audit log available?}
B -->|Yes| C[Query for GRANT/REVOKE]
B -->|No| D[Check password_last_changed]
C --> E[Identify actor]
D --> E
E --> F{Authorized?}
F -->|No| G[Revoke and investigate]
F -->|Yes| H[Update baseline]Establish a privilege baseline. Dump
mysql.user,mysql.db,mysql.tables_priv,mysql.columns_priv, and, in MySQL 8.0+,mysql.role_edgesandmysql.default_rolesto a versioned snapshot. Without a baseline you cannot distinguish a new grant from historical configuration.Correlate counter deltas. Sample
Com_grantandCom_revokebefore and after the suspected window. A sustained increase indicates active changes.Inspect
password_last_changed. Querymysql.userfor accounts with a recent timestamp. This column is non-NULL only for built-in authentication plugins, so it is a coarse filter, not proof of a grant.Query logs. If
general_logis enabled, filter it forGRANT,REVOKE,CREATE USER,ALTER USER, andDROP USER. Keep the window short; the general log grows fast and adds overhead. For a real audit trail, use MySQL Enterprise Audit or the MariaDB Audit Plugin.Identify the actor. Audit plugins and the general log record the MySQL database user, not the OS or end user. If a shared app or automation account executed the change, correlate the timestamp with CI/CD pipelines, deployment logs, or bastion session records. If the session is still open, check
PROCESSLISTorperformance_schema.threads.Assess
SUPERandALL PRIVILEGESscope. Count accounts withSuper_priv = 'Y'and any global*.*grants outside your expected admin set. Map existingSUPERgrants to the minimum required dynamic privileges and revoke the broad grant.
# Safe read-only baseline dump. On MySQL 5.7, omit role_edges and default_roles.
mysqldump mysql user db tables_priv columns_priv role_edges default_roles \
> "mysql_privs_$(date +%F).sql"
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
Com_grant rate | Detects new grants | Sustained spike outside maintenance |
Com_revoke rate | Detects privilege removal, possibly to hide activity | Spike correlating with an incident |
SUPER holder count | Tracks deprecated and overbroad grants | Count above the admin baseline |
password_last_changed recency | Identifies recently touched accounts | Entries newer than the last approved change |
| Audit log events | Forensic statement-level evidence | GRANT/REVOKE/CREATE USER/ALTER USER from non-allowlisted hosts |
Fixes
Revoke unauthorized grants
If an account received privileges outside a change window, revoke the specific privilege and scope rather than using REVOKE ALL unless the account should have no access.
-- Revoke a specific deprecated privilege
REVOKE SUPER ON *.* FROM 'unexpected_user'@'%';
Verify with SHOW GRANTS FOR 'unexpected_user'@'%';.
If the account is entirely unauthorized, drop it:
DROP USER 'unexpected_user'@'%';
Warning: DROP USER is immediate. If the account is used by a running application, connections will fail on their next authentication. Prefer REVOKE until you confirm the account is unused.
Replace SUPER with dynamic privileges
SUPER bundles capabilities now available as dynamic privileges. Replace it with the smallest set the account actually needs. For example:
REVOKE SUPER ON *.* FROM 'backup_svc'@'%';
GRANT BACKUP_ADMIN, SESSION_VARIABLES_ADMIN ON *.* TO 'backup_svc'@'%';
An account that only initiates replication should receive REPLICATION_SLAVE_ADMIN instead of SUPER.
Tradeoff: migration requires knowing exactly which SUPER operations the account uses. A too-narrow replacement breaks automation. Test in a non-production environment first.
Address role-based privilege inflation
In MySQL 8.0+, roles carry privileges that may be activated automatically or at login. An account can show few direct grants while inheriting broad access.
SHOW GRANTS FOR 'app_user'@'%';
SHOW GRANTS FOR 'app_user'@'%' USING 'admin_role';
SHOW GRANTS FOR 'admin_role';
Audit mysql.role_edges and mysql.default_roles regularly, and expand role grants during reviews so the effective privilege set is visible.
Prevention
- Snapshot privileges weekly. Dump
mysql.user,mysql.db,mysql.tables_priv,mysql.columns_priv, and, in 8.0+,mysql.role_edgesandmysql.default_rolesinto version control and diff them. - Restrict
GRANTexecution. Do not use shared application credentials for schema or user changes. Use dedicated, named admin accounts. - Require explicit
CREATE USER. Disable implicit user creation viaGRANTby policy and automation linting. - Enable continuous auditing. Use MySQL Enterprise Audit or the MariaDB Audit Plugin. The general log is too expensive and unstructured for continuous security monitoring.
- Alert on counter spikes. Page or ticket for any non-zero
Com_grantorCom_revokerate outside maintenance windows. - Audit
SUPERquarterly. Migrate remainingSUPERgrants to dynamic privileges before the privilege is removed.
How Netdata helps
- Chart
Com_grantandCom_revokerates continuously to detect privilege changes as they happen. - Correlate privilege counter spikes with connection anomalies, such as
Aborted_connectsincreases, that can indicate probing before escalation. - Feed custom
SUPER-holder queries into Netdata via a script or custom collector to alert on baseline drift. - Cross-reference privilege change timestamps with slow query or replication lag charts to separate administrative activity from workload shifts.
Related guides
- How MySQL actually works in production: a mental model for operators
- MySQL Aborted_connects and Aborted_clients climbing: diagnosis
- MySQL adaptive hash index latch contention: high CPU, low throughput
- MySQL binary logs filling the disk: expiry, lagging replicas, and purge
- MySQL InnoDB buffer pool hit ratio collapse: the cliff edge
- MySQL slow after restart: buffer pool warm-up and the cold cache
- MySQL innodb_buffer_pool_size tuning: 60-80% of RAM and when that breaks
- MySQL Innodb_buffer_pool_wait_free > 0: buffer pool memory pressure
- MySQL InnoDB checkpoint age: the redo log capacity signal nobody watches
- MySQL connection exhaustion: detection, diagnosis, and prevention
- MySQL stuck in InnoDB crash recovery: why startup hangs after an unclean shutdown
- MySQL innodb_deadlock_detect=OFF: when deadlock detection becomes the bottleneck







