dsh-perm-gate 5.1.2-compat.7 → 5.2.0

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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Command normalization — decode the layers a call hides behind, as a scan signal.
3
+ *
4
+ * Why this exists: every deterministic layer reads the command *as written*.
5
+ * `deny-defaults` is a word list, the parsers switch on `baseName`, and
6
+ * `isCleanupSafe` matches path segments. All three are blind to
7
+ * `powershell -EncodedCommand UgBlAG0A...`, to `R="-rf"; rm $R /`, and to a
8
+ * function wrapper around a wipe. Measured on the eval set, the encoded cases are
9
+ * exactly where a graded model ships an unrecoverable act past the gate
10
+ * (`adv_013` decodes to `Remove-Item -Recurse -Force C:\`, `adv_032` to
11
+ * `DROP DATABASE orders_prod`).
12
+ *
13
+ * Two rules keep this from becoming its own vulnerability:
14
+ *
15
+ * 1. **Additive only.** The caller feeds these strings to *stricter* scans
16
+ * (deny-keyword, P0 wipe detection, tier grading). Nothing here can widen a
17
+ * decision, and no result is written back to `exec.arguments` or the event
18
+ * log, so fingerprints, grants and audit text stay about the call as received.
19
+ * 2. **Bounded.** Length caps, depth cap, variant cap and a printable-ratio gate,
20
+ * because decoding is attacker-controlled input on the decision path. Oversized
21
+ * or undecodable input yields nothing, which leaves the call
22
+ * `targetResolvable=false` — opaque, never optimistically safe.
23
+ *
24
+ * On the plugin's own `--exec-b64` launcher wrapper: it is decoded like anything
25
+ * else rather than exempted. An exemption would hand the agent a disguise
26
+ * (`ac-launcher --config x --exec-b64 <rm -rf ...>`), while decoding it can only
27
+ * ever escalate — so the cost is at most a second confirmation, never a block
28
+ * that could not have been reached honestly.
29
+ */
30
+ /** Where a decoded string came from, and whether the original runs it as code. */
31
+ export interface NormalizedText {
32
+ readonly text: string;
33
+ readonly via: 'base64' | 'hex' | 'varsub';
34
+ /**
35
+ * True when the payload is handed to an execution primitive
36
+ * (`-EncodedCommand`, `FromBase64String`, `Invoke-Expression`, `eval`,
37
+ * `bash -c`, `xxd -r -p` …), i.e. it will really run. Only these variants may
38
+ * feed P0 hard-deny; a stray base64 blob in a comment must not.
39
+ */
40
+ readonly executed: boolean;
41
+ }
42
+ export interface NormalizeOptions {
43
+ /** Refuse to touch a command longer than this (DoS + false-positive guard). */
44
+ readonly maxInputChars?: number;
45
+ /** How many decode layers to peel. */
46
+ readonly maxDepth?: number;
47
+ /** Shortest base64 blob considered (below this, too noisy). */
48
+ readonly minBlobChars?: number;
49
+ /** Longest decoded output kept. */
50
+ readonly maxOutputChars?: number;
51
+ /** Hard cap on returned variants. */
52
+ readonly maxVariants?: number;
53
+ /**
54
+ * Wall-clock budget for the whole peel. On expiry **all** partial work is
55
+ * dropped, not just the remaining layers: a decoded fragment found late could
56
+ * turn into a P0 deny, and the guardrail this plugin cannot trade away is that
57
+ * the normalizer never decides — an expired peel leaves the call opaque, so the
58
+ * decision falls back to the ask it was already heading for.
59
+ */
60
+ readonly maxMs?: number;
61
+ /** Injected clock (tests drive the deadline without sleeping). */
62
+ readonly now?: () => number;
63
+ }
64
+ /**
65
+ * Peel encoding layers off one command and return every readable variant.
66
+ *
67
+ * Never throws: any failure yields whatever decoded fine, and an empty result
68
+ * simply means the call stays opaque to the deterministic layers. Exceeding the
69
+ * time budget returns **nothing** rather than the partial set (see `maxMs`).
70
+ */
71
+ export declare function normalizeCommandText(text: string, options?: NormalizeOptions): NormalizedText[];
72
+ /**
73
+ * Whether a command is the plugin's own AppContainer launcher wrapper.
74
+ * Informational only — the payload is still decoded and judged on its merits.
75
+ */
76
+ export declare function isOwnLauncher(text: string): boolean;
77
+ /** Variants strict enough to feed a deterministic deny: the payload actually runs. */
78
+ export declare function executableVariants(variants: readonly NormalizedText[]): string[];
79
+ /** All decoded variants, for the additive-only keyword/tier scans. */
80
+ export declare function allVariants(variants: readonly NormalizedText[]): string[];
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Command normalization — decode the layers a call hides behind, as a scan signal.
3
+ *
4
+ * Why this exists: every deterministic layer reads the command *as written*.
5
+ * `deny-defaults` is a word list, the parsers switch on `baseName`, and
6
+ * `isCleanupSafe` matches path segments. All three are blind to
7
+ * `powershell -EncodedCommand UgBlAG0A...`, to `R="-rf"; rm $R /`, and to a
8
+ * function wrapper around a wipe. Measured on the eval set, the encoded cases are
9
+ * exactly where a graded model ships an unrecoverable act past the gate
10
+ * (`adv_013` decodes to `Remove-Item -Recurse -Force C:\`, `adv_032` to
11
+ * `DROP DATABASE orders_prod`).
12
+ *
13
+ * Two rules keep this from becoming its own vulnerability:
14
+ *
15
+ * 1. **Additive only.** The caller feeds these strings to *stricter* scans
16
+ * (deny-keyword, P0 wipe detection, tier grading). Nothing here can widen a
17
+ * decision, and no result is written back to `exec.arguments` or the event
18
+ * log, so fingerprints, grants and audit text stay about the call as received.
19
+ * 2. **Bounded.** Length caps, depth cap, variant cap and a printable-ratio gate,
20
+ * because decoding is attacker-controlled input on the decision path. Oversized
21
+ * or undecodable input yields nothing, which leaves the call
22
+ * `targetResolvable=false` — opaque, never optimistically safe.
23
+ *
24
+ * On the plugin's own `--exec-b64` launcher wrapper: it is decoded like anything
25
+ * else rather than exempted. An exemption would hand the agent a disguise
26
+ * (`ac-launcher --config x --exec-b64 <rm -rf ...>`), while decoding it can only
27
+ * ever escalate — so the cost is at most a second confirmation, never a block
28
+ * that could not have been reached honestly.
29
+ */
30
+ const DEFAULTS = {
31
+ maxInputChars: 4096,
32
+ maxDepth: 3,
33
+ minBlobChars: 16,
34
+ maxOutputChars: 2048,
35
+ maxVariants: 8,
36
+ maxMs: 25,
37
+ now: Date.now,
38
+ };
39
+ /** Primitives that run a following encoded string / blob as code. */
40
+ const EXEC_PRIMITIVE = /(?:-EncodedCommand|FromBase64String|Invoke-Expression|\biex\b|\beval\b|bash\s+-c|sh\s+-c|pwsh\s+-c|powershell\s+-c|xxd\s+-r|--decode\b)/i;
41
+ /** base64 blobs long enough to be a real payload, quoted or bare. */
42
+ const B64_RE = /["']?([A-Za-z0-9+/]{16,}={0,2})["']?/g;
43
+ /** `\x72\x6d` escapes, or a spaced hex dump. */
44
+ const HEX_ESCAPE_RE = /\\x([0-9a-fA-F]{2})/g;
45
+ const HEX_PAIR_RE = /[0-9a-fA-F]{2}/g;
46
+ /**
47
+ * Simple assignments we can substitute textually: `X="v"`, `$X='v'`, `${X}: v`.
48
+ *
49
+ * The value is matched lazily without escape handling, because a Windows path is
50
+ * the common case and ends in a backslash right before the closing quote
51
+ * (`$t="C:\"`): an escape-aware pattern cannot find that terminator and silently
52
+ * yields nothing, which is the exact blind spot this folding exists to close.
53
+ * Truncating a value that contains a literal quote is acceptable here — variants
54
+ * only ever feed stricter scans.
55
+ */
56
+ const ASSIGN_RE = /\$?\{?([A-Za-z_][A-Za-z0-9_]*)\}?\s*=\s*(["'])(.*?)\2/g;
57
+ /** Share of decoded bytes that must be printable before we trust it as a command. */
58
+ const PRINTABLE_MIN = 0.85;
59
+ function printableRatio(text) {
60
+ if (text === '')
61
+ return 0;
62
+ let ok = 0;
63
+ for (const ch of text) {
64
+ const code = ch.codePointAt(0) ?? 0;
65
+ if (code === 0x09 || code === 0x0a || code === 0x0d || (code >= 0x20 && code !== 0x7f))
66
+ ok += 1;
67
+ }
68
+ return ok / text.length;
69
+ }
70
+ function clip(text, max) {
71
+ return text.length <= max ? text : text.slice(0, max);
72
+ }
73
+ /** Decoded text is only useful if it plausibly is a command, not binary noise. */
74
+ function looksLikeText(text, minPrintable = PRINTABLE_MIN) {
75
+ return text !== '' && printableRatio(text) >= minPrintable && /[A-Za-z]{3}/.test(text);
76
+ }
77
+ /** Decode a blob as one encoding; '' when it is not valid base64 at all. */
78
+ function decodeB64(blob, encoding) {
79
+ try {
80
+ return Buffer.from(blob, 'base64').toString(encoding);
81
+ }
82
+ catch {
83
+ return '';
84
+ }
85
+ }
86
+ /**
87
+ * Decode one base64 blob, or undefined when it is not decodable text.
88
+ *
89
+ * Two readings are attempted because they look identical from outside and both
90
+ * are common:
91
+ * - **utf16le** — `powershell/pwsh -EncodedCommand` and
92
+ * `[Convert]::ToBase64String([Text.Encoding]::Unicode…)` carry UTF-16LE. Read
93
+ * as UTF-8 the result is `R\0e\0m\0o\0v\0e…`, which a printable check alone
94
+ * rejects: a silent false negative on exactly the highest-value case, caught
95
+ * only once the test asserted the decoded text.
96
+ * - **utf8** — POSIX `base64 -d` and generic payloads.
97
+ */
98
+ function tryB64(blob, opts) {
99
+ if (blob.length < opts.minBlobChars)
100
+ return undefined;
101
+ const utf8 = decodeB64(blob, 'utf8');
102
+ const interleavedNull = /\x00/.test(utf8);
103
+ if (!interleavedNull && looksLikeText(utf8))
104
+ return clip(utf8, opts.maxOutputChars);
105
+ const utf16 = decodeB64(blob, 'utf16le');
106
+ if (looksLikeText(utf16))
107
+ return clip(utf16, opts.maxOutputChars);
108
+ if (looksLikeText(utf8))
109
+ return clip(utf8, opts.maxOutputChars);
110
+ return undefined;
111
+ }
112
+ /** Decode `\x72\x6d` escapes or a spaced hex dump into text. */
113
+ function tryHex(blob, opts) {
114
+ const escaped = [];
115
+ HEX_ESCAPE_RE.lastIndex = 0;
116
+ let e = HEX_ESCAPE_RE.exec(blob);
117
+ while (e !== null) {
118
+ escaped.push(parseInt(e[1] ?? '0', 16));
119
+ e = HEX_ESCAPE_RE.exec(blob);
120
+ }
121
+ const bytes = escaped.length > 0 ? escaped : [];
122
+ if (bytes.length === 0) {
123
+ const compact = blob.replace(/\s+/g, '');
124
+ if (compact.length % 2 !== 0)
125
+ return undefined;
126
+ HEX_PAIR_RE.lastIndex = 0;
127
+ let m = HEX_PAIR_RE.exec(compact);
128
+ while (m !== null) {
129
+ bytes.push(parseInt(m[0], 16));
130
+ m = HEX_PAIR_RE.exec(compact);
131
+ }
132
+ }
133
+ if (bytes.length === 0)
134
+ return undefined;
135
+ let hex;
136
+ try {
137
+ hex = Buffer.from(bytes).toString('utf8');
138
+ }
139
+ catch {
140
+ return undefined;
141
+ }
142
+ if (!looksLikeText(hex))
143
+ return undefined;
144
+ return clip(hex, opts.maxOutputChars);
145
+ }
146
+ /**
147
+ * Substitute `X="rm -rf"` / `$X` pairs so `rm $X /` becomes visible as one
148
+ * string. Only literal assignments are folded; nothing is evaluated.
149
+ */
150
+ function expandVars(text, opts) {
151
+ const table = new Map();
152
+ ASSIGN_RE.lastIndex = 0;
153
+ let m = ASSIGN_RE.exec(text);
154
+ while (m !== null) {
155
+ const name = m[1];
156
+ const value = m[3] ?? '';
157
+ if (name !== undefined && value !== '')
158
+ table.set(name, value);
159
+ m = ASSIGN_RE.exec(text);
160
+ }
161
+ if (table.size === 0)
162
+ return undefined;
163
+ let out = text;
164
+ for (const [name, value] of table) {
165
+ // Skip the assignment's own left-hand side (`$t=` …), which would otherwise
166
+ // be rewritten into noise like `C:\="C:\"`; only uses are folded.
167
+ const use = new RegExp(`\\$\\{?${name}\\}?(?!\\s*=)`, 'g');
168
+ out = out.replace(use, () => value);
169
+ }
170
+ return out === text ? undefined : clip(out, opts.maxOutputChars);
171
+ }
172
+ /**
173
+ * Peel encoding layers off one command and return every readable variant.
174
+ *
175
+ * Never throws: any failure yields whatever decoded fine, and an empty result
176
+ * simply means the call stays opaque to the deterministic layers. Exceeding the
177
+ * time budget returns **nothing** rather than the partial set (see `maxMs`).
178
+ */
179
+ export function normalizeCommandText(text, options = {}) {
180
+ const opts = { ...DEFAULTS, ...options };
181
+ if (typeof text !== 'string' || text.trim() === '' || text.length > opts.maxInputChars)
182
+ return [];
183
+ const startedAt = opts.now();
184
+ const spent = () => opts.now() - startedAt;
185
+ const seen = new Set();
186
+ const out = [];
187
+ const push = (variant) => {
188
+ const trimmed = variant.text.trim();
189
+ if (trimmed === '' || seen.has(trimmed))
190
+ return;
191
+ seen.add(trimmed);
192
+ out.push({ ...variant, text: trimmed });
193
+ };
194
+ let frontier = [{ text, executed: false }];
195
+ for (let depth = 0; depth < opts.maxDepth && frontier.length > 0 && out.length < opts.maxVariants; depth += 1) {
196
+ if (spent() > opts.maxMs)
197
+ return [];
198
+ const next = [];
199
+ for (const item of frontier) {
200
+ const runsCode = item.executed || EXEC_PRIMITIVE.test(item.text);
201
+ B64_RE.lastIndex = 0;
202
+ let b = B64_RE.exec(item.text);
203
+ while (b !== null) {
204
+ const decoded = tryB64(b[1] ?? '', opts);
205
+ if (decoded !== undefined) {
206
+ push({ text: decoded, via: 'base64', executed: runsCode });
207
+ next.push({ text: decoded, executed: runsCode });
208
+ }
209
+ b = B64_RE.exec(item.text);
210
+ }
211
+ if (/\\x[0-9a-fA-F]{2}/.test(item.text) || /(?:\b[0-9a-fA-F]{2}\b){6,}/.test(item.text)) {
212
+ const decoded = tryHex(item.text, opts);
213
+ if (decoded !== undefined) {
214
+ push({ text: decoded, via: 'hex', executed: runsCode });
215
+ next.push({ text: decoded, executed: runsCode });
216
+ }
217
+ }
218
+ const expanded = expandVars(item.text, opts);
219
+ if (expanded !== undefined) {
220
+ // A folded variable is the same command the shell will run, not a hidden
221
+ // payload, so it always counts as executed.
222
+ push({ text: expanded, via: 'varsub', executed: true });
223
+ next.push({ text: expanded, executed: true });
224
+ }
225
+ if (out.length >= opts.maxVariants)
226
+ break;
227
+ }
228
+ frontier = next;
229
+ }
230
+ return out.slice(0, opts.maxVariants);
231
+ }
232
+ /**
233
+ * Whether a command is the plugin's own AppContainer launcher wrapper.
234
+ * Informational only — the payload is still decoded and judged on its merits.
235
+ */
236
+ export function isOwnLauncher(text) {
237
+ return /--exec-b64\s+[A-Za-z0-9+/=]{8,}/.test(text) && /--config\s+/.test(text);
238
+ }
239
+ /** Variants strict enough to feed a deterministic deny: the payload actually runs. */
240
+ export function executableVariants(variants) {
241
+ return variants.filter((v) => v.executed).map((v) => v.text);
242
+ }
243
+ /** All decoded variants, for the additive-only keyword/tier scans. */
244
+ export function allVariants(variants) {
245
+ return variants.map((v) => v.text);
246
+ }
package/lib/config.d.ts CHANGED
@@ -95,6 +95,13 @@ export interface PermGateConfig {
95
95
  * (possibly empty) replaces it. Editable from the settings card as a list.
96
96
  */
97
97
  readonly denyKeywords?: string[];
98
+ /**
99
+ * One-shot marker for the preset deny-table seed (`preset-rules.ts`, ported
100
+ * from cc-safety-net): set once the `[deny-rule:*]` entries have been merged
101
+ * into the live rules source. A full revocation (deleting every preset entry)
102
+ * persists — the seed never re-adds them.
103
+ */
104
+ readonly presetRulesSeeded?: boolean;
98
105
  /**
99
106
  * Extra tool names classified as read-only/internal and therefore auto-allowed.
100
107
  *
@@ -332,126 +339,128 @@ export declare function readRulesFromSettings(scope: {
332
339
  export declare const Config: z<Schemastery.ObjectS<NoInfer<{
333
340
  rulesFile: z<string, string, "plain">;
334
341
  dshHome: z<string, string, "plain">;
335
- defaultAction: z<"allow" | "ask" | "deny", "allow" | "ask" | "deny", "volatile-defined">;
336
- caseInsensitivePaths: z<boolean, boolean, "volatile-defined">;
337
- classifierEnabled: z<boolean, boolean, "volatile-defined">;
338
- classifierEndpoint: z<string, string, "volatile">;
339
- classifierModel: z<string, string, "volatile-defined">;
340
- classifierApiKey: z<string, string, "volatile">;
341
- riskTimeoutMs: z<number, number, "volatile-defined">;
342
- riskLearning: z<boolean, boolean, "volatile-defined">;
343
- riskSediment: z<boolean, boolean, "volatile-defined">;
344
- classifierSource: z<"host" | "custom", "host" | "custom", "volatile-defined">;
345
- classifierProvider: z<string, string, "volatile">;
346
- riskThreshold: z<number, number, "volatile-defined">;
342
+ defaultAction: z<"allow" | "ask" | "deny", "allow" | "ask" | "deny", "defined">;
343
+ caseInsensitivePaths: z<boolean, boolean, "defined">;
344
+ classifierEnabled: z<boolean, boolean, "defined">;
345
+ classifierEndpoint: z<string, string, "plain">;
346
+ classifierModel: z<string, string, "defined">;
347
+ classifierApiKey: z<string, string, "plain">;
348
+ riskTimeoutMs: z<number, number, "defined">;
349
+ riskLearning: z<boolean, boolean, "defined">;
350
+ riskSediment: z<boolean, boolean, "defined">;
351
+ classifierSource: z<"host" | "custom", "host" | "custom", "defined">;
352
+ classifierProvider: z<string, string, "plain">;
353
+ riskThreshold: z<number, number, "defined">;
347
354
  learningFile: z<string, string, "plain">;
348
355
  eventsFile: z<string, string, "plain">;
349
- grantTtlMs: z<number, number, "volatile-defined">;
350
- grantMaxUses: z<number, number, "volatile-defined">;
351
- permissive: z<boolean, boolean, "volatile-defined">;
356
+ grantTtlMs: z<number, number, "defined">;
357
+ grantMaxUses: z<number, number, "defined">;
358
+ permissive: z<boolean, boolean, "defined">;
352
359
  gatePresets: z<string[], string[], "plain">;
353
- permissiveStrategies: z<NoInfer<Schemastery.ObjectS<NoInfer<{
360
+ permissiveStrategies: z<Schemastery.ObjectS<NoInfer<{
354
361
  trustAutoAllow: z<boolean, boolean, "defined">;
355
362
  alwaysConfirm: z<boolean, boolean, "defined">;
356
363
  llmAssist: z<boolean, boolean, "defined">;
357
364
  trustEscalation: z<boolean, boolean, "defined">;
358
- }>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
365
+ }>>, Schemastery.ObjectT<NoInfer<{
359
366
  trustAutoAllow: z<boolean, boolean, "defined">;
360
367
  alwaysConfirm: z<boolean, boolean, "defined">;
361
368
  llmAssist: z<boolean, boolean, "defined">;
362
369
  trustEscalation: z<boolean, boolean, "defined">;
363
- }>>>, "volatile">;
364
- allowlist: z<NoInfer<string[]>, NoInfer<string[]>, "volatile">;
365
- denyKeywords: z<NoInfer<string[]>, NoInfer<string[]>, "volatile">;
366
- autoAllowTools: z<NoInfer<string[]>, NoInfer<string[]>, "volatile">;
367
- sessionSweep: z<boolean, boolean, "volatile-defined">;
370
+ }>>, "plain">;
371
+ allowlist: z<string[], string[], "plain">;
372
+ denyKeywords: z<string[], string[], "plain">;
373
+ presetRulesSeeded: z<boolean, boolean, "defined">;
374
+ autoAllowTools: z<string[], string[], "plain">;
375
+ sessionSweep: z<boolean, boolean, "defined">;
368
376
  workspaceStoreFile: z<string, string, "plain">;
369
- searchUp: z<boolean, boolean, "volatile-defined">;
377
+ searchUp: z<boolean, boolean, "defined">;
370
378
  fallbackPath: z<string, string, "plain">;
371
- badFilePolicy: z<"fail" | "warn", "fail" | "warn", "volatile-defined">;
372
- maxChainLength: z<number, number, "volatile-defined">;
373
- networkEnabled: z<boolean, boolean, "volatile-defined">;
374
- networkMode: z<"deny-all" | "whitelist" | "allow-all", "deny-all" | "whitelist" | "allow-all", "volatile-defined">;
375
- networkUnlisted: z<"ask" | "deny", "ask" | "deny", "volatile-defined">;
376
- networkUnattributed: z<"allow" | "deny", "allow" | "deny", "volatile-defined">;
377
- networkLoopback: z<"allow" | "policy", "allow" | "policy", "volatile-defined">;
378
- networkBind: z<string, string, "volatile-defined">;
379
- networkPort: z<number, number, "volatile-defined">;
380
- networkNoProxy: z<"clear" | "preserve", "clear" | "preserve", "volatile-defined">;
381
- networkInjectEnv: z<boolean, boolean, "volatile-defined">;
382
- networkAskTimeoutMs: z<number, number, "volatile-defined">;
383
- networkGrantTtlMs: z<number, number, "volatile-defined">;
384
- sandboxEnabled: z<boolean, boolean, "volatile-defined">;
385
- sandboxMode: z<"read-only" | "workspace-write", "read-only" | "workspace-write", "volatile-defined">;
386
- sandboxProxy: z<string, string, "volatile-defined">;
387
- sandboxLoopback: z<"exempt" | "off", "exempt" | "off", "volatile-defined">;
388
- rules: z<NoInfer<RulesConfig>, NoInfer<RulesConfig>, "volatile">;
379
+ badFilePolicy: z<"fail" | "warn", "fail" | "warn", "defined">;
380
+ maxChainLength: z<number, number, "defined">;
381
+ networkEnabled: z<boolean, boolean, "defined">;
382
+ networkMode: z<"deny-all" | "whitelist" | "allow-all", "deny-all" | "whitelist" | "allow-all", "defined">;
383
+ networkUnlisted: z<"ask" | "deny", "ask" | "deny", "defined">;
384
+ networkUnattributed: z<"allow" | "deny", "allow" | "deny", "defined">;
385
+ networkLoopback: z<"allow" | "policy", "allow" | "policy", "defined">;
386
+ networkBind: z<string, string, "defined">;
387
+ networkPort: z<number, number, "defined">;
388
+ networkNoProxy: z<"clear" | "preserve", "clear" | "preserve", "defined">;
389
+ networkInjectEnv: z<boolean, boolean, "defined">;
390
+ networkAskTimeoutMs: z<number, number, "defined">;
391
+ networkGrantTtlMs: z<number, number, "defined">;
392
+ sandboxEnabled: z<boolean, boolean, "defined">;
393
+ sandboxMode: z<"read-only" | "workspace-write", "read-only" | "workspace-write", "defined">;
394
+ sandboxProxy: z<string, string, "defined">;
395
+ sandboxLoopback: z<"exempt" | "off", "exempt" | "off", "defined">;
396
+ rules: z<RulesConfig>;
389
397
  watch: z<boolean, boolean, "defined">;
390
398
  watchDebounceMs: z<number, number, "defined">;
391
399
  }>>, Schemastery.ObjectT<NoInfer<{
392
400
  rulesFile: z<string, string, "plain">;
393
401
  dshHome: z<string, string, "plain">;
394
- defaultAction: z<"allow" | "ask" | "deny", "allow" | "ask" | "deny", "volatile-defined">;
395
- caseInsensitivePaths: z<boolean, boolean, "volatile-defined">;
396
- classifierEnabled: z<boolean, boolean, "volatile-defined">;
397
- classifierEndpoint: z<string, string, "volatile">;
398
- classifierModel: z<string, string, "volatile-defined">;
399
- classifierApiKey: z<string, string, "volatile">;
400
- riskTimeoutMs: z<number, number, "volatile-defined">;
401
- riskLearning: z<boolean, boolean, "volatile-defined">;
402
- riskSediment: z<boolean, boolean, "volatile-defined">;
403
- classifierSource: z<"host" | "custom", "host" | "custom", "volatile-defined">;
404
- classifierProvider: z<string, string, "volatile">;
405
- riskThreshold: z<number, number, "volatile-defined">;
402
+ defaultAction: z<"allow" | "ask" | "deny", "allow" | "ask" | "deny", "defined">;
403
+ caseInsensitivePaths: z<boolean, boolean, "defined">;
404
+ classifierEnabled: z<boolean, boolean, "defined">;
405
+ classifierEndpoint: z<string, string, "plain">;
406
+ classifierModel: z<string, string, "defined">;
407
+ classifierApiKey: z<string, string, "plain">;
408
+ riskTimeoutMs: z<number, number, "defined">;
409
+ riskLearning: z<boolean, boolean, "defined">;
410
+ riskSediment: z<boolean, boolean, "defined">;
411
+ classifierSource: z<"host" | "custom", "host" | "custom", "defined">;
412
+ classifierProvider: z<string, string, "plain">;
413
+ riskThreshold: z<number, number, "defined">;
406
414
  learningFile: z<string, string, "plain">;
407
415
  eventsFile: z<string, string, "plain">;
408
- grantTtlMs: z<number, number, "volatile-defined">;
409
- grantMaxUses: z<number, number, "volatile-defined">;
410
- permissive: z<boolean, boolean, "volatile-defined">;
416
+ grantTtlMs: z<number, number, "defined">;
417
+ grantMaxUses: z<number, number, "defined">;
418
+ permissive: z<boolean, boolean, "defined">;
411
419
  gatePresets: z<string[], string[], "plain">;
412
- permissiveStrategies: z<NoInfer<Schemastery.ObjectS<NoInfer<{
420
+ permissiveStrategies: z<Schemastery.ObjectS<NoInfer<{
413
421
  trustAutoAllow: z<boolean, boolean, "defined">;
414
422
  alwaysConfirm: z<boolean, boolean, "defined">;
415
423
  llmAssist: z<boolean, boolean, "defined">;
416
424
  trustEscalation: z<boolean, boolean, "defined">;
417
- }>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
425
+ }>>, Schemastery.ObjectT<NoInfer<{
418
426
  trustAutoAllow: z<boolean, boolean, "defined">;
419
427
  alwaysConfirm: z<boolean, boolean, "defined">;
420
428
  llmAssist: z<boolean, boolean, "defined">;
421
429
  trustEscalation: z<boolean, boolean, "defined">;
422
- }>>>, "volatile">;
423
- allowlist: z<NoInfer<string[]>, NoInfer<string[]>, "volatile">;
424
- denyKeywords: z<NoInfer<string[]>, NoInfer<string[]>, "volatile">;
425
- autoAllowTools: z<NoInfer<string[]>, NoInfer<string[]>, "volatile">;
426
- sessionSweep: z<boolean, boolean, "volatile-defined">;
430
+ }>>, "plain">;
431
+ allowlist: z<string[], string[], "plain">;
432
+ denyKeywords: z<string[], string[], "plain">;
433
+ presetRulesSeeded: z<boolean, boolean, "defined">;
434
+ autoAllowTools: z<string[], string[], "plain">;
435
+ sessionSweep: z<boolean, boolean, "defined">;
427
436
  workspaceStoreFile: z<string, string, "plain">;
428
- searchUp: z<boolean, boolean, "volatile-defined">;
437
+ searchUp: z<boolean, boolean, "defined">;
429
438
  fallbackPath: z<string, string, "plain">;
430
- badFilePolicy: z<"fail" | "warn", "fail" | "warn", "volatile-defined">;
431
- maxChainLength: z<number, number, "volatile-defined">;
432
- networkEnabled: z<boolean, boolean, "volatile-defined">;
433
- networkMode: z<"deny-all" | "whitelist" | "allow-all", "deny-all" | "whitelist" | "allow-all", "volatile-defined">;
434
- networkUnlisted: z<"ask" | "deny", "ask" | "deny", "volatile-defined">;
435
- networkUnattributed: z<"allow" | "deny", "allow" | "deny", "volatile-defined">;
436
- networkLoopback: z<"allow" | "policy", "allow" | "policy", "volatile-defined">;
437
- networkBind: z<string, string, "volatile-defined">;
438
- networkPort: z<number, number, "volatile-defined">;
439
- networkNoProxy: z<"clear" | "preserve", "clear" | "preserve", "volatile-defined">;
440
- networkInjectEnv: z<boolean, boolean, "volatile-defined">;
441
- networkAskTimeoutMs: z<number, number, "volatile-defined">;
442
- networkGrantTtlMs: z<number, number, "volatile-defined">;
443
- sandboxEnabled: z<boolean, boolean, "volatile-defined">;
444
- sandboxMode: z<"read-only" | "workspace-write", "read-only" | "workspace-write", "volatile-defined">;
445
- sandboxProxy: z<string, string, "volatile-defined">;
446
- sandboxLoopback: z<"exempt" | "off", "exempt" | "off", "volatile-defined">;
447
- rules: z<NoInfer<RulesConfig>, NoInfer<RulesConfig>, "volatile">;
439
+ badFilePolicy: z<"fail" | "warn", "fail" | "warn", "defined">;
440
+ maxChainLength: z<number, number, "defined">;
441
+ networkEnabled: z<boolean, boolean, "defined">;
442
+ networkMode: z<"deny-all" | "whitelist" | "allow-all", "deny-all" | "whitelist" | "allow-all", "defined">;
443
+ networkUnlisted: z<"ask" | "deny", "ask" | "deny", "defined">;
444
+ networkUnattributed: z<"allow" | "deny", "allow" | "deny", "defined">;
445
+ networkLoopback: z<"allow" | "policy", "allow" | "policy", "defined">;
446
+ networkBind: z<string, string, "defined">;
447
+ networkPort: z<number, number, "defined">;
448
+ networkNoProxy: z<"clear" | "preserve", "clear" | "preserve", "defined">;
449
+ networkInjectEnv: z<boolean, boolean, "defined">;
450
+ networkAskTimeoutMs: z<number, number, "defined">;
451
+ networkGrantTtlMs: z<number, number, "defined">;
452
+ sandboxEnabled: z<boolean, boolean, "defined">;
453
+ sandboxMode: z<"read-only" | "workspace-write", "read-only" | "workspace-write", "defined">;
454
+ sandboxProxy: z<string, string, "defined">;
455
+ sandboxLoopback: z<"exempt" | "off", "exempt" | "off", "defined">;
456
+ rules: z<RulesConfig>;
448
457
  watch: z<boolean, boolean, "defined">;
449
458
  watchDebounceMs: z<number, number, "defined">;
450
459
  }>>, "plain">;
451
460
  /** Live reference the 0.1.7 loader hands `apply` for `.volatile()` config fields. */
452
461
  export type VolatileRef<T> = Volatile<T>;
453
462
  /** The config fields marked `.volatile()` — live refs inside `apply`'s config. */
454
- export declare const VOLATILE_CONFIG_KEYS: readonly ["defaultAction", "caseInsensitivePaths", "classifierEnabled", "classifierEndpoint", "classifierModel", "classifierApiKey", "riskTimeoutMs", "riskLearning", "riskSediment", "classifierSource", "classifierProvider", "riskThreshold", "grantTtlMs", "grantMaxUses", "permissive", "permissiveStrategies", "allowlist", "denyKeywords", "autoAllowTools", "sessionSweep", "searchUp", "badFilePolicy", "maxChainLength", "networkEnabled", "networkMode", "networkUnlisted", "networkUnattributed", "networkLoopback", "networkBind", "networkPort", "networkNoProxy", "networkInjectEnv", "networkAskTimeoutMs", "networkGrantTtlMs", "rules", "sandboxEnabled", "sandboxMode", "sandboxProxy", "sandboxLoopback"];
463
+ export declare const VOLATILE_CONFIG_KEYS: readonly ["defaultAction", "caseInsensitivePaths", "classifierEnabled", "classifierEndpoint", "classifierModel", "classifierApiKey", "riskTimeoutMs", "riskLearning", "riskSediment", "classifierSource", "classifierProvider", "riskThreshold", "grantTtlMs", "grantMaxUses", "permissive", "permissiveStrategies", "allowlist", "denyKeywords", "presetRulesSeeded", "autoAllowTools", "sessionSweep", "searchUp", "badFilePolicy", "maxChainLength", "networkEnabled", "networkMode", "networkUnlisted", "networkUnattributed", "networkLoopback", "networkBind", "networkPort", "networkNoProxy", "networkInjectEnv", "networkAskTimeoutMs", "networkGrantTtlMs", "rules", "sandboxEnabled", "sandboxMode", "sandboxProxy", "sandboxLoopback"];
455
464
  /** Resolve one possibly-volatile field: a live ref on 0.1.7+, a plain value otherwise. */
456
465
  export declare function readVolatileValue<T>(value: T | Volatile<T> | undefined): VolatileSnapshot<T> | undefined;
457
466
  /** Shallow-resolve every volatile field into a plain snapshot (one per read). */