@malloydata/db-duckdb 0.0.378 → 0.0.380

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,20 +10,29 @@ must not depend on them for essential context.
10
10
 
11
11
  ## Mental Model
12
12
 
13
- Malloy exposes two user-facing policy axes for native DuckDB:
13
+ Malloy exposes one user-facing policy property for native DuckDB:
14
14
 
15
- - `filesystemPolicy: "open" | "sandboxed"`
16
- - `networkPolicy: "open" | "closed"`
15
+ - `securityPolicy: "none" | "local" | "sandboxed"`
17
16
 
18
- Those fields are Malloy policy controls. They are not raw DuckDB option names
19
- and they are not a general policy language.
17
+ This field is a Malloy policy control. It is not a raw DuckDB option name
18
+ and it is not a general policy language.
20
19
 
21
- The reviewed strict recipe for untrusted Malloy is:
20
+ The three levels form a strict escalation:
22
21
 
23
- - `filesystemPolicy: "sandboxed"`
24
- - `networkPolicy: "closed"`
22
+ - `"none"` — no security policy applied. Ordinary DuckDB behavior.
23
+ - `"local"` — no network access. DuckDB cannot reach the network, but local
24
+ filesystem access is not sandboxed to specific directories.
25
+ - `"sandboxed"` — no network access AND filesystem confined to
26
+ `allowedDirectories`. The reviewed strict recipe.
25
27
 
26
- The implementation compiles those public policy values into an internal
28
+ DuckDB's `enable_external_access` is a single toggle that gates both filesystem
29
+ reach beyond the working directory and network reach. Setting
30
+ `allowed_directories` without disabling `enable_external_access` has no effect.
31
+ This is why the policy is a single axis rather than two independent axes — the
32
+ underlying DuckDB mechanism does not support independent filesystem and network
33
+ control.
34
+
35
+ The implementation compiles the public policy value into an internal
27
36
  `NormalizedDuckDBSafetyPolicy`. That internal object records the derived
28
37
  enforcement requirements, such as locked configuration, no setup SQL,
29
38
  temp-file encryption, extension restrictions, and secret neutralization. Keep
@@ -41,9 +50,9 @@ process isolation, query cancellation, and host-level quotas separately.
41
50
 
42
51
  - Native connection schema: `src/native.ts`
43
52
  - Registers native DuckDB properties.
44
- - `filesystemPolicy` and `networkPolicy` are `requireLiteralString` so
45
- invalid reference-shaped or non-string values reach registry validation
46
- instead of being silently dropped by generic config compilation.
53
+ - `securityPolicy` is `requireLiteralString` so invalid reference-shaped or
54
+ non-string values reach registry validation instead of being silently
55
+ dropped by generic config compilation.
47
56
  - Config compiler literal guard: `../malloy/src/api/foundation/config_compile.ts`
48
57
  - Preserves invalid literal-required values as values after warning, allowing
49
58
  registry lookup to fail closed before the DuckDB factory runs.
@@ -72,7 +81,7 @@ process isolation, query cancellation, and host-level quotas separately.
72
81
  baseline must fail connection creation.
73
82
  - Policy validation must run early enough that invalid raw values such as
74
83
  `true`, `42`, or `{env: "POLICY"}` cannot be silently dropped and interpreted
75
- as the default `"open"` policy.
84
+ as the default `"none"` policy.
76
85
  - A restricted DuckDB connection must never share a live instance with a less
77
86
  restrictive or otherwise semantically different connection.
78
87
  - The fixed baseline must be established before any Malloy-derived SQL runs.
@@ -85,22 +94,11 @@ process isolation, query cancellation, and host-level quotas separately.
85
94
 
86
95
  ## User-Facing Policy Behavior
87
96
 
88
- `filesystemPolicy: "open"` means Malloy does not derive a filesystem sandbox.
97
+ `securityPolicy: "none"` means no security policy is applied. Ordinary DuckDB
98
+ behavior.
89
99
 
90
- `filesystemPolicy: "sandboxed"` means Malloy derives and enforces a native
91
- DuckDB filesystem boundary:
92
-
93
- - `allowedDirectories` is required or derived.
94
- - `tempDirectory` is required or derived.
95
- - `workingDirectory`, when present, must be inside `allowedDirectories`.
96
- - `tempDirectory` must be inside `allowedDirectories`.
97
- - Non-POSIX hosts are rejected. Do not approximate Windows path behavior for
98
- the current policy.
99
-
100
- `networkPolicy: "open"` means network-capable DuckDB behavior may remain
101
- available.
102
-
103
- `networkPolicy: "closed"` means Malloy disables network-capable DuckDB behavior:
100
+ `securityPolicy: "local"` means Malloy disables network-capable DuckDB
101
+ behavior:
104
102
 
105
103
  - `enableExternalAccess` is forced to `false`.
