# Risk map — sqlite/sqlite

> Canonical page: https://codevetter.com/unpack/sqlite/risk-map

# Risk map — sqlite/sqlite

Scanned commit fde3a84dddf35f4c19797c8afad04a44c596f4d8 (2026-09-21). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

The core library is one of the most heavily tested C codebases in existence, so the interesting risk is not 'is this correct' but 'where does trust boundary crossing happen'. The sharpest edges are: the CLI, which by design shells out and touches the filesystem; `sqlite3_load_extension`, which `dlopen`s attacker-suppliable paths; the schema itself, which is executable input (the `trusted-schema`/`defensive` model exists precisely because of this); and `sqlite3_rsync`, which `fork`/`execl`s a shell. Structurally, the blast radius is concentrated in a handful of enormous files — `btree.c`, `select.c`, `vdbe.c`, `pager.c`, `where.c` are each 300–400KB — and in generated-file drift, which `srctree-check` only partially guards. Note that AGENTS.md explicitly states the project does not accept agentic code, which is itself a process constraint on any change made here.
- The shell has an explicit unsafe-operation surface gated only by `bSafeMode`: `failIfSafeMode()` guards `.archive`, `.cd`, `.clone`, `.import`, `.load`, `.read`, `.restore` and shell-out, and `safeModeAuth()` blocks the functions `edit`, `fts3_tokenizer`, `load_extension`, `readfile`, `realpath`, `writefile`, `zipfile`, `zipfile_cds` plus `ATTACH`. — [src/shell.c.in#L2366-L2375](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L2366-L2375) [src/shell.c.in#L2789-L2832](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L2789-L2832) [src/shell.c.in#L10038-L11612](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L10038-L11612)
- Safe mode is implemented as an authorizer callback, so it is only active when `SQLITE_OMIT_AUTHORIZATION` is not defined — the whole `safeModeAuth`/`shellAuth` block is inside `#ifndef SQLITE_OMIT_AUTHORIZATION`, meaning a build without the authorizer silently loses these protections. — [src/shell.c.in#L2785-L2875](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L2785-L2875)
- The CLI calls `system()` directly in at least two places (the `edit()` SQL function at `src/shell.c.in:2505` and the `.shell`/`.system` dot-command at `:6045`) and `sqlite3_popen()` in three more (`.import` from a pipe, output redirection to a pipe, and `.read` from a pipe), all constructing command strings from user input. — [src/shell.c.in#L2435-L2510](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L2435-L2510) [src/shell.c.in#L6040-L6050](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L6040-L6050) [src/shell.c.in#L8367](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L8367) [src/shell.c.in#L9553](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L9553) [src/shell.c.in#L11572](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L11572)
- `open_db()` unconditionally calls `sqlite3_enable_load_extension(p->db, 1)` on every CLI connection, re-enabling a capability the library deliberately disables by default; the `.load` dot-command is then only stopped by safe mode. — [src/shell.c.in#L5153-L5156](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L5153-L5156) [src/loadext.c#L592-L604](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/loadext.c#L592-L604) [src/shell.c.in#L11119](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L11119)
- `sqlite3LoadExtension()` passes the caller-supplied path to `sqlite3OsDlOpen` and, on failure, retries with platform suffixes (`.so`/`.dylib`/`.dll`) appended — the only guards are the `SQLITE_LoadExtension` flag, a zero-length-name rejection (to prevent linking the running binary), and a `SQLITE_MAX_PATHLEN` cap added because some `dlopen()` implementations segfault on oversize names. — [src/loadext.c#L561-L632](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/loadext.c#L561-L632)
- The schema is an execution surface: `doc/trusted-schema.md` documents that an attacker who modifies a database's schema (swapping a table for a view, adding triggers, CHECK constraints, generated columns or index expressions) can get code to run in a high-privilege reader's process, which is why functions carry Innocuous/Normal/Direct-Only risk levels and `TRUSTED_SCHEMA` defaults to *on* for backwards compatibility. — [doc/trusted-schema.md#L1-L50](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/doc/trusted-schema.md#L1-L50) [src/main.c#L1013-L1022](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/main.c#L1013-L1022)
- The CLI ties both hardening knobs to one flag: `open_db` sets `SQLITE_DBCONFIG_TRUSTED_SCHEMA` to the value of `--unsafe-testing` and `SQLITE_DBCONFIG_DEFENSIVE` to its inverse, so enabling testing mode disables defensive mode in the same breath. — [src/shell.c.in#L5147-L5152](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L5147-L5152)
- `ext/misc/fileio.c` provides `readfile()`/`writefile()` SQL functions that do arbitrary filesystem I/O; they are registered with `SQLITE_DIRECTONLY` (blocking use from triggers/views/schema) but are plain callable functions from top-level SQL, and the shell compiles them in by default. — [ext/misc/fileio.c#L264](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/ext/misc/fileio.c#L264) [ext/misc/fileio.c#L538](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/ext/misc/fileio.c#L538) [ext/misc/fileio.c#L1275-L1281](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/ext/misc/fileio.c#L1275-L1281) [src/shell.c.in#L5173](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L5173)
- `tool/sqlite3_rsync.c` embeds a `popen2()` implementation copied from Fossil that `fork()`s and `execl()`s either the command directly or `/bin/sh -c zCmd`, building the command from a user-supplied `--ssh` program name and a `USER@HOST:PATH` argument — a shell-injection-shaped surface in a network-facing tool. — [tool/sqlite3_rsync.c#L106-L354](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/tool/sqlite3_rsync.c#L106-L354) [tool/sqlite3_rsync.c#L2077-L2300](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/tool/sqlite3_rsync.c#L2077-L2300)
- Blast radius is concentrated in five files that dominate the tree: `btree.c` (408KB), `select.c` (337KB), `vdbe.c` (325KB), `pager.c` (305KB), `where.c` (298KB) — any edit in these touches the hot path for essentially every query. — [src/btree.c](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/btree.c) [src/select.c](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/select.c) [src/vdbe.c](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/vdbe.c) [src/pager.c](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/pager.c) [src/where.c](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/where.c)
- Generated files are a standing hazard: `sqlite3.h`, `parse.c`/`parse.h`, `opcodes.h`/`opcodes.c`, `keywordhash.h`, `pragma.h` and `sqlite3.c` must never be hand-edited, and `tool/srctree-check.tcl` only verifies two things (Makefile.msc sync and VERSION vs tea/configure.ac) — it does not detect a hand-edited `pragma.h` or `parse.c`. — [AGENTS.md](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/AGENTS.md) [tool/srctree-check.tcl#L1-L45](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/tool/srctree-check.tcl#L1-L45) [tool/mkpragmatab.tcl#L1-L20](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/tool/mkpragmatab.tcl#L1-L20)
- Opcode numbering is implicitly coupled to source formatting: `tool/mkopcodeh.tcl` derives opcode numbers by scanning `src/vdbe.c` for lines beginning with `case OP_`, so a reformatting or reordering of those 190 case labels silently renumbers the bytecode. — [src/vdbe.c#L1080-L1090](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/vdbe.c#L1080-L1090) [tool/mkopcodeh.tcl](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/tool/mkopcodeh.tcl) [AGENTS.md](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/AGENTS.md)
- The unix VFS carries a large matrix of platform-conditional locking styles (`posixIoFinder`, `dotlockIoFinder`, `flockIoFinder`, `semIoFinder`, `afpIoFinder`, `nfsIoFinder`, `proxyIoFinder`, `vxworksIoFinder`) selected by `autolockIoFinder`; most of these paths cannot be exercised on any single developer machine. — [src/os_unix.c#L5842-L5945](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/os_unix.c#L5842-L5945) [src/os_unix.c#L8497-L8536](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/os_unix.c#L8497-L8536)
- The shell reads and executes `~/.sqliterc` through `process_input` at startup unless `-noinit` is passed, so a writable home directory is a code-execution vector into any subsequent `sqlite3` invocation. — [src/shell.c.in#L13745-L13755](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L13745-L13755)
- There is a process risk distinct from a code risk: AGENTS.md states SQLite does not accept agentic code and does not accept pull requests without prior legal agreement, because a copyrightable contribution would break the public-domain status of the whole tree. — [AGENTS.md](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/AGENTS.md) [README.md#L44-L56](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/README.md#L44-L56) [LICENSE.md](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/LICENSE.md)
- `main_init()` puts the CLI in `SQLITE_CONFIG_MULTITHREAD` rather than serialized mode, so the shell's own connection is not safe to share across threads — relevant for anyone embedding or extending `shell.c.in`. (inferred) — [src/shell.c.in#L13878-L13884](https://github.com/sqlite/sqlite/blob/fde3a84dddf35f4c19797c8afad04a44c596f4d8/src/shell.c.in#L13878-L13884)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
