Skip to main content

SEC.CFG.SECURE_FILE_PRIV — File I/O has no limit, and the value that says so looks like nothing

  • Category: security
  • Severity: high
  • Level: 0
  • Confidence: deterministic
  • Downtime class: none — the finding is about a server setting, not about a statement
  • Stability: stable
  • Suites: audit
  • Applies to: MySQL 8.4

Read this table before the rest of the page

secure_file_priv decides how far the FILE privilege reaches. It has three states, and MySQL spells them in the way most likely to be misread:

The server reportsIt meansWhat it looks like
NULL — the four-character stringfile I/O is switched off — the safest state"some value is set, so probably fine"
the empty valueno limit at all — the most dangerous state"nothing is set, so it does not matter"
a pathconfined to that directorycorrect

The two ends of the scale are spelled the opposite way round from the intuition. An is_null(), an empty() or a ?: 'unset' anywhere in a script that reads this variable swaps them — and reports the hardened server as the one worth ignoring.

What the unrestricted state actually allows

LOAD_FILE() reads any file the MySQL server process can read. SELECT … INTO OUTFILE writes one anywhere it can write. Both run as the server's operating-system user, not as the account connecting.

On its own that grants nobody anything — the operations need the FILE privilege, which SEC.PRIV.GRANT_FILE reports separately. Together they are the difference between a database problem and a problem on the host.

Why this is high and not critical

It is a multiplier on a privilege, not a privilege. Without an account holding FILE, an unrestricted secure_file_priv changes nothing. Reporting it at the top of the axis would put it beside findings that need nothing else to be true.

Two findings, not one

The grant and the setting are reported separately on purpose. They are fixed in different places by different people — one by whoever manages accounts, one by whoever manages the server — and either one alone is already worth acting on. Folding them into a single alarm would mean neither could be closed without the other.

What it looks like

# my.cnf: no limit at all on where FILE may read and write.
# (MySQL reports this state as the EMPTY value.)
secure_file_priv =

What to do about it

# Switch the operations off entirely. MySQL reports this as the string NULL.
secure_file_priv = NULL

# …or confine them to one directory, if an import job genuinely needs them.
secure_file_priv = /var/lib/mysql-files

A directory is a deliberate limit and is never reported. Neither is the disabled state. The only finding is the empty value — so this rule is silent on every server somebody has actually configured, and a data-import job using LOAD DATA INFILE keeps working with a directory set.

The variable is read-only at run time: it moves in my.cnf and takes a restart.