106
104
  - `httpfs` must not load.
@@ -108,14 +106,20 @@ available.
108
106
  - network-requiring `databasePath` values, including MotherDuck paths, are
109
107
  rejected.
110
108
  - `motherDuckToken` is rejected.
109
+ - configuration is locked after baseline setup.
110
+ - `setupSQL` is not allowed.
111
+ - temp-file encryption is required.
112
+ - secrets are neutralized.
111
113
 
112
- The two axes can be used independently:
114
+ `securityPolicy: "sandboxed"` means everything in `"local"`, plus Malloy
115
+ derives and enforces a native DuckDB filesystem boundary:
113
116
 
114
- - sandboxed filesystem plus open network is allowed, but it is not the reviewed
115
- strict recipe.
116
- - open filesystem plus closed network is allowed, for hosts that trust an
117
- external filesystem/container boundary but want Malloy to close DuckDB's
118
- network-capable surface.
117
+ - `allowedDirectories` is required or derived.
118
+ - `tempDirectory` is required or derived.
119
+ - `workingDirectory`, when present, must be inside `allowedDirectories`.
120
+ - `tempDirectory` must be inside `allowedDirectories`.
121
+ - Non-POSIX hosts are rejected. Do not approximate Windows path behavior for
122
+ the current policy.
119
123
 
120
124
  ## Registered Config Surface
121
125
 
@@ -128,10 +132,9 @@ Native DuckDB supports the existing Malloy-facing properties:
128
132
  - `readOnly`
129
133
  - `setupSQL`
130
134
 
131
- The config extension adds policy properties:
135
+ The config extension adds the policy property:
132
136
 
133
- - `filesystemPolicy`
134
- - `networkPolicy`
137
+ - `securityPolicy`
135
138
 
136
139
  It also adds curated DuckDB-level properties:
137
140
 
@@ -153,7 +156,7 @@ connection property model does not yet have a first-class string-array type.
153
156
  Even though the registry metadata says `json`, DuckDB normalization must require
154
157
  the value to be a JSON array of strings. Do not add a registry default for
155
158
  `allowedDirectories`; its only defaulting behavior belongs to
156
- `filesystemPolicy: "sandboxed"` normalization.
159
+ `securityPolicy: "sandboxed"` normalization.
157
160
 
158
161
  `workingDirectory` defaults to `{config: "rootDirectory"}` in the native DuckDB
159
162
  registry. Hosts that know the project root should populate `config.rootDirectory`
@@ -171,28 +174,28 @@ All policy reasoning belongs in `normalizeDuckDBConfig()`.
171
174
 
172
175
  Policy parsing:
173
176
 
174
- - Missing `filesystemPolicy` defaults to `"open"`.
175
- - Missing `networkPolicy` defaults to `"open"`.
176
- - Accepted policy values are exact documented strings only.
177
+ - Missing `securityPolicy` defaults to `"none"`.
178
+ - Accepted policy values are exact documented strings only: `"none"`, `"local"`,
179
+ `"sandboxed"`.
177
180
  - Unknown strings, strings with whitespace or different casing, non-strings,
178
181
  and reference-shaped values must fail closed.
179
182
 
180
183
  Derived safety policy:
181
184
 
182
- - If either policy is restricted, derive:
185
+ - If `securityPolicy` is `"local"` or `"sandboxed"` (i.e., restricted), derive:
183
186
  - `requiresLockedConfiguration: true`
184
187
  - `requiresNoSetupSQL: true`
185
188
  - `requiresTempFileEncryption: true`
186
189
  - `requiresSecretNeutralization: true`
187
190
  - `forbidAdditionalExtensions: true`
191
+ - `allowHttpfs: false`
192
+ - `enableExternalAccess: false`
188
193
  - required baseline extensions `icu` and `json`
189
- - If `filesystemPolicy === "sandboxed"`, also derive:
194
+ - no extension install or auto-install/autoload expansion
195
+ - If `securityPolicy === "sandboxed"`, also derive:
190
196
  - POSIX host required
191
197
  - sandboxed path validation required
192
198
  - derived temp directory name `.tmp`
193
- - If `networkPolicy === "closed"`, also derive:
194
- - `allowHttpfs: false`
195
- - no extension install or auto-install/autoload expansion
196
199
 
197
200
  Conflict checks:
198
201
 
@@ -201,7 +204,7 @@ Conflict checks:
201
204
  extension broadening.
202
205
  - Reject `lockConfiguration: false` under any restricted policy.
203
206
  - Reject `tempFileEncryption: false` under any restricted policy.
204
- - Under `networkPolicy: "closed"`, reject:
207
+ - Under any restricted policy, reject:
205
208
  - `enableExternalAccess: true`
206
209
  - `autoloadKnownExtensions: true`
207
210
  - `autoinstallKnownExtensions: true`
