Policy. See Security overview for
how this fits with permissions.
Scope entries (per-capability)
A capability can attach ascope of Entry objects to constrain command arguments. Each Entry
has two optional fields (camelCase in JSON):
string
Allow a connection string — checked by
load. Matches exactly.string
Allow a filesystem path prefix — checked by
import’s source and export’s path. Matches by
whole path component and is traversal-safe.allow / deny arrays — see
Capabilities & permissions.
Enforcement: per-field and deny-first
Enforcement is evaluated independently per field:1
Deny wins
A matching deny entry for the field always rejects.
2
Allow requires a match
If any allow entries exist for that field, the value must match one of them.
3
Empty allow is unconstrained
An empty allow list for a field means that field is unconstrained by the capability — the
init-time
Policy remains the mandatory layer.Matching rules
connectionmatches by exact string equality —duckdb:app.dballows only that exact string.pathmatches by whole-component prefix and is traversal-safe:- a
..segment anywhere in the value is rejected outright, sodata/never allowsdata/../secret; - matching is on whole components, so
dataallowsdata/xbut notdatabase/xordata_secret/y; - backslashes are treated as separators (Windows paths).
- a
scope::Entry and registered from build.rs, so capability files
get validation and autocompletion for these fields.
The Rust Policy
The Policy is configured on core::Builder / diesel::Builder at plugin init and applies to
every operation regardless of capabilities — it is the mandatory floor beneath the per-window
scope. Its default is fully permissive; opt into each restriction.
The risky-statement filter
deny_risky_statements(true) rejects a statement whose leading keyword is one of:
Path normalization
allow_path canonicalizes both the allowed directories and the candidate path, then checks the
candidate starts with an allowed directory. For a not-yet-created export target, it falls back to
canonicalizing the parent directory and rejoining the filename — so writing a new file inside an
allowed directory still resolves.
Network-extension gating
load_extension is gated by allow_network: while it is off (default), the network extensions
httpfs, aws, and azure are rejected (they enable SQL-driven network egress / SSRF /
exfiltration). Non-network extensions (spatial, json, vss, …) load regardless.
How the two layers combine
For a given command invocation:- The capability must grant the command (permissions gate).
- The capability’s scope entries for the relevant field must allow the value (or be empty).
- The init-time
Policymust allow it.
denied by policy: ….
Scope and
Policy are complementary, not redundant. Scope is declarative and per-window (an
app can grant different windows different connections); Policy is mandatory and global (it
cannot be widened by a capability). Use scope to differentiate windows; use Policy as the floor
no window can escape.See also
- Capabilities & permissions — the command-level gate scope attaches to.
- Security overview — the threat model and the defense-in-depth checklist.
- Registration — where the
Policysetters live on the builder.