Skip to content
🔍 Seeking a 6-month software engineering internship starting January 2027. Based in France, open to remote or relocation.
contact@julienc.meResume

Sandboxing

When anyquery is exposed to clients, as a MySQL server or as an LLM endpoint (gpt/mcp), those clients can run arbitrary SQL. Anyquery’s built-in features make arbitrary SQL powerful: the read_* table functions read files, several of them fetch remote URLs, ATTACH DATABASE writes files, and a few scalar functions read files or delete cache directories. On a server, that turns into local file read, server-side request forgery (SSRF), and arbitrary file write for anyone who can reach the port.

The sandbox closes those doors. It is a policy attached to the database namespace that confines file access to an explicit set of directories, blocks remote fetches, and denies the dangerous SQL statements and functions.

The default depends on the command, because the exposure differs:

Command Sandbox by default How to change it
anyquery server On --no-sandbox to disable, or --dev (see below)
anyquery gpt On (exposes an internet tunnel by default) --no-sandbox to disable
anyquery mcp Auto: on when network-exposed (--tunnel, or a non-loopback --host); off for plain localhost/--stdio --sandbox to force on, --sandbox=false to force off
anyquery query / interactive shell Off (local use is trusted) --sandbox to opt in

CLI mode is meant for local data analysis and is not an attack surface, so it is unrestricted by default. You can still opt in with --sandbox to mirror the server’s behaviour.

When active, the sandbox enforces the following. The default is deny everything, then you relax it with the flags below.

  • File reads: the read_* table functions (read_csv, read_json, read_parquet, read_yaml, read_toml, read_jsonl, read_html, read_log) may only read files inside the directories you list with --allow-dirs. The confinement is enforced by the operating system, and a symlink inside an allowed directory cannot be used to escape it.
  • Remote fetches: fetching http and https URLs is disabled unless you pass --allow-remote. S3 and GCS URLs are not supported; query a presigned HTTPS URL instead (see Querying files).
  • stdin: denied under the sandbox, in every command (read_csv('stdin'), -, /dev/stdin), regardless of --allow-dirs or --allow-remote.
  • Database readers: the duckdb_reader, postgres_reader, mysql_reader, clickhouse_reader and cassandra_reader modules are not registered at all (they take arbitrary connection strings, and DuckDB can itself read local files and load extensions). CREATE VIRTUAL TABLE … USING duckdb_reader(...) fails with no such module unless you pass --allow-db-connections.
  • ATTACH DATABASE / VACUUM … INTO: both are arbitrary-file-write primitives. In-memory databases (:memory:, mode=memory) are always allowed; writing to disk is denied unless you pass --allow-attach, and even then it is confined to --allow-dirs. The ATTACH confinement is slightly weaker than the read_* one (SQLite opens the target itself): someone who can already write inside an allowed directory could race the check with a symlink.
  • Blocked SQL functions: see below.
  • Restricted PRAGMAs: see below.

The default configuration is intentionally strict: no readable directories, no remote access, no database connections. Open up only what you need.

Allow read_* tables to read two directories
anyquery server --allow-dirs /var/data,/srv/exports
Allow remote fetches (http/https)
anyquery server --allow-dirs /var/data --allow-remote
Allow ATTACH/VACUUM INTO to disk (still confined to --allow-dirs)
anyquery server --allow-dirs /var/data --allow-attach
Re-enable the database reader modules
anyquery server --allow-db-connections
Flag Effect
--allow-dirs <dir,dir> Directories the read_* tables (and on-disk ATTACH) may access. Repeatable / comma-separated. Empty by default.
--allow-remote Allow read_* tables to fetch remote URLs.
--allow-attach Allow ATTACH DATABASE / VACUUM … INTO to on-disk paths within --allow-dirs.
--allow-db-connections Register the duckdb_reader/postgres_reader/… modules.

With --allow-remote, share links (GitHub, GitLab, Codeberg, Hugging Face, Google Sheets, Dropbox; see Querying files) are rewritten to their raw-file form before fetching. Operators who want to know the egress surface up front should therefore expect outbound requests to any host a query names directly, plus:

raw.githubusercontent.com, gist.githubusercontent.com, huggingface.co,
docs.google.com, www.dropbox.com, gitlab.com, www.gitlab.com, codeberg.org

A handful of scalar functions read files or delete directories on disk. When the sandbox is active they are denied outright by the SQLite authorizer, and they cannot be relaxed with --allow-dirs:

Function Why it is blocked
load_file, load_file_bytes Read an arbitrary file into a value (a local-file-read bypass of the read_* confinement).
clear_plugin_cache, clear_file_cache Delete cache directories on disk (cache management is an operator action, not a client one).
load_extension Loading a SQLite extension is remote code execution. The SQL function is disabled by the driver, and the sandbox denies it explicitly as defence in depth.
Blocked under the sandbox
SELECT load_file('/etc/passwd'); -- error: not authorized

To read a file inside an allowed directory, use a read_* table function instead of load_file: those are permitted within --allow-dirs:

Allowed when /var/data is in --allow-dirs
SELECT * FROM read_csv('/var/data/report.csv');

PRAGMA is gated to a read-only allowlist. Only schema-introspection pragmas that the engine, the MySQL protocol handler and the information_schema/SHOW emulation rely on are permitted:

table_info, table_xinfo, table_list, index_info, index_xinfo, index_list,
foreign_key_list, database_list, collation_list, function_list, module_list,
pragma_list, compile_options

Every other PRAGMA is denied. This blocks schema-corruption vectors (PRAGMA writable_schema=ON followed by UPDATE sqlite_master) and memory-inflation pragmas (PRAGMA cache_size, PRAGMA mmap_size).

Blocked under the sandbox
PRAGMA writable_schema=ON; -- error: not authorized

The function deny-list and the PRAGMA allowlist are part of the sandbox and cannot be relaxed individually. If you genuinely need load_file, an arbitrary PRAGMA, or the database readers without restriction, you must turn the sandbox off entirely. Only do this on a trusted, non-exposed deployment:

Disable the sandbox (UNSAFE on an exposed port)
anyquery server --no-sandbox
MCP exposed via a tunnel, but sandbox explicitly forced off
anyquery mcp --tunnel --sandbox=false

--dev registers the developer UDFs (load_dev_plugin, reload_dev_plugin, unload_dev_plugin, see Creating a plugin) that read a manifest file from an arbitrary path, run its build_command, and write to its log_file, none of which go through the sandbox’s file-access policy. Rather than requiring two flags to get a safe developer server, --dev implies --no-sandbox: on anyquery server, passing --dev disables the sandbox entirely, regardless of --allow-dirs, --allow-remote, --allow-attach, --allow-db-connections, or even an explicit --no-sandbox=false.

server --dev: sandbox is OFF, logged loudly
anyquery server --dev
# WARN Server sandboxing is DISABLED (--dev): developer mode always disables the
# sandbox, because load_dev_plugin/reload_dev_plugin/unload_dev_plugin read
# arbitrary files, exec build_command, and write log_file with no policy
# check. Clients can also read local files, reach internal endpoints, and
# write arbitrary files. Do not expose this server to a network; keep
# --host on loopback (the default) and do not run --dev in production.

anyquery query / the interactive shell are unsandboxed by default already, so --dev doesn’t change their sandbox posture. If you do pass --dev --sandbox together there, the sandbox stays on but the dev UDFs are withheld (they are gated on the sandbox being off, not just on --dev). Pass --dev without --sandbox to use them.

CLI mode is unrestricted by default. Pass --sandbox to apply the same policy, which is useful when running untrusted SQL locally, or to reproduce the server’s behaviour:

Run a query under the sandbox
anyquery query --sandbox --allow-dirs /var/data -q "SELECT * FROM read_csv('/var/data/report.csv')"

Without --allow-dirs, a sandboxed query cannot read any file:

Terminal window
anyquery query --sandbox -q "SELECT * FROM read_csv('/etc/passwd')"
# error: sandbox: access to "/etc/passwd" is not allowed; permitted directories: []