December 28, 2022·4 min read·engineeringsqlitesecuritycli

SQLite repairs the shell's safe-mode function checks

SQLite 3.40.1 corrects safe mode in the command-line shell so functions such as writefile are blocked. A SELECT statement can have filesystem effects when the host registers functions that perform them.

A SQL statement beginning with SELECT can still change a file if it invokes a function that writes one. The command-line shell includes useful functions whose effects extend outside the database. SQLite 3.40.1, released on December 28, fixes its --safe option so it correctly disallows functions such as writefile that can cause harmful side effects. The relevant restriction concerns the function's capability, independently of whether the statement looks like an ordinary query.

This is a shell behavior correction. It does not mean that every application linking SQLite automatically exposes the shell's file-writing functions. The host decides which functions to register and which authorization policy to apply. I value SQLite's embeddability, and this separation is part of why it can fit different applications. It also makes the host's integration choices part of the security boundary.

File-writing SQL is an intentional extension

The file I/O extension shipped with this release documents writefile(FILE, DATA) as writing a blob to a named file. With two arguments, success returns the number of bytes written and failure returns NULL. The function makes file export expressible inside SQL, which is useful in a command-line tool operating on local data.

Optional arguments expand its behavior. A mode argument can describe a regular file, a symbolic link, or a directory. For a regular file, the supplied data becomes the file contents. For a symbolic link, the data supplies the link target. A further modification-time argument sets the target's timestamp. With at least three arguments, errors raise an exception according to the extension's documentation.

Those are capabilities of the process running the shell. The SQL engine evaluates a function call, but the extension performs operations through the host environment. Calling that function from SELECT does not change the fact that it has filesystem effects. The safe-mode policy needs to evaluate those capabilities explicitly.

The same extension supplies readfile and a filesystem-directory virtual table. These illustrate why an interactive SQL shell can be more capable than a database-only query interface. It can integrate filesystem contents into queries and export results. A restricted execution mode must account for these conveniences, not only statements that modify database tables.

The authorizer sees functions during compilation

The shell source for 3.40.1 implements a safeModeAuth callback. Its prohibited-function list includes writefile, readfile, load_extension, and other shell-related functions. When the authorizer receives a function action, it compares the function name against that list without regard to case and invokes the safe-mode failure path for a prohibited match.

The callback also handles ATTACH. Its native-shell branch prevents that action in safe mode, while a separate conditional accounts for the virtual sandbox used by the WebAssembly shell build. The code makes the environment-specific distinction explicit instead of assuming that every build has the same filesystem consequences.

When opening a connection with persistent safe mode enabled, the shell registers the callback through sqlite3_set_authorizer. That places enforcement at the database connection, where SQL compilation can identify the operations being requested. The repair therefore belongs to how the shell applies its policy to executable SQL.

The public authorizer API documentation in the release explains that callbacks run while statements are compiled by the prepare interfaces. They receive an action code and contextual arguments. A host can allow an action, deny the statement, or use the documented ignore behavior where appropriate.

This mechanism supplies information that a text-prefix check would miss. A query's first keyword cannot describe the capabilities of every function it invokes. Compilation resolves the function operation, which gives the host an appropriate point to apply its restriction.

Safe mode and read-only data are different policies

A database opened read-only constrains changes to that database. A function writing an unrelated path performs a different operation. The file I/O extension demonstrates this distinction without requiring malformed SQL or damage to the database itself. Its operation is part of its documented purpose.

For the shell, a restricted mode therefore needs a policy covering actions outside the selected database as well. The corrected function checks are part of that policy. A function's presence in the process and permission to invoke it in a restricted session are separate decisions.

An ordinary safe-mode invocation can inspect data through permitted SQL, for example:

sqlite3 --safe example.db 'SELECT name FROM sqlite_schema ORDER BY name;'

This example uses the shell's restricted mode for schema inspection. It does not rely on the mode being a general-purpose operating-system sandbox. The specific correction in this release is that SQL functions with prohibited side effects are correctly blocked under that mode.

The public authorizer documentation also distinguishes rejection from substitution. Returning SQLITE_DENY rejects compilation with an error. For a read action, SQLITE_IGNORE can substitute NULL for a column value. These are different policy results, so a host using the API needs to choose the behavior appropriate to the operation being restricted.

The host defines the effective SQL environment

The authorizer documentation describes applications that accept user-provided queries while restricting access or modifications. It also points such applications toward resource limits. Authorization determines which operations may be compiled, while limits address how much work or storage permitted operations may consume. Neither can be inferred from the fact that the engine supports SQL.

This separation is particularly useful when understanding command-line tools. A shell deliberately combines database operations with local conveniences. An application embedding only the engine can expose a different set of functions and a different policy. The release fixes the shell's safe-mode implementation rather than imposing one universal policy on every host.

I consider the function checks the substantive point of this patch. The SQL text expresses an operation within an environment defined by its host, and that environment can include filesystem access. In 3.40.1, the shell correctly applies its safe-mode restriction to those function calls, allowing the policy to reflect what the invoked code can actually do.