Skip to content

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 ... TO and new ATTACHes are blocked, except for --allowed-path prefixes.
  • No extension loading: autoinstall, autoload and community extensions are off.
  • Always denied: ATTACH, DETACH, LOAD, INSTALL and UPDATE 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 USE never 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.

Authorization is only as good as the inspection that feeds it. A differential fuzzer checks it against DuckDB’s real behavior:

  1. execute each generated statement;
  2. snapshot every catalog (rows, columns, tables, views, schemas, sequences, macros) before and after;
  3. fail if a changed object is missing from the targets of an inspection marked resolved.

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.

  • Materialized results: the whole result is materialized inside DuckDB before the first row is sent. Use --memory-limit, --temp-dir and --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.

Please report privately as described in SECURITY.md.