How go-sql-driver/mysql Secures LOAD DATA LOCAL INFILE Requests
The go-sql-driver/mysql implements a deny-by-default policy for LOAD DATA LOCAL INFILE that requires explicit file registration, supports secure reader abstractions, and optionally allows all files via a DSN parameter.
The go-sql-driver/mysql package provides a pure Go driver for MySQL that must carefully handle the LOAD DATA LOCAL INFILE command, which instructs the client to read local files and send them to the server. Without proper safeguards, this feature could allow a malicious server to exfiltrate arbitrary files from the client machine. The driver mitigates this risk through a three-layer security model defined in infile.go and dsn.go that puts explicit control in the hands of the application developer.
Three-Layer Security Model
File Allow-List Registration
The primary defense mechanism is the file allow-list managed through the RegisterLocalFile and DeregisterLocalFile functions. The driver maintains an internal fileRegister map (lines 36-52 in infile.go) that stores absolute paths of files explicitly permitted for upload.
When the MySQL server requests a local file, the driver's okHandler.handleInFileRequest method checks this map under a read lock. If the file path is not present in the registry, the request is rejected with a clear error: "local file '<path>' is not registered".
Secure Reader Handlers
For scenarios where data does not exist as a static file on disk, the driver supports reader handlers via RegisterReaderHandler and DeregisterReaderHandler (lines 68-78 in infile.go). Applications register named handler functions that return io.Reader implementations.
To use a registered reader, the SQL statement must reference it with the Reader::<name> prefix. This abstraction prevents direct filesystem access while allowing streaming from memory, network sources, or compressed archives.
DSN-Level Override
The allowAllFiles parameter in the Data Source Name (DSN) provides a global override. When set to true in Config.AllowAllFiles (defined in dsn.go lines 62-69 and parsed at lines 84-88), it disables the allow-list check and permits any local file to be sent.
This setting defaults to false, enforcing the principle of least privilege. It should only be enabled in controlled environments where the server is fully trusted.
Request Handling and Enforcement Logic
The enforcement logic resides in okHandler.handleInFileRequest in infile.go (lines 96-132). When the server issues a LOCAL INFILE request, the handler executes three distinct checks:
-
Reader Path Detection: If the requested name begins with
Reader::(optionally prefixed with a slash), the handler treats it as a reader request and looks up the name in thereaderRegistermap. -
File Path Validation: For standard file paths, the handler trims surrounding quotes and checks the
fileRegistermap. Only ifcfg.AllowAllFilesevaluates to true will the driver proceed with unregistered files. -
Resource Opening: Valid files are opened with
os.Open, while valid readers are invoked to return anio.Reader. The driver then streams data in chunks respecting MySQL packet size limits.
Thread Safety and Concurrency Protection
All registration structures are protected by sync.RWMutex to ensure safe concurrent access. The fileRegister and readerRegister maps are effectively read-only during request handling, preventing race conditions that could inadvertently expose files during concurrent connection operations.
Practical Implementation Examples
Registering a Specific File
// Register the file before opening the connection
mysql.RegisterLocalFile("/home/gopher/data.csv")
db, _ := sql.Open("mysql", "user:pw@tcp(127.0.0.1:3306)/mydb")
_, err := db.Exec("LOAD DATA LOCAL INFILE '/home/gopher/data.csv' INTO TABLE mytable")
Using a Custom Reader
mysql.RegisterReaderHandler("csvdata", func() io.Reader {
// Could be a gzip.Reader, bytes.Buffer, etc.
return strings.NewReader("col1,col2\nval1,val2\n")
})
// Reference with Reader:: prefix
_, err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::csvdata' INTO TABLE mytable")
Allowing All Files via DSN
// Use with extreme caution - disables allow-list protection
dsn := "user:pw@tcp(127.0.0.1:3306)/mydb?allowAllFiles=true"
db, _ := sql.Open("mysql", dsn)
_, err := db.Exec("LOAD DATA LOCAL INFILE '/etc/passwd' INTO TABLE mytable")
Summary
- Deny-by-default: No local files can be transmitted unless explicitly registered via
RegisterLocalFileorallowAllFilesis enabled in the DSN. - Explicit registration: The
fileRegistermap ininfile.gomaintains the allow-list of absolute paths validated byhandleInFileRequest. - Reader abstraction:
RegisterReaderHandlerenables secure data streaming without direct filesystem access using theReader::prefix. - DSN control: The
allowAllFilesparameter inConfig.AllowAllFiles(dsn.golines 62-69) provides a global toggle that defaults to securefalse. - Thread safety:
sync.RWMutexprotects registration maps during concurrent access across multiple connections. - Clear errors: Unregistered resources produce explicit error messages indicating whether a local file or reader is not registered.
Frequently Asked Questions
What happens if I try to load a file that hasn't been registered?
The driver rejects the request and returns an error stating "local file '<path>' is not registered". This occurs in okHandler.handleInFileRequest in infile.go when the path is missing from the fileRegister map and the AllowAllFiles configuration is false.
Can I use wildcards or directories in RegisterLocalFile?
No, RegisterLocalFile requires explicit absolute file paths. The driver does not support directory traversal or pattern matching in the allow-list; each file must be registered individually to maintain strict control over what data can be exfiltrated from the client.
Is the allowAllFiles setting secure for production use?
Generally no. According to the source code in dsn.go, allowAllFiles=true disables the primary security mechanism and should only be used when connecting to fully trusted servers. In production environments, prefer explicit file registration or reader handlers to minimize attack surface.
How do I stream data from memory instead of disk?
Register a reader handler using RegisterReaderHandler with a unique name, then reference it in your SQL with the Reader::name syntax. The handler must return an io.Reader, allowing you to stream from bytes.Buffer, strings.Reader, or custom implementations without the driver touching the filesystem.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →