Security model
Guarantees that do not depend on the policy
Section titled “Guarantees that do not depend on the policy”- Locked configuration: after startup, clients cannot change DuckDB settings.
- No external access by default:
read_*,COPY ... TOand newATTACHes are blocked, except for--allowed-pathprefixes. - No extension loading: autoinstall, autoload and community extensions are off.
- Always denied:
ATTACH,DETACH,LOAD,INSTALLandUPDATE EXTENSIONS. - Fail-closed inspection: the verb found by the tokenizer must match the
type DuckDB prepared; anything unmodelled is
resolved: false. - One statement per request, rejected before anything executes otherwise.
- A new connection per request: TEMP tables, variables and
USEnever leak between users. - One transaction per request: inspection, authorization and execution see the same snapshot.
- Clean environment: variables referenced in the catalog are removed from the process after startup.
- No metadata in errors: binder hints that could name hidden objects are stripped from responses.
How inspection is verified
Section titled “How inspection is verified”Authorization is only as good as the inspection that feeds it. A differential fuzzer checks it against DuckDB’s real behavior:
- execute each generated statement;
- snapshot every catalog (rows, columns, tables, views, schemas, sequences, macros) before and after;
- fail if a changed object is missing from the
targetsof an inspection markedresolved.
CI runs the seed corpus and an exhaustive sweep; a weekly job fuzzes for 10
minutes. Findings so far, all fixed with regression tests: a bypass with
$$...$$, ALTER ... RENAME TO missing the new name, memory.t resolved
in the wrong catalog, and invalid UTF-8 names breaking DuckDB’s metadata.
Row filters and masks have their own differential test.
Known limitations
Section titled “Known limitations”- Materialized results: the whole result is materialized inside DuckDB
before the first row is sent. Use
--memory-limit,--temp-dirand--max-rows. - Bind before authorization: binding happens before the policy runs.
With remote sources such as
iceberg_scan('s3://...')inside--allowed-path, an error message can reveal whether a table exists. - Iceberg latency without
cache_ttl: 0.3–1 s per request on R2. - Audit on crash: a crash between a write’s commit and its audit event can lose that event.
Reporting a vulnerability
Section titled “Reporting a vulnerability”Please report privately as described in SECURITY.md.