@@ -210,26 +213,25 @@ Conflict checks:
210
213
  - `motherDuckToken`
211
214
  - remote or network-requiring `databasePath`
212
215
  - Redundant matching values are allowed. For example,
213
- `networkPolicy: "closed"` plus `enableExternalAccess: false` is valid.
216
+ `securityPolicy: "local"` plus `enableExternalAccess: false` is valid.
214
217
 
215
218
  Derived defaults:
216
219
 
217
- - Under `networkPolicy: "closed"`, force:
220
+ - Under any restricted policy, force:
218
221
  - `enableExternalAccess = false`
222
+ - `lockConfiguration = true`
223
+ - `tempFileEncryption = true`
219
224
  - `autoloadKnownExtensions = false`
220
225
  - `autoinstallKnownExtensions = false`
221
226
  - `allowCommunityExtensions = false`
222
227
  - `allowUnsignedExtensions = false`
223
- - Under any restricted policy, force:
224
- - `lockConfiguration = true`
225
- - `tempFileEncryption = true`
226
- - Under `filesystemPolicy: "sandboxed"`:
228
+ - Under `securityPolicy: "sandboxed"`:
227
229
  - If `allowedDirectories` is omitted, derive it to exactly the canonical
228
230
  `workingDirectory`, and nothing broader.
229
231
  - If `tempDirectory` is omitted, derive it to `workingDirectory/.tmp`.
230
232
  - If Malloy cannot derive the required paths safely, fail closed with a
231
233
  field-specific error.
232
- - Outside `filesystemPolicy: "sandboxed"`, do not invent an
234
+ - Outside `securityPolicy: "sandboxed"`, do not invent an
233
235
  `allowedDirectories` default.
234
236
  - With `databasePath: ":memory:"`, normalize `readOnly` to `false`.
235
237
  This intentionally preserves existing behavior. A user-facing warning for
@@ -278,8 +280,7 @@ effective setting that can affect runtime behavior, safety, or semantics:
278
280
 
279
281
  - `databasePath`
280
282
  - `readOnly`
281
- - `filesystemPolicy`
282
- - `networkPolicy`
283
+ - `securityPolicy`
283
284
  - `setupSQL`
284
285
  - canonicalized `allowedDirectories`
285
286
  - `enableExternalAccess`
@@ -346,7 +347,7 @@ Current baseline mapping:
346
347
  - must be established before lock
347
348
  - built-in extension loading
348
349
  - preserve ordinary compatibility outside restricted modes
349
- - under `networkPolicy: "closed"`, do not `INSTALL`, do not load `httpfs`,
350
+ - under any restricted policy, do not `INSTALL`, do not load `httpfs`,
350
351
  and load only the fixed Malloy baseline extensions
351
352
  - `setupSQL`
352
353
  - preserve outside restricted modes
@@ -361,9 +362,9 @@ configuration after `lock_configuration=true`.
361
362
  `icu` and `json` are part of the fixed Malloy DuckDB baseline.
362
363
 
363
364
  `httpfs` is not part of that baseline. It broadens remote/network-capable
364
- behavior and is controlled by `networkPolicy`.
365
+ behavior and is controlled by `securityPolicy`.
365
366
 
366
- Under `networkPolicy: "closed"`:
367
+ Under any restricted policy (`"local"` or `"sandboxed"`):
367
368
 
368
369
  - do not load `httpfs`
369
370
  - do not `INSTALL` extensions
@@ -371,7 +372,7 @@ Under `networkPolicy: "closed"`:
371
372
  - load only fixed baseline extensions `icu` and `json`
372
373
  - fail closed if a required baseline extension is unavailable locally
373
374
 
374
- Outside `networkPolicy: "closed"`, preserve ordinary compatibility unless a
375
+ Outside restricted policies, preserve ordinary compatibility unless a
375
376
  separate product decision changes it.
376
377
 
377
378
  ## Secrets
@@ -400,11 +401,10 @@ restricted-mode tests.
400
401
 
401
402
  The policy system described here targets native DuckDB.
402
403
 
403
- Do not register native-only policy fields in the `duckdb_wasm` connection
404
+ Do not register the native-only policy field in the `duckdb_wasm` connection
404
405
  schema in this pass:
405
406
 
406
- - `filesystemPolicy`
407
- - `networkPolicy`
407
+ - `securityPolicy`
408
408
  - native-only hardening properties
409
409
 
410
410
  Do not add native restricted-policy behavior to `DuckDBWASMConnection` as a
@@ -422,7 +422,7 @@ When adding a new native DuckDB config property, update all relevant layers:
422
422
 
423
423
  - Register the property in `src/native.ts` with the correct config metadata.
424
424
  - Parse and validate it in `normalizeDuckDBConfig()`.
425
- - Decide whether it conflicts with `filesystemPolicy` or `networkPolicy`.
425
+ - Decide whether it conflicts with `securityPolicy`.
426
426
  - Decide whether any restricted policy must derive or force a value.
427
427
  - If it is path-like, canonicalize it before validation and identity
428
428
  comparison.
@@ -440,8 +440,8 @@ forced to a safe value under restricted policies.
440
440
 
441
441
  ## Changing Policy Behavior
442
442
 
443
- When changing `filesystemPolicy`, `networkPolicy`, or
444
- `NormalizedDuckDBSafetyPolicy`, review these together:
443
+ When changing `securityPolicy` or `NormalizedDuckDBSafetyPolicy`, review these
444
+ together:
445
445
 
446
446
  - user-facing policy contract
447
447
  - normalizer parsing and conflict checks
@@ -468,8 +468,7 @@ Keep focused tests for:
468
468
  - `allowedDirectories` accepted as a JSON array of strings
469
469
  - `allowedDirectories` rejected for non-array or non-string JSON values
470
470
  - exact policy value parsing
471
- - policy fields rejected when provided as non-literal or reference-shaped
472
- values
471
+ - policy field rejected when provided as non-literal or reference-shaped value
473
472
  - missing policy-required values failing closed
474
473
  - conflict checks
475
474
  - redundant matching explicit values accepted
@@ -478,10 +477,11 @@ Keep focused tests for:
478
477
  - network-requiring `databasePath` rejection
479
478
  - `readOnly: true` with `:memory:` normalizing to `false`
480
479
  - share keys differing when safety-relevant settings differ
480
+ - different `securityPolicy` values producing different share keys
481
481
  - semantically identical allowlists sharing
482
482
  - restricted baseline order
483
- - `networkPolicy: "closed"` not loading `httpfs`
484
- - `networkPolicy: "closed"` not running `INSTALL`
483
+ - restricted policies not loading `httpfs`
484
+ - restricted policies not running `INSTALL`
485
485
  - required baseline extensions loaded or failing closed
486
486
  - later config-changing `SET` statements failing after lock
487
487
  - `closeAllInstances()` clearing all share-keyed native instances
@@ -1,6 +1,5 @@
1
1
  import type { ConnectionConfig } from '@malloydata/malloy';
2
- export type DuckDBFilesystemPolicy = 'open' | 'sandboxed';
3
- export type DuckDBNetworkPolicy = 'open' | 'closed';
2
+ export type DuckDBSecurityPolicy = 'none' | 'local' | 'sandboxed';
4
3
  export interface NormalizedDuckDBSafetyPolicy {
5
4
  requiresPosixHost: boolean;
6
5
  requiresLockedConfiguration: boolean;
@@ -9,7 +8,6 @@ export interface NormalizedDuckDBSafetyPolicy {
9
8
  requiresTempFileEncryption: boolean;
10
9
  requiresSecretNeutralization: boolean;
11
10
  requiredBaselineExtensions: readonly ['icu', 'json'];
12
- allowHttpfs: boolean;
13
11
  forbidAdditionalExtensions: boolean;
14
12
  derivedTempDirectoryName: '.tmp';
15
13
  }
@@ -18,8 +16,7 @@ export interface NormalizedDuckDBConfig {
18
16
  databasePath: string;
19
17
  readOnly: boolean;
20
18
  workingDirectory?: string;
21
- filesystemPolicy: DuckDBFilesystemPolicy;
22
- networkPolicy: DuckDBNetworkPolicy;
19
+ securityPolicy: DuckDBSecurityPolicy;
23
20
  safetyPolicy?: NormalizedDuckDBSafetyPolicy;
24
21
  allowedDirectories?: string[];
25
22
  enableExternalAccess?: boolean;
@@ -61,10 +61,9 @@ const DERIVED_TEMP_DIRECTORY_NAME = '.tmp';
61
61
  const DERIVED_SECRET_DIRECTORY_NAME = '.duckdb-secrets';
62
62
  function normalizeDuckDBConfig(config) {
63
63
  var _a, _b, _c, _d;
64
- const filesystemPolicy = parsePolicyValue(config['filesystemPolicy'], 'filesystemPolicy', ['open', 'sandboxed']);
65
- const networkPolicy = parsePolicyValue(config['networkPolicy'], 'networkPolicy', ['open', 'closed']);
66
- if (filesystemPolicy === 'sandboxed' && !pathSecurity.isPosixHost()) {
67
- throw new DuckDBConfigValidationError('filesystemPolicy "sandboxed" is only supported on POSIX hosts');
64
+ const securityPolicy = parseSecurityPolicy(config['securityPolicy']);
65
+ if (securityPolicy === 'sandboxed' && !pathSecurity.isPosixHost()) {
66
+ throw new DuckDBConfigValidationError('securityPolicy "sandboxed" is only supported on POSIX hosts');
68
67
  }
69
68
  const rawDatabasePath = (_b = (_a = readOptionalString(config, 'databasePath')) !== null && _a !== void 0 ? _a : readOptionalString(config, 'path')) !== null && _b !== void 0 ? _b : ':memory:';
70
69
  const rawWorkingDirectory = readOptionalString(config, 'workingDirectory');
@@ -84,94 +83,92 @@ function normalizeDuckDBConfig(config) {
84
83
  const rawTempDirectory = readOptionalString(config, 'tempDirectory');
85
84
  const rawExtensionDirectory = readOptionalString(config, 'extensionDirectory');
86
85
  const rawAllowedDirectories = readOptionalStringArray(config['allowedDirectories'], 'allowedDirectories');
87
- const restricted = filesystemPolicy === 'sandboxed' || networkPolicy === 'closed';
86
+ const restricted = securityPolicy !== 'none';
88
87
  const safetyPolicy = restricted
89
88
  ? {
90
- requiresPosixHost: filesystemPolicy === 'sandboxed',
89
+ requiresPosixHost: securityPolicy === 'sandboxed',
91
90
  requiresLockedConfiguration: true,
92
91
  requiresNoSetupSQL: true,
93
- requiresSandboxedPaths: filesystemPolicy === 'sandboxed',
92
+ requiresSandboxedPaths: securityPolicy === 'sandboxed',
94
93
  requiresTempFileEncryption: true,
95
94
  requiresSecretNeutralization: true,
96
95
  requiredBaselineExtensions: REQUIRED_BASELINE_EXTENSIONS,
97
- allowHttpfs: networkPolicy === 'open',
98
96
  forbidAdditionalExtensions: true,
99
97
  derivedTempDirectoryName: DERIVED_TEMP_DIRECTORY_NAME,
100
98
  }
101
99
  : undefined;
102
100
  if ((safetyPolicy === null || safetyPolicy === void 0 ? void 0 : safetyPolicy.requiresNoSetupSQL) && rawSetupSQL !== undefined) {
103
- throw new DuckDBConfigValidationError('setupSQL is not allowed when filesystemPolicy or networkPolicy requires a locked DuckDB baseline');
101
+ throw new DuckDBConfigValidationError(`setupSQL is not allowed when securityPolicy is "${securityPolicy}"`);
104
102
  }
105
103
  if ((safetyPolicy === null || safetyPolicy === void 0 ? void 0 : safetyPolicy.forbidAdditionalExtensions) &&
106
104
  additionalExtensions.length > 0) {
107
- throw new DuckDBConfigValidationError('additionalExtensions is not allowed when filesystemPolicy or networkPolicy requires a locked DuckDB baseline');
105
+ throw new DuckDBConfigValidationError(`additionalExtensions is not allowed when securityPolicy is "${securityPolicy}"`);
108
106
  }
109
107
  if (restricted && lockConfiguration === false) {
110
- throw new DuckDBConfigValidationError('lockConfiguration cannot be false when filesystemPolicy or networkPolicy requires a locked DuckDB baseline');
108
+ throw new DuckDBConfigValidationError(`lockConfiguration cannot be false when securityPolicy is "${securityPolicy}"`);
111
109
  }
112
110
  if (restricted && tempFileEncryption === false) {
113
- throw new DuckDBConfigValidationError('tempFileEncryption cannot be false when filesystemPolicy or networkPolicy requires a locked DuckDB baseline');
114
- }
115
- if (networkPolicy === 'closed') {
116
- rejectConflictingBoolean(enableExternalAccess, 'enableExternalAccess', true, 'networkPolicy "closed"');
117
- rejectConflictingBoolean(autoloadKnownExtensions, 'autoloadKnownExtensions', true, 'networkPolicy "closed"');
118
- rejectConflictingBoolean(autoinstallKnownExtensions, 'autoinstallKnownExtensions', true, 'networkPolicy "closed"');
119
- rejectConflictingBoolean(allowCommunityExtensions, 'allowCommunityExtensions', true, 'networkPolicy "closed"');
120
- rejectConflictingBoolean(allowUnsignedExtensions, 'allowUnsignedExtensions', true, 'networkPolicy "closed"');
111
+ throw new DuckDBConfigValidationError(`tempFileEncryption cannot be false when securityPolicy is "${securityPolicy}"`);
112
+ }
113
+ if (restricted) {
114
+ rejectConflictingBoolean(enableExternalAccess, 'enableExternalAccess', true, `securityPolicy "${securityPolicy}"`);
115
+ rejectConflictingBoolean(autoloadKnownExtensions, 'autoloadKnownExtensions', true, `securityPolicy "${securityPolicy}"`);
116
+ rejectConflictingBoolean(autoinstallKnownExtensions, 'autoinstallKnownExtensions', true, `securityPolicy "${securityPolicy}"`);
117
+ rejectConflictingBoolean(allowCommunityExtensions, 'allowCommunityExtensions', true, `securityPolicy "${securityPolicy}"`);
118
+ rejectConflictingBoolean(allowUnsignedExtensions, 'allowUnsignedExtensions', true, `securityPolicy "${securityPolicy}"`);
121
119
  if (rawMotherDuckToken !== undefined) {
122
- throw new DuckDBConfigValidationError('motherDuckToken is not allowed when networkPolicy is "closed"');
120
+ throw new DuckDBConfigValidationError(`motherDuckToken is not allowed when securityPolicy is "${securityPolicy}"`);
123
121
  }
124
122
  }
125
123
  let workingDirectory;
126
124
  if (rawWorkingDirectory !== undefined) {
127
125
  workingDirectory = canonicalizeConfigPath(rawWorkingDirectory, 'workingDirectory', {
128
- mustExist: filesystemPolicy === 'sandboxed',
126
+ mustExist: securityPolicy === 'sandboxed',
129
127
  });
130
128
  }
131
129
  const databasePath = canonicalizeDatabasePath(rawDatabasePath);
132
130
  const isMotherDuck = isMotherDuckPath(databasePath);
133
- if (networkPolicy === 'closed' &&
134
- !isAllowedClosedNetworkDatabasePath(databasePath)) {
135
- throw new DuckDBConfigValidationError(`databasePath "${rawDatabasePath}" is not allowed when networkPolicy is "closed"`);
131
+ if (restricted && !isAllowedClosedNetworkDatabasePath(databasePath)) {
132
+ throw new DuckDBConfigValidationError(`databasePath "${rawDatabasePath}" is not allowed when securityPolicy is "${securityPolicy}"`);
136
133
  }
137
- if (networkPolicy === 'closed' && isMotherDuck) {
138
- throw new DuckDBConfigValidationError('MotherDuck database paths are not allowed when networkPolicy is "closed"');
134
+ if (restricted && isMotherDuck) {
135
+ throw new DuckDBConfigValidationError(`MotherDuck database paths are not allowed when securityPolicy is "${securityPolicy}"`);
139
136
  }
140
137
  const normalizedAllowedDirectories = rawAllowedDirectories === undefined
141
138
  ? undefined
142
139
  : canonicalizeConfigPathList(rawAllowedDirectories, 'allowedDirectories');
143
140
  let allowedDirectories = normalizedAllowedDirectories;
144
- if (filesystemPolicy === 'sandboxed') {
141
+ if (securityPolicy === 'sandboxed') {
145
142
  if (allowedDirectories === undefined) {
146
143
  if (workingDirectory === undefined) {
147
- throw new DuckDBConfigValidationError('filesystemPolicy "sandboxed" requires either allowedDirectories or workingDirectory. If you expected workingDirectory to come from config.rootDirectory, verify that overlay is available.');
144
+ throw new DuckDBConfigValidationError('securityPolicy "sandboxed" requires either allowedDirectories or workingDirectory. If you expected workingDirectory to come from config.rootDirectory, verify that overlay is available.');
148
145
  }
149
146
  allowedDirectories = [workingDirectory];
150
147
  }
151
148
  if (workingDirectory !== undefined &&
152
149
  !allowedDirectories.some(allowed => pathSecurity.isContainedPath(allowed, workingDirectory))) {
153
- throw new DuckDBConfigValidationError('workingDirectory must be contained within allowedDirectories when filesystemPolicy is "sandboxed"');
150
+ throw new DuckDBConfigValidationError('workingDirectory must be contained within allowedDirectories when securityPolicy is "sandboxed"');
154
151
  }
155
152
  }
156
153
  let tempDirectory;
157
154
  if (rawTempDirectory !== undefined) {
158
155
  tempDirectory = canonicalizeConfigPath(rawTempDirectory, 'tempDirectory');
159
156
  }
160
- else if (filesystemPolicy === 'sandboxed') {
157
+ else if (securityPolicy === 'sandboxed') {
161
158
  if (workingDirectory === undefined) {
162
- throw new DuckDBConfigValidationError('filesystemPolicy "sandboxed" requires tempDirectory or workingDirectory so Malloy can derive a safe temp directory');
159
+ throw new DuckDBConfigValidationError('securityPolicy "sandboxed" requires tempDirectory or workingDirectory so Malloy can derive a safe temp directory');
163
160
  }
164
161
  tempDirectory = path_1.default.join(workingDirectory, DERIVED_TEMP_DIRECTORY_NAME);
165
162
  }
166
- if (filesystemPolicy === 'sandboxed') {
163
+ if (securityPolicy === 'sandboxed') {
167
164
  if (allowedDirectories === undefined) {
168
- throw new DuckDBConfigValidationError('filesystemPolicy "sandboxed" requires allowedDirectories');
165
+ throw new DuckDBConfigValidationError('securityPolicy "sandboxed" requires allowedDirectories');
169
166
  }
170
167
  if (tempDirectory === undefined) {
171
- throw new DuckDBConfigValidationError('filesystemPolicy "sandboxed" requires tempDirectory');
168
+ throw new DuckDBConfigValidationError('securityPolicy "sandboxed" requires tempDirectory');
172
169
  }
173
170
  if (!allowedDirectories.some(allowed => pathSecurity.isContainedPath(allowed, tempDirectory))) {
174
- throw new DuckDBConfigValidationError('tempDirectory must be contained within allowedDirectories when filesystemPolicy is "sandboxed"');
171
+ throw new DuckDBConfigValidationError('tempDirectory must be contained within allowedDirectories when securityPolicy is "sandboxed"');
175
172
  }
176
173
  }
177
174
  const extensionDirectory = rawExtensionDirectory === undefined
@@ -187,18 +184,17 @@ function normalizeDuckDBConfig(config) {
187
184
  databasePath,
188
185
  readOnly: databasePath === ':memory:' ? false : readOnly,
189
186
  workingDirectory,
190
- filesystemPolicy,
191
- networkPolicy,
187
+ securityPolicy,
192
188
  safetyPolicy,
193
189
  allowedDirectories,
194
- enableExternalAccess: networkPolicy === 'closed' ? false : enableExternalAccess,
190
+ enableExternalAccess: restricted ? false : enableExternalAccess,
195
191
  lockConfiguration: (safetyPolicy === null || safetyPolicy === void 0 ? void 0 : safetyPolicy.requiresLockedConfiguration)
196
192
  ? true
197
193
  : lockConfiguration,
198
- autoloadKnownExtensions: networkPolicy === 'closed' ? false : autoloadKnownExtensions,
199
- autoinstallKnownExtensions: networkPolicy === 'closed' ? false : autoinstallKnownExtensions,
200
- allowCommunityExtensions: networkPolicy === 'closed' ? false : allowCommunityExtensions,
201
- allowUnsignedExtensions: networkPolicy === 'closed' ? false : allowUnsignedExtensions,
194
+ autoloadKnownExtensions: restricted ? false : autoloadKnownExtensions,
195
+ autoinstallKnownExtensions: restricted ? false : autoinstallKnownExtensions,
196
+ allowCommunityExtensions: restricted ? false : allowCommunityExtensions,
197
+ allowUnsignedExtensions: restricted ? false : allowUnsignedExtensions,
202
198
  tempFileEncryption: (safetyPolicy === null || safetyPolicy === void 0 ? void 0 : safetyPolicy.requiresTempFileEncryption)
203
199
  ? true
204
200
  : tempFileEncryption,
@@ -216,7 +212,7 @@ function buildDuckDBShareKey(config) {
216
212
  var _a, _b, _c, _d, _e, _f, _g;
217
213
  // secretDirectory is derived from policy + workingDirectory/tempDirectory,
218
214
  // which already participate in the share key.
219
- return (0, malloy_1.makeDigest)('duckdb-share-key-v1', config.databasePath, String(config.readOnly), config.filesystemPolicy, config.networkPolicy, (_a = config.setupSQL) !== null && _a !== void 0 ? _a : '', ...((_b = config.allowedDirectories) !== null && _b !== void 0 ? _b : []), config.enableExternalAccess === undefined
215
+ return (0, malloy_1.makeDigest)('duckdb-share-key-v2', config.databasePath, String(config.readOnly), config.securityPolicy, (_a = config.setupSQL) !== null && _a !== void 0 ? _a : '', ...((_b = config.allowedDirectories) !== null && _b !== void 0 ? _b : []), config.enableExternalAccess === undefined
220
216
  ? ''
221
217
  : String(config.enableExternalAccess), config.lockConfiguration === undefined
222
218
  ? ''
@@ -241,21 +237,25 @@ function sqlStringListLiteral(values) {
241
237
  function stringifyDuckDBOption(value) {
242
238
  return String(value);
243
239
  }
244
- function parsePolicyValue(rawValue, fieldName, allowedValues) {
240
+ const SECURITY_POLICY_VALUES = [
241
+ 'none',
242
+ 'local',
243
+ 'sandboxed',
244
+ ];
245
+ function isSecurityPolicy(value) {
246
+ return SECURITY_POLICY_VALUES.includes(value);
247
+ }
248
+ function parseSecurityPolicy(rawValue) {
245
249
  if (rawValue === undefined) {
246
- return 'open';
250
+ return 'none';
247
251
  }
248
252
  if (typeof rawValue !== 'string') {
249
- throw new DuckDBConfigValidationError(`${fieldName} must be one of ${allowedValues
250
- .map(v => `"${v}"`)
251
- .join(' or ')}, got ${typeof rawValue}`);
253
+ throw new DuckDBConfigValidationError(`securityPolicy must be one of ${SECURITY_POLICY_VALUES.map(v => `"${v}"`).join(', ')}, got ${typeof rawValue}`);
252
254
  }
253
- if (allowedValues.includes(rawValue)) {
255
+ if (isSecurityPolicy(rawValue)) {
254
256
  return rawValue;
255
257
  }
256
- throw new DuckDBConfigValidationError(`${fieldName} must be one of ${allowedValues
257
- .map(v => `"${v}"`)
258
- .join(' or ')}, got "${rawValue}"`);
258
+ throw new DuckDBConfigValidationError(`securityPolicy must be one of ${SECURITY_POLICY_VALUES.map(v => `"${v}"`).join(', ')}, got "${rawValue}"`);
259
259
  }
260
260
  function readOptionalString(config, fieldName) {
261
261
  const rawValue = config[fieldName];
@@ -9,8 +9,7 @@ export interface DuckDBConnectionOptions extends ConnectionConfig {
9
9
  workingDirectory?: string;
10
10
  readOnly?: boolean;
11
11
  setupSQL?: string;
12
- filesystemPolicy?: 'open' | 'sandboxed';
13
- networkPolicy?: 'open' | 'closed';
12
+ securityPolicy?: 'none' | 'local' | 'sandboxed';
14
13
  allowedDirectories?: string[];
15
14
  enableExternalAccess?: boolean;
16
15
  lockConfiguration?: boolean;
@@ -50,7 +49,6 @@ export declare class DuckDBConnection extends DuckDBCommon {
50
49
  private shouldApplyEnableExternalAccessAtOpenTime;
51
50
  private applyFinalBaseline;
52
51
  private loadBaselineExtensions;
53
- private shouldLoadHttpfs;
54
52
  private loadExtension;
55
53
  protected runDuckDBQuery(sql: string): Promise<{
56
54
  rows: QueryRecord[];
@@ -175,7 +175,7 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
175
175
  await this.loadBaselineExtensions();
176
176
  }
177
177
  async loadBaselineExtensions() {
178
- if (this.normalized.networkPolicy === 'closed') {
178
+ if (this.normalized.securityPolicy !== 'none') {
179
179
  await this.loadExtension('json', { allowInstall: false, required: true });
180
180
  await this.loadExtension('icu', { allowInstall: false, required: true });
181
181
  return;
@@ -183,7 +183,7 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
183
183
  const allowInstall = this.normalized.enableExternalAccess !== false;
184
184
  await this.loadExtension('json', { allowInstall, required: false });
185
185
  await this.loadExtension('icu', { allowInstall, required: false });
186
- if (this.shouldLoadHttpfs()) {
186
+ if (this.normalized.enableExternalAccess !== false) {
187
187
  await this.loadExtension('httpfs', { allowInstall, required: false });
188
188
  }
189
189
  for (const extension of this.normalized.additionalExtensions) {
@@ -193,12 +193,6 @@ class DuckDBConnection extends duckdb_common_1.DuckDBCommon {
193
193
  await this.loadExtension('motherduck', { allowInstall, required: false });
194
194
  }
195
195
  }
196
- shouldLoadHttpfs() {
197
- if (this.normalized.networkPolicy === 'closed') {
198
- return false;
199
- }
200
- return this.normalized.enableExternalAccess !== false;
201
- }
202
196
  async loadExtension(extension, options) {
203
197
  try {
204
198
  await this.runDuckDBQuery(`LOAD ${(0, duckdb_config_1.sqlStringLiteral)(extension)}`);
package/dist/native.js CHANGED
@@ -38,15 +38,8 @@ const duckdb_connection_1 = require("./duckdb_connection");
38
38
  default: { config: 'rootDirectory' },
39
39
  },
40
40
  {
41
- name: 'filesystemPolicy',
42
- displayName: 'Filesystem Policy',
43
- type: 'string',
44
- optional: true,
45
- requireLiteralString: true,
46
- },
47
- {
48
- name: 'networkPolicy',
49
- displayName: 'Network Policy',
41
+ name: 'securityPolicy',
42
+ displayName: 'Security Policy',
50
43
  type: 'string',
51
44
  optional: true,
52
45
  requireLiteralString: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/db-duckdb",
3
- "version": "0.0.378",
3
+ "version": "0.0.380",
4
4
  "license": "MIT",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -60,7 +60,7 @@
60
60
  "dependencies": {
61
61
  "@duckdb/duckdb-wasm": "1.33.1-dev13.0",
62
62
  "@duckdb/node-api": "1.4.4-r.1",
63
- "@malloydata/malloy": "0.0.378",
63
+ "@malloydata/malloy": "0.0.380",
64
64
  "@motherduck/wasm-client": "^0.6.6",
65
65
  "apache-arrow": "^17.0.0",
66
66
  "web-worker": "^1.3.0"