–safe Flag Bypass with readfile()/writefile() in SQLite CLI
Issue Overview: Unexpected File Access Permissions with –safe Flag in SQLite CLI
The SQLite command-line interface (CLI) includes a --safe
flag designed to restrict access to functions and commands that could modify the host system or leak sensitive data. According to official documentation, enabling --safe
should disable SQL functions such as readfile()
, writefile()
, edit()
, fts3_tokenizer()
, and load_extension()
, along with blocking shell command execution via .shell
or .system
. However, users have observed that even with --safe
enabled, readfile()
and writefile()
remain operational, allowing file system read/write operations. This behavior contradicts the documented security guarantees, creating confusion and potential security risks for users relying on --safe
to sandbox untrusted scripts.
For example, running the following commands in SQLite 3.40.0 with --safe
enabled demonstrates the issue:
sqlite3 --safe ":memory:" "SELECT writefile('secret.txt', randomblob(5))" # Creates 'secret.txt'
sqlite3 --safe ":memory:" "SELECT readfile('secret.txt')" # Outputs file contents
The .shell
command is correctly blocked in safe mode, but writefile()
and readfile()
bypass the restriction. This discrepancy suggests either a documentation error or a flaw in the CLI’s implementation of the --safe
flag. The behavior persists across platforms, indicating it is not OS-specific.
Possible Causes: Incomplete Function Disabling and Security Model Ambiguity
1. CLI Implementation Oversight in Function Restriction
The SQLite CLI’s --safe
mode operates by disabling specific functions and commands at runtime. However, the mechanism responsible for disabling readfile()
and writefile()
might have been incomplete or omitted in certain SQLite versions. For instance, prior to the fix in SQLite trunk (post-3.40.0), the sqlite3_set_authorizer
function—used to enforce security policies—might not have been configured to block these file I/O functions when --safe
is active. This would explain why .shell
is blocked (its restriction is handled separately) while readfile()
/writefile()
remain accessible.
2. Documentation Ambiguity Regarding Security Scope
The documentation states that --safe
disables "SQL functions that have potentially harmful side-effects," but it does not explicitly define "harmful." One interpretation is that --safe
aims to prevent modification of the host environment (e.g., writing files, executing shell commands). Under this view, readfile()
might be considered less critical since it does not modify data. However, the documentation explicitly lists readfile()
as disabled, creating an expectation that it should be blocked regardless of its read/write nature. This mismatch between intent and specification contributes to confusion.
3. Security Context and Threat Model Assumptions
The --safe
flag is designed for scenarios where users run untrusted CLI scripts. Its primary goal is to prevent scripts from altering the host system (e.g., deleting files, installing malware). However, readfile()
could still expose sensitive data if an attacker crafts a script to read /etc/passwd
or other confidential files. The original design might have underestimated the risk of read operations, focusing instead on write operations. This oversight becomes critical when users assume --safe
provides comprehensive isolation.
4. Privilege Escalation and CVE-2022-46908 Implications
The ability to bypass --safe
restrictions led to the assignment of CVE-2022-46908, classifying it as a security vulnerability. While SQLite’s developers argue that the risk is limited to users who run untrusted CLI scripts (a rare practice), the CVE highlights the potential for misuse. For example, a malicious script could use writefile()
to overwrite configuration files or readfile()
to exfiltrate credentials, depending on the user’s permissions. The vulnerability’s severity depends on the environment: a user with limited file system access poses less risk than one with administrative privileges.
Troubleshooting Steps, Solutions & Fixes: Mitigating the –safe Bypass
1. Verify SQLite Version and Functionality
First, confirm the SQLite CLI version and test whether readfile()
/writefile()
are restricted:
sqlite3 --version # Check for versions ≥3.40.0 (fixed in trunk)
sqlite3 --safe ":memory:" "SELECT writefile('test.txt', 'demo'); SELECT readfile('test.txt');"
If the commands succeed, the --safe
restriction is not fully enforced.
2. Apply the Official Patch or Compile from Trunk
The SQLite development team addressed this issue in the trunk version (post-3.40.0). To apply the fix:
- Download the latest source code from SQLite’s Fossil repository.
- Compile the CLI with the patched authorization logic:
./configure && make sqlite3
- Replace the existing
sqlite3
binary with the newly compiled version.
3. Use Workarounds for Unpatched Versions
If updating is not feasible, mitigate the risk by:
- Revoking File System Permissions: Run the SQLite CLI with a restricted user account that has read/write access only to necessary directories.
- Sandboxing: Use containerization (e.g., Docker) or OS-level sandboxing (e.g., Firejail) to isolate the CLI process.
- Input Validation: Scrub untrusted scripts to remove
readfile()
/writefile()
calls before execution.
4. Re-evaluate Threat Model and Documentation
Understand the intended scope of --safe
:
- It blocks modification primitives (
.shell
,writefile()
) but historically allowed read-only functions. - The documentation’s inclusion of
readfile()
as a restricted function was incorrect prior to the patch. Adjust expectations accordingly.
5. Address CVE-2022-46908 in Security Policies
If your organization tracks CVEs:
- Determine whether your workflow involves running untrusted CLI scripts with
--safe
. If not, the CVE’s applicability is limited. - For high-risk environments, enforce the use of patched SQLite versions or alternative isolation mechanisms.
6. Monitor for Official Releases
SQLite releases are infrequent and depend on thorough testing. Watch for version 3.41.0 or newer, which will include the fix. Until then, the trunk remains the authoritative source for the resolution.
7. Clarify Documentation and User Guidance
After applying fixes, review the updated documentation to ensure consistency. The --safe
flag should now correctly block both readfile()
and writefile()
, aligning with the stated behavior. Educate users on the updated security guarantees and recommend against executing untrusted scripts without additional safeguards.
By systematically addressing implementation flaws, updating dependencies, and refining security practices, users can restore the intended protections of the --safe
flag and prevent unintended file system access via SQLite CLI.