Back to blog
Best PracticesCloud SecurityDatabasesGCPOperations & Compliance

MySQL local_infile Enabled on GCP Cloud SQL: Risk and Remediation

Learn why an enabled local_infile flag on GCP Cloud SQL MySQL is a security risk, plus step-by-step gcloud, Terraform, and policy-as-code fixes.

TL;DR

The local_infile flag on a Cloud SQL MySQL instance lets clients load data from their local filesystem using LOAD DATA LOCAL INFILE, which opens the door to file read attacks and data exfiltration through compromised or malicious clients. Disable it by setting the local_infile database flag to off unless you have a specific, controlled need for it.

If you run MySQL on GCP Cloud SQL, the local_infile setting is one of those defaults that rarely gets a second look. It exists to make bulk data loading convenient, but that same convenience is what makes it a liability. This Lensix check flags any Cloud SQL MySQL instance where local_infile is turned on so you can decide whether you actually need it.


What this check detects

The sql_mysqllocalinfile check inspects the database flags on your GCP Cloud SQL MySQL instances and reports any instance where the local_infile flag is set to on.

The local_infile system variable controls whether MySQL allows the LOCAL keyword in LOAD DATA INFILE statements. When enabled, a connected client can tell the server to read a file from the client machine and stream its contents into a table. The key detail is that file access happens on the client side, driven by the server's response, which is exactly where the trust problem starts.

Note: LOAD DATA INFILE (without LOCAL) reads files from the database server's filesystem and requires the FILE privilege. LOAD DATA LOCAL INFILE reads from the client's filesystem and bypasses that privilege check, which is why local_infile is governed separately.


Why it matters

On a managed service like Cloud SQL you don't control the server's filesystem, so the classic "read /etc/passwd off the database host" scenario is less of a concern. The real risk lives on the client side and in your application layer.

Malicious or spoofed server reads client files

The MySQL protocol lets the server request any file from the client during a LOAD DATA LOCAL exchange. A client connecting to a rogue or compromised MySQL endpoint can be instructed to send arbitrary files back, such as SSH keys, application secrets, or config files. If an attacker can position themselves between your app and a database, or trick a client into connecting to their server, local_infile on the client side becomes a file exfiltration channel.

SQL injection turns into data exfiltration

This is the scenario that bites most teams. If an application has a SQL injection vulnerability and the connection allows LOAD DATA LOCAL INFILE, an attacker can use it to pull files off the application server hosting the database client. A bug that might otherwise leak a few rows now becomes a way to read credentials, environment files, and other secrets from disk.

Warning: Leaving local_infile enabled widens the blast radius of any SQL injection flaw in your application. It converts a read-only injection into a filesystem read on whatever host runs your database client.

Compliance pressure

Hardening benchmarks, including the CIS Google Cloud Platform Foundations Benchmark, specifically call out local_infile for MySQL and recommend it be set to off. If you're audited against CIS, PCI DSS, or similar frameworks, an enabled flag here will show up as a finding.


How to fix it

The fix is to set the local_infile database flag to off on the affected instance. Pick the method that matches how you manage infrastructure.

Option 1: gcloud CLI

Warning: Changing a database flag on Cloud SQL may require an instance restart, which can interrupt active connections. Schedule the change during a maintenance window or expect a brief failover for HA instances.

First confirm the current value:

gcloud sql instances describe INSTANCE_NAME \
  --format="value(settings.databaseFlags)"

Then set the flag to off:

gcloud sql instances patch INSTANCE_NAME \
  --database-flags=local_infile=off

Danger: The --database-flags argument replaces the entire set of existing flags, it does not merge. If your instance has other flags configured, list them all in a single comma-separated command or you will silently wipe them.

If your instance already has other flags, include them so you don't lose them:

gcloud sql instances patch INSTANCE_NAME \
  --database-flags=local_infile=off,slow_query_log=on,long_query_time=2

Option 2: Google Cloud Console

  1. Open SQL and select the MySQL instance.
  2. Click Edit.
  3. Expand Flags.
  4. Find local_infile and set its value to Off, or add the flag if it isn't listed.
  5. Click Save and confirm any restart prompt.

Option 3: Terraform

resource "google_sql_database_instance" "mysql" {
  name             = "my-mysql-instance"
  database_version = "MYSQL_8_0"
  region           = "us-central1"

  settings {
    tier = "db-n1-standard-1"

    database_flags {
      name  = "local_infile"
      value = "off"
    }
  }
}

Tip: In Terraform, every database_flags block you declare becomes the full desired state. Define all your flags as separate database_flags blocks in the same resource so a plan doesn't accidentally drop ones you set manually.

Verify the change

Connect to the instance and confirm the runtime value:

SHOW VARIABLES LIKE 'local_infile';

You want to see local_infile | OFF.


How to prevent it from happening again

Disabling the flag on one instance is a point fix. To keep it off across your fleet, push the control left into the tooling that creates instances.

Policy-as-code with OPA / Conftest

Gate Terraform plans in CI so an instance can't ship with local_infile enabled:

package cloudsql

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "google_sql_database_instance"
  flag := resource.change.after.settings[_].database_flags[_]
  flag.name == "local_infile"
  flag.value != "off"
  msg := sprintf("Cloud SQL instance '%s' must set local_infile to off", [resource.address])
}

Organization policy and templates

Bake the correct flag into a shared Terraform module so teams provisioning MySQL inherit the safe default without thinking about it. A module that exposes a curated set of flags is far easier to govern than letting each team hand-roll instance config.

Tip: Run the Lensix sql_mysqllocalinfile check on a schedule rather than only at deploy time. Flags can be changed out of band through the console or a quick gcloud patch, and continuous scanning catches that drift before it lingers.


Best practices

  • Default to off. Most applications never use LOAD DATA LOCAL INFILE. Turn it on only when a specific import workflow requires it, and turn it back off afterward.
  • Disable it client-side too. Even with the server flag off, set local_infile to a safe value in your application's MySQL client or driver configuration so a rogue server can't request files. For the official MySQL client, avoid the --local-infile=1 option.
  • Use parameterized queries. The injection path is what makes this flag dangerous. Eliminate SQL injection at the source with prepared statements and an ORM that escapes input properly.
  • Prefer Cloud SQL native import. For bulk loads, use gcloud sql import from a Cloud Storage bucket instead of LOAD DATA LOCAL INFILE. It keeps data movement inside Google's control plane and avoids touching the client flag entirely.
  • Audit all database flags regularly. local_infile is one of several flags worth reviewing alongside skip_show_database, local_infile, and logging settings. Treat the full flag set as part of your security baseline.

The pattern here is common across cloud databases: a convenience feature that's harmless in isolation becomes a serious risk when paired with a vulnerable application. Turning local_infile off costs you nothing in most environments and removes a real escalation path. Set it, enforce it in CI, and scan for drift.