sqlite-extension-load

SQLite: no loading of extensions

shipped

The sections below are generated from the CLI (methodology 0.3-draft, CLI 0.3.0); the same text is printed by sealmark explain sqlite-extension-load.

pack
sqlite
weight
critical
scope
server (tested through its SQL tool against a seeded SQLite file; the engine itself is not what is graded; SQLite only in this release, DuckDB is planned); decided from the error text, not from observed code execution
decided by
response
since
0.2-draft (methodology 0.3-draft)
config keys
checks.sqlite-extension-load.corpus.addchecks.sqlite-extension-load.corpus.removechecks.sqlite-extension-load.enabledchecks.sqlite-extension-load.severity

Probes

  • load-extension SELECT load_extension('{canary}/sm-ext')
  • load-extension-writer-insert INSERT INTO probe_t (id, v) VALUES (9100, load_extension('{canary}/sm-ext')) (a declared writer whose answer to the SELECT form said nothing about loading)
  • load-extension-writer-update UPDATE probe_t SET v = load_extension('{canary}/sm-ext') WHERE id = 9010 (as above, when the INSERT form was also refused without a word about loading)
  • load-extension-reader-where SELECT 1 WHERE load_extension('{canary}/sm-ext') IS NULL (a reader whose answer to the SELECT form said nothing about loading)
  • load LOAD '{canary}/sm.duckdb_extension' (DuckDB; planned, not run in this release)
  • install INSTALL sm_nonexistent (DuckDB; planned, not run in this release)

Settings and thresholds

Every check also takes checks.sqlite-extension-load.enabled and checks.sqlite-extension-load.severity; they are described once in the configuration reference.

This check has no numeric threshold.

KeyDefaultStandardMeaning
checks.sqlite-extension-load.corpus.add[]lockedExtra probes for sqlite-extension-load, validated against the pack's corpus schema. Appends across layers. Any addition makes the run CUSTOM.
checks.sqlite-extension-load.corpus.remove[]lockedProbe ids or exact strings to drop from the sqlite-extension-load corpus. Appends across layers. Any removal makes the run CUSTOM.

How the verdict is established

  • Scoring. critical and scored: pass = 1 point, part = 0.5, fail = 0. A fail caps the pack's grade at D, whatever the other checks scored.

What to fix

Disable extension loading on every connection, the writer's as well as the reader's (do not enable SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION or call load_extension), and make the refusal say so ("not authorized", "extension loading is disabled"). The probe is sent in the form the tool accepts: a SELECT for a reader, an INSERT and then an UPDATE for a declared writer. A refusal of the form itself ("only INSERT is allowed") says nothing about extension loading, so a server that only refuses forms is inconclusive, not passed.

False positives

The verdict is read from the engine's own words, never from the fact that the call failed. A generic refusal ("SELECT queries are not allowed") is not a denial of loading, because the connection behind it may have loading enabled: the tool is then sent a form its role accepts (an INSERT or UPDATE for a writer, a SELECT with a WHERE for a reader). Only wording about loading counts as a denial (not authorized, load_extension disabled, no such function: load_extension, or not allowed, disabled, forbidden or unsupported next to the word extension or load). Wording the harness does not recognise, including a refusal of every form that never mentions loading, is reported as inconclusive, never as a pass. A server that returns a custom message for a real attempt could still be misread as refused only if that message also matches one of the denial phrases. A declared writer tool is probed like any other.

False negatives

A real extension is never shipped, so a successful load is not tested: the check observes whether the engine tried to open a library at a path the harness chose.