@alvin0/ai-agent-sdk-sandbox 0.1.3

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,731 @@
1
+ //#region src/entries.d.ts
2
+ /**
3
+ * Nested filesystem carve-outs.
4
+ *
5
+ * A single writable root is not enough: an agent that may write in a repository
6
+ * must still be kept out of `.git`, and an operator must be able to reopen one
7
+ * directory beneath a denied parent. Entries express that as an overlapping
8
+ * list resolved by path specificity — the deepest matching entry wins, so
9
+ * `/repo = write`, `/repo/a = deny`, `/repo/a/b = write` behaves as written.
10
+ */
11
+ /** What one entry grants for the subtree it names. */
12
+ type FileSystemAccess = 'write' | 'read' | 'deny';
13
+ /** One carve-out in a filesystem policy. */
14
+ interface FileSystemEntry {
15
+ /** Absolute path whose subtree this entry governs. */
16
+ readonly path: string;
17
+ /** Access granted for that subtree, overriding any broader entry. */
18
+ readonly access: FileSystemAccess;
19
+ }
20
+ /**
21
+ * Order entries from broadest to narrowest so a consumer can apply them in
22
+ * sequence and let the most specific one win. Equal-depth entries keep a stable
23
+ * lexical order so a policy always produces the same backend profile.
24
+ */
25
+ declare function orderEntries(entries: readonly FileSystemEntry[]): readonly FileSystemEntry[];
26
+ /**
27
+ * Resolve the effective access for one target against an ordered entry list.
28
+ * @param target - absolute path being evaluated.
29
+ * @param entries - carve-outs, in any order.
30
+ * @param fallback - access to use when no entry covers the target.
31
+ */
32
+ declare function accessFor(target: string, entries: readonly FileSystemEntry[], fallback: FileSystemAccess): FileSystemAccess;
33
+ /**
34
+ * Entries that carve a narrower rule *inside* `root`. A backend that grants
35
+ * `root` wholesale must re-apply these afterwards or the grant is too wide.
36
+ */
37
+ declare function entriesWithin(root: string, entries: readonly FileSystemEntry[]): readonly FileSystemEntry[];
38
+ //#endregion
39
+ //#region src/mode.d.ts
40
+ /** File-effect vocabulary shared by every sandbox backend and consumer. */
41
+ /**
42
+ * File-effect policy for confined processes. `read-only` permits only the sinks
43
+ * a shell requires (`/dev/null` and its platform equivalent); `workspace-write`
44
+ * additionally permits the workspace root and a backend-defined temp area;
45
+ * `danger-full-access` bypasses confinement entirely.
46
+ *
47
+ * Network reachability and process visibility are deliberately outside this
48
+ * vocabulary — they are governed by their own seam, not by a file-effect mode.
49
+ */
50
+ type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';
51
+ /** A confining mode — the only modes a provider can be asked to enforce. */
52
+ type ConfinedSandboxMode = Exclude<SandboxMode, 'danger-full-access'>;
53
+ /**
54
+ * Enforcement completeness for this host, reported as a fact rather than
55
+ * promised. `partial` means the selected backend cannot govern every file
56
+ * effect the mode promises; `fence-only` means no process confinement exists on
57
+ * this platform and just the in-process path fence applies, so a consumer that
58
+ * needs a kernel boundary must reject it rather than treat it as enforcement.
59
+ */
60
+ type SandboxEnforcement = 'full' | 'partial' | 'fence-only';
61
+ /** Every mode, in widening order of authority. */
62
+ declare const SANDBOX_MODES: readonly SandboxMode[];
63
+ /** Whether an arbitrary value is one of the known modes. */
64
+ declare function isSandboxMode(value: unknown): value is SandboxMode;
65
+ /** Whether a mode still asks a provider to confine the execution. */
66
+ declare function isConfinedMode(mode: SandboxMode): mode is ConfinedSandboxMode;
67
+ /** Rank used when a narrower mode must not be widened by a weaker source. */
68
+ declare function modeAuthority(mode: SandboxMode): number;
69
+ //#endregion
70
+ //#region src/network.d.ts
71
+ /**
72
+ * Network reachability — a seam of its own, deliberately not a file-effect mode.
73
+ *
74
+ * `SandboxMode` governs file effects and says so. Folding network reachability
75
+ * into it would make the mode claim something it does not decide, and a mode
76
+ * that lies about its scope is worse than one with a narrow one. So network is
77
+ * a second, independent axis carried on the same policy: a call can be
78
+ * `read-only` on the filesystem and still reach the internet, or writable in
79
+ * its workspace and reach nothing.
80
+ *
81
+ * The distinction matters because the two are enforced by different mechanisms
82
+ * — mount bindings versus a network namespace — and a host can provide one
83
+ * without the other.
84
+ */
85
+ /**
86
+ * What a confined execution may reach.
87
+ *
88
+ * `loopback` is not a weaker `deny`: it is what a command needs when the
89
+ * deployment runs a proxy or a language server it is meant to talk to, and
90
+ * nothing else. On a host whose only mechanism is a network namespace the two
91
+ * enforce identically, which the enforcement report says rather than hides.
92
+ */
93
+ type NetworkMode = 'deny' | 'loopback' | 'allow-all';
94
+ /** Every network mode, in widening order of reach. */
95
+ declare const NETWORK_MODES: readonly NetworkMode[];
96
+ /** How completely a backend enforces a network mode. */
97
+ type NetworkEnforcement = 'full' | 'loopback-only' | 'none';
98
+ /** Whether an arbitrary value is one of the known network modes. */
99
+ declare function isNetworkMode(value: unknown): value is NetworkMode;
100
+ /** Rank used so an untrusted request can narrow reach but never widen it. */
101
+ declare function networkAuthority(mode: NetworkMode): number;
102
+ /**
103
+ * Narrow a network mode toward a stricter one, refusing to widen.
104
+ * @param ceiling - the reach already permitted.
105
+ * @param requested - the reach being asked for.
106
+ * @returns the stricter of the two.
107
+ */
108
+ declare function narrowNetwork(ceiling: NetworkMode, requested: NetworkMode | undefined): NetworkMode;
109
+ //#endregion
110
+ //#region src/approval.d.ts
111
+ /** How long a minted approval remains usable. */
112
+ type SandboxApprovalScope = 'single-call' | 'session';
113
+ /** What an approval permits, beyond what the session already allows. */
114
+ interface SandboxApprovalGrant {
115
+ /**
116
+ * Whether the approval survives the call it was minted for.
117
+ *
118
+ * `single-call` — the default — is consumed the first time a policy is
119
+ * resolved with it, so a grant given for one operation cannot be replayed
120
+ * for the next. A person approving "read this file" approved one read.
121
+ */
122
+ readonly scope?: SandboxApprovalScope;
123
+ /** Wall-clock deadline in epoch milliseconds, after which it is refused. */
124
+ readonly expiresAt?: number;
125
+ /** Mode the approval raises this call to. */
126
+ readonly mode?: SandboxMode;
127
+ /** Entries the approval may add, including widening ones. */
128
+ readonly entries?: readonly FileSystemEntry[];
129
+ /** Network reach the approval raises this call to. */
130
+ readonly network?: NetworkMode;
131
+ /** Free-text reason, carried for audit; never interpreted. */
132
+ readonly justification?: string;
133
+ }
134
+ /**
135
+ * An approval token. Structurally it is just its grant, but only a value minted
136
+ * by {@link approveSandboxEscalation} is accepted — a look-alike object, however
137
+ * carefully shaped, is refused.
138
+ */
139
+ interface SandboxApproval extends SandboxApprovalGrant {
140
+ readonly approved: true;
141
+ }
142
+ /**
143
+ * Mint an approval. Call this only after the host has actually authorized the
144
+ * escalation; the SDK cannot tell an approved grant from a requested one, which
145
+ * is precisely why this call has to be the place the distinction is made.
146
+ */
147
+ declare function approveSandboxEscalation(grant: SandboxApprovalGrant): SandboxApproval;
148
+ /** Whether a value is an approval this module minted. */
149
+ declare function isSandboxApproval(value: unknown): value is SandboxApproval;
150
+ /** Whether a minted approval has already been used up. */
151
+ declare function isSandboxApprovalSpent(approval: SandboxApproval): boolean;
152
+ /**
153
+ * Accept an approval, or refuse it loudly.
154
+ * @throws SandboxPolicyError when the value was not minted here — which is what
155
+ * a forged approval arriving through a tool payload looks like.
156
+ */
157
+ declare function requireSandboxApproval(value: unknown, now?: number): SandboxApproval;
158
+ //#endregion
159
+ //#region src/exec.d.ts
160
+ /**
161
+ * Reading a command for what it *does*, not just what it touches.
162
+ *
163
+ * The file seam cannot tell `systemctl status nginx` from `systemctl restart
164
+ * nginx`: both are argv, neither writes a file the policy cares about, and one
165
+ * observes while the other changes the machine. Deciding between them needs the
166
+ * command read semantically, before any of it runs.
167
+ *
168
+ * This is a classifier, and a classifier is a guess. Two rules keep the guess
169
+ * from becoming a hazard: a command it does not recognise is never allowed, and
170
+ * a command that hides other commands — a shell string, a pipeline, a chain —
171
+ * is classified by the riskiest thing inside it rather than by its wrapper.
172
+ */
173
+ /** What a command does to the machine, in increasing order of consequence. */
174
+ type ExecCapability = 'observe' | 'use' | 'modify' | 'service-control' | 'package-install' | 'privilege' | 'credential' | 'critical' | 'unknown';
175
+ /** What a harness should do with a command carrying that capability. */
176
+ type ExecOutcome = 'allow' | 'allow-scoped' | 'ask-approval' | 'deny';
177
+ /** One classified command, and why it was classified that way. */
178
+ interface ExecClassification {
179
+ readonly capability: ExecCapability;
180
+ readonly outcome: ExecOutcome;
181
+ /** The program the decision was made about, after unwrapping. */
182
+ readonly program: string;
183
+ /** Why, in terms a person approving it can check. */
184
+ readonly reason: string;
185
+ /** Every command found inside a shell string or chain, already classified. */
186
+ readonly parts: readonly ExecClassification[];
187
+ }
188
+ /** The outcome each capability maps to, before a deployment adjusts it. */
189
+ declare const DEFAULT_EXEC_OUTCOMES: Readonly<Record<ExecCapability, ExecOutcome>>;
190
+ /**
191
+ * Classify one command.
192
+ * @param argv - the exact argv, program first. A shell string is read through.
193
+ * @param outcomes - the capability-to-outcome mapping a deployment uses.
194
+ */
195
+ declare function classifyExec(argv: readonly string[], outcomes?: Readonly<Record<ExecCapability, ExecOutcome>>): ExecClassification;
196
+ /**
197
+ * Every command an argv actually runs.
198
+ *
199
+ * A shell invocation carries its real command in a string, and that string can
200
+ * hold several. Splitting it with a pattern cannot work: `grep -E 'a|b'` puts a
201
+ * separator inside a quoted word, and a pattern either splits there — inventing
202
+ * commands out of a regex — or refuses to split anywhere a quote appears. The
203
+ * script is therefore walked one character at a time, so a separator only
204
+ * separates when nothing is quoting it.
205
+ */
206
+ declare function splitCommands(argv: readonly string[]): readonly (readonly string[])[];
207
+ /**
208
+ * Split a shell script into commands, respecting quotes and escapes.
209
+ *
210
+ * Deliberately not a shell parser: it does not expand, substitute, or
211
+ * understand control flow. It answers one question — which words belong to
212
+ * which command — and leaves the rest to the classifier, which treats anything
213
+ * it cannot read as something to ask about.
214
+ */
215
+ declare function tokenizeScript(script: string): readonly (readonly string[])[];
216
+ //#endregion
217
+ //#region src/errors.d.ts
218
+ /** Fail-closed error surface. Silent unconfined passthrough is never legal. */
219
+ /** Thrown when no backend on this host can enforce a confining policy. */
220
+ declare class SandboxUnavailableError extends Error {
221
+ readonly platform: string;
222
+ readonly code: 'SANDBOX_UNAVAILABLE';
223
+ /** Runner ids that were considered for this platform, in chain order. */
224
+ readonly attempted: readonly string[];
225
+ constructor(platform: string, attempted?: readonly string[], detail?: string);
226
+ }
227
+ /** Thrown by the in-process fence when a mutation leaves the permitted roots. */
228
+ declare class SandboxDeniedError extends Error {
229
+ readonly path: string;
230
+ readonly mode: string;
231
+ readonly writableRoots: readonly string[];
232
+ readonly code: 'SANDBOX_DENIED';
233
+ constructor(path: string, mode: string, writableRoots: readonly string[]);
234
+ }
235
+ /** Thrown when a policy is structurally unusable before any backend is asked. */
236
+ declare class SandboxPolicyError extends Error {
237
+ readonly code: 'SANDBOX_POLICY_INVALID';
238
+ constructor(message: string);
239
+ }
240
+ //#endregion
241
+ //#region src/policy.d.ts
242
+ /** The complete file-effect policy resolved for one capability call. */
243
+ interface SandboxExecutionPolicy {
244
+ /** The file-effect mode this execution runs under. */
245
+ readonly mode: SandboxMode;
246
+ /** Absolute root directory `workspace-write` may write under. */
247
+ readonly workspaceRoot: string;
248
+ /** Nested carve-outs layered over the mode's base grant. */
249
+ readonly entries?: readonly FileSystemEntry[];
250
+ /** Untrusted per-call carve-outs, kept separate so they can only intersect. */
251
+ readonly restrictions?: readonly FileSystemEntry[];
252
+ /** Carve-outs carried by a minted approval and therefore allowed to widen. */
253
+ readonly approvedEntries?: readonly FileSystemEntry[];
254
+ /**
255
+ * The access in force where no layer applies.
256
+ *
257
+ * `read` — the default — means the host is readable and a policy closes
258
+ * paths one at a time, which is a deny-list: it protects what someone
259
+ * remembered to name. `deny` inverts that into an allow-list, where nothing
260
+ * is readable until an entry says so, and a path nobody thought about is
261
+ * closed rather than open.
262
+ */
263
+ readonly baseline?: FileSystemAccess;
264
+ /**
265
+ * What this execution may reach over the network. Independent of
266
+ * {@link SandboxExecutionPolicy.mode}, which governs file effects only.
267
+ */
268
+ readonly network?: NetworkMode;
269
+ /** Opaque calling-session identity; backends key per-session state off it. */
270
+ readonly sessionId?: string;
271
+ }
272
+ /**
273
+ * A policy narrowed to a confining mode — the only shape a provider accepts.
274
+ * Resolution happens at the consumer boundary; the provider treats what it
275
+ * receives as fully specified and never re-defaults anything.
276
+ */
277
+ interface SandboxPolicy extends SandboxExecutionPolicy {
278
+ readonly mode: ConfinedSandboxMode;
279
+ }
280
+ /**
281
+ * Inputs that select the policy for one capability call.
282
+ *
283
+ * `mode` and `entries` are the UNTRUSTED half: they arrive from whatever asked
284
+ * for the execution, which in an agent is a model-authored tool payload. They
285
+ * may only narrow. Widening lives behind {@link approval}, which a tool payload
286
+ * cannot contain because it is a capability rather than data.
287
+ */
288
+ interface SandboxPolicyRequest {
289
+ /**
290
+ * The mode the caller asks for. Honoured only when it is at least as strict
291
+ * as the session's own mode — a request can tighten its own execution, never
292
+ * loosen it.
293
+ */
294
+ readonly mode?: SandboxMode;
295
+ /** The calling session's mode, as last logged for that session. */
296
+ readonly sessionMode?: SandboxMode;
297
+ /** The calling session's immutable cwd; becomes the workspace boundary. */
298
+ readonly cwd?: string;
299
+ /**
300
+ * Carve-outs the caller asks for. Restrictions only: a `write` entry here is
301
+ * an escalation attempt and is refused, because granting write to `.git` or
302
+ * `~/.ssh` defeats the boundary just as completely as raising the mode.
303
+ */
304
+ readonly entries?: readonly FileSystemEntry[];
305
+ /** The network reach the caller asks for; honoured only when it narrows. */
306
+ readonly network?: NetworkMode;
307
+ /** An approval minted by `approveSandboxEscalation`; the only way to widen. */
308
+ readonly approval?: SandboxApproval;
309
+ /** Opaque calling-session identity. */
310
+ readonly sessionId?: string;
311
+ }
312
+ /** Deployment-level fallbacks applied when a request omits them. */
313
+ interface SandboxPolicyDefaults {
314
+ /** Mode for calls that carry neither an override nor a session mode. */
315
+ readonly mode: SandboxMode;
316
+ /** Workspace root for agentless calls and sessions without a cwd. */
317
+ readonly workspaceRoot: string;
318
+ /** Carve-outs that always apply, before request-supplied ones. */
319
+ readonly entries?: readonly FileSystemEntry[];
320
+ /** Access where no layer applies; see {@link SandboxExecutionPolicy.baseline}. */
321
+ readonly baseline?: FileSystemAccess;
322
+ /**
323
+ * Network reach for calls that do not narrow it. Defaults to `allow-all`,
324
+ * which is what this package did before the seam existed; a deployment
325
+ * running anything untrusted should set `deny` and widen per call.
326
+ */
327
+ readonly network?: NetworkMode;
328
+ }
329
+ /**
330
+ * Resolve the complete policy for one capability call.
331
+ *
332
+ * Authority only ever decreases across untrusted inputs: the deployment default
333
+ * and the session's mode set a ceiling, a request may narrow beneath it, and a
334
+ * minted approval is the single path that raises it. A session cwd is its
335
+ * `workspace-write` boundary; the configured root is the fallback for agentless
336
+ * calls and sessions without a cwd.
337
+ * @param request - the calling session's untrusted ask, plus any approval.
338
+ * @param defaults - deployment mode, workspace root, and standing carve-outs.
339
+ * @throws SandboxPolicyError when a request tries to widen without an approval.
340
+ */
341
+ declare function resolveSandboxPolicy(request: SandboxPolicyRequest, defaults: SandboxPolicyDefaults): SandboxExecutionPolicy;
342
+ /**
343
+ * Narrow a resolved policy to the confining shape a provider accepts.
344
+ * @returns the confining policy, or `undefined` under `danger-full-access`,
345
+ * whose consumer spawns its original argv and never calls the provider.
346
+ */
347
+ declare function confiningPolicy(policy: SandboxExecutionPolicy): SandboxPolicy | undefined;
348
+ /**
349
+ * Narrow a policy toward a stricter mode without widening it. Used where a
350
+ * consumer may tighten a caller's policy but must never loosen it.
351
+ */
352
+ declare function narrowPolicy(policy: SandboxExecutionPolicy, mode: SandboxMode): SandboxExecutionPolicy;
353
+ //#endregion
354
+ //#region src/classify.d.ts
355
+ /**
356
+ * Outcome classification.
357
+ *
358
+ * Two failures look alike in a shell but mean opposite things. A *denial* means
359
+ * confinement worked and blocked the command. A *runner failure* means the
360
+ * sandbox itself refused or crashed and the command never ran at all. Reporting
361
+ * the second as the first sends a model off rewriting correct code, so runner
362
+ * failure is always checked first and never inferred from an exit code alone.
363
+ */
364
+ /** Evidence that a runner failed before it executed the wrapped command. */
365
+ interface RunnerFailureRule {
366
+ /** Nonzero exits this rule may match; omitted permits any nonzero exit. */
367
+ readonly allowedExitCodes?: readonly number[];
368
+ /** Case-insensitive substrings identifying a fatal runner diagnostic. */
369
+ readonly fatalSignatures: readonly string[];
370
+ /** Benign lines removed by exact, case-insensitive full-line equality first. */
371
+ readonly informationalLines?: readonly string[];
372
+ /**
373
+ * Substrings that disqualify a line from proving runner failure, applied
374
+ * before {@link fatalSignatures}. A runner reports the child's failed `exec`
375
+ * under its own name and often its own exit code, so the prefix that
376
+ * identifies its diagnostics also matches an ordinary missing program; this
377
+ * is how that one case is carved back out.
378
+ */
379
+ readonly excludedSignatures?: readonly string[];
380
+ }
381
+ /** What a finished confined command turned out to be. */
382
+ type SandboxOutcomeKind = 'success' | 'runner-failure' | 'denied' | 'command-failure';
383
+ /** The observable result of spawning a confined argv. */
384
+ interface CommandOutcome {
385
+ /** Process exit code; a signalled process reports its conventional code. */
386
+ readonly exitCode: number;
387
+ /** Stderr text as produced, never rewritten by classification. */
388
+ readonly stderr: string;
389
+ /** Terminating signal name, when the host reports one. */
390
+ readonly signal?: string | null;
391
+ /**
392
+ * Whether the runner reported, on a channel of its own, that it executed the
393
+ * command. Stderr cannot answer this: the runner and the command share that
394
+ * stream, so a command can print the runner's fatal signature and exit with
395
+ * the runner's code, and claim it never ran. When this is `true` no
396
+ * runner-failure rule applies, because the claim is already contradicted by
397
+ * evidence the command could not write.
398
+ */
399
+ readonly childStarted?: boolean;
400
+ }
401
+ /** Evidence a consumer needs to classify one confined command's outcome. */
402
+ interface SandboxClassificationInput {
403
+ /** Denial dialect of the backend that actually wrapped this command. */
404
+ readonly denialSignatures: readonly string[];
405
+ /** Structured runner-failure evidence for that same backend. */
406
+ readonly runnerFailureRules: readonly RunnerFailureRule[];
407
+ }
408
+ /** A classified outcome plus the stderr line that proved it. */
409
+ interface SandboxClassification {
410
+ readonly kind: SandboxOutcomeKind;
411
+ /** The stderr line that matched, kept verbatim for the caller to surface. */
412
+ readonly evidence?: string;
413
+ }
414
+ /**
415
+ * Classify one confined command's outcome.
416
+ *
417
+ * Runner failure is tested first, then a seccomp kill (deterministic, no text
418
+ * matching needed), then the backend's own denial dialect. A cross-backend
419
+ * union of denial strings is deliberately not used: it would claim denials a
420
+ * given backend never produces.
421
+ */
422
+ declare function classifyOutcome(outcome: CommandOutcome, input: SandboxClassificationInput): SandboxClassification;
423
+ /**
424
+ * Append a short, factual note to stderr explaining a sandbox outcome, so a
425
+ * reader never has to infer confinement from a bare error string.
426
+ */
427
+ declare function annotateStderr(stderr: string, classification: SandboxClassification, mode: string): string;
428
+ //#endregion
429
+ //#region src/provider.d.ts
430
+ /** The argv to spawn in place of the caller's own, plus how to read its result. */
431
+ interface ConfinedArgv {
432
+ /** The wrapped argv: runner, profile arguments, separator, caller's argv. */
433
+ readonly argv: readonly string[];
434
+ /** How completely the selected backend enforces this policy's file effects. */
435
+ readonly enforcement: SandboxEnforcement;
436
+ /**
437
+ * How completely it enforces the policy's network reach. `none` means the
438
+ * command can reach whatever the host can, whatever the policy asked for —
439
+ * a separate fact from file enforcement, because a host can provide one
440
+ * mechanism and not the other.
441
+ */
442
+ readonly networkEnforcement: NetworkEnforcement;
443
+ /** Identifier of the backend that produced this wrap. */
444
+ readonly backend: string;
445
+ /**
446
+ * The selected backend's denial DIALECT: the stderr substrings a file effect
447
+ * denied by THIS backend produces. Matched instead of a cross-backend union,
448
+ * which would claim denials this backend never emits.
449
+ */
450
+ readonly denialSignatures: readonly string[];
451
+ /** Structured evidence that the runner failed before the command ran. */
452
+ readonly runnerFailureRules: readonly RunnerFailureRule[];
453
+ /** Environment additions marking the confinement for child processes. */
454
+ readonly env: Readonly<Record<string, string>>;
455
+ /**
456
+ * File descriptor the runner reports its own status on, when it has one.
457
+ *
458
+ * The consumer must give the spawned process a pipe at this descriptor and
459
+ * read it back: it is the only channel a confined command cannot write to,
460
+ * and therefore the only evidence that distinguishes a runner that failed
461
+ * from a command claiming the runner failed.
462
+ */
463
+ readonly statusFd?: number;
464
+ }
465
+ /**
466
+ * In-process path fence. It governs file effects a tool performs itself, which
467
+ * no process sandbox can see, and is the only enforcement available on a
468
+ * platform without a process backend.
469
+ */
470
+ interface FsFence {
471
+ /** Throw `SandboxDeniedError` unless the path may be written. */
472
+ assertWritable(path: string): Promise<void>;
473
+ /** Whether the path may be written, without throwing. */
474
+ isWritable(path: string): Promise<boolean>;
475
+ /** Whether the path may be read; `deny` carve-outs make this false. */
476
+ isReadable(path: string): Promise<boolean>;
477
+ /** The roots this fence permits writes under, for surfacing to a caller. */
478
+ readonly writableRoots: readonly string[];
479
+ /** Whether the path's inode carries another name this policy never examined. */
480
+ isAliased(path: string): Promise<boolean>;
481
+ }
482
+ /**
483
+ * Abstract process-sandbox service. `confine` must return an enforcing argv or
484
+ * fail closed; silent unconfined passthrough is forbidden.
485
+ */
486
+ interface SandboxProvider {
487
+ /** Stable identifier of this provider implementation. */
488
+ readonly id: string;
489
+ /**
490
+ * Wrap `argv` so it executes confined under `policy` on this host.
491
+ * @param argv - the exact argv the caller is about to spawn, NOT a shell
492
+ * string; a shell-shaped consumer passes `['bash', '-c', command]`.
493
+ * @param policy - the file-effect policy this execution runs under.
494
+ * @returns the argv to spawn instead, plus its enforcement and dialects.
495
+ */
496
+ confine(argv: readonly string[], policy: SandboxPolicy): Promise<ConfinedArgv>;
497
+ /** Build the in-process fence for the same policy the backends receive. */
498
+ fence(policy: SandboxPolicy): FsFence;
499
+ }
500
+ /** Filesystem facts the Universal contract cannot read for itself. */
501
+ interface PathResolver {
502
+ /** Canonical path with symlinks resolved, for the nearest existing ancestor. */
503
+ realpath(path: string): Promise<string>;
504
+ /** Whether the path currently exists. */
505
+ exists(path: string): Promise<boolean>;
506
+ /**
507
+ * The target of a symbolic link, or `undefined` when the path is not one.
508
+ *
509
+ * Existence has to be judged without following links: a link pointing at a
510
+ * path that does not exist yet still exists itself, and treating it as absent
511
+ * makes the resolver judge the link's own name instead of where it leads —
512
+ * which is inside the workspace, and therefore writable.
513
+ */
514
+ readLink?(path: string): Promise<string | undefined>;
515
+ /**
516
+ * How many names refer to this file's inode, when the host can say.
517
+ *
518
+ * A path boundary cannot see a hard link: two names for one inode, one inside
519
+ * the workspace and one outside, let a write reach past the boundary through
520
+ * the inside name. A count above one means the file is reachable under
521
+ * another name this policy never examined.
522
+ */
523
+ hardLinkCount?(path: string): Promise<number>;
524
+ }
525
+ //#endregion
526
+ //#region src/roots.d.ts
527
+ /**
528
+ * Directory names never writable inside a granted root. Writing `.git` lets a
529
+ * command install a hook that runs arbitrary code on the next git invocation,
530
+ * which defeats the point of confining the command; the credential files are
531
+ * there so a confined command cannot rewrite the caller's own authentication.
532
+ * They stay readable — this is a write boundary, not a read boundary.
533
+ */
534
+ declare const PROTECTED_SUBPATHS: readonly string[];
535
+ /** Where a layer came from, which decides ties at equal path specificity. */
536
+ type GrantOrigin = 'mode' | 'protected' | 'entry' | 'restriction' | 'approval';
537
+ /** One subtree's access, overriding whatever the layers beneath it said. */
538
+ interface GrantLayer {
539
+ /** Absolute, normalized path whose subtree this layer governs. */
540
+ readonly path: string;
541
+ /** Access this layer establishes for that subtree. */
542
+ readonly access: FileSystemAccess;
543
+ /** What contributed the layer; an explicit entry outranks a generated one. */
544
+ readonly origin: GrantOrigin;
545
+ }
546
+ /**
547
+ * The baseline every policy starts from: the host is readable and nothing is
548
+ * writable. Layers only ever move a subtree away from this.
549
+ */
550
+ declare const BASELINE_ACCESS: FileSystemAccess;
551
+ /** Optional platform inputs the caller knows and this package must not guess. */
552
+ interface WritableRootOptions {
553
+ /** Temp directories `workspace-write` may also use (e.g. the OS temp root). */
554
+ readonly tempRoots?: readonly string[];
555
+ /** Whether to layer {@link PROTECTED_SUBPATHS} under every granted root. */
556
+ readonly protectSubpaths?: boolean;
557
+ /**
558
+ * Absolute paths the platform hides outright — credential stores and host
559
+ * daemon sockets. Layered as `deny` above the mode grant and below any
560
+ * explicit entry, so a deployment can still reopen one deliberately while a
561
+ * tool-supplied restriction cannot widen it.
562
+ */
563
+ readonly deniedPaths?: readonly string[];
564
+ /**
565
+ * Permit writes to a file whose inode carries more than one name. Off by
566
+ * default: a hard link reaching out of the workspace is otherwise invisible
567
+ * to a boundary made of paths.
568
+ */
569
+ readonly allowAliasedWrites?: boolean;
570
+ }
571
+ /**
572
+ * Resolve a policy into the ordered layers that express it.
573
+ *
574
+ * Layers are sorted broadest to narrowest, and an explicit entry wins a tie at
575
+ * equal depth so a deployment can deliberately reopen a protected subpath. A
576
+ * layer that would not change the access already in force is dropped, so the
577
+ * result carries no mount or profile rule that does nothing.
578
+ */
579
+ declare function grantLayers(policy: SandboxPolicy, options?: WritableRootOptions): readonly GrantLayer[];
580
+ /**
581
+ * The access in force at one path, given layers already applied in order.
582
+ * The last layer whose subtree contains the path wins, which is what makes a
583
+ * narrower grant reopen a denied parent.
584
+ */
585
+ declare function accessInLayers(target: string, layers: readonly GrantLayer[], baseline?: FileSystemAccess): FileSystemAccess;
586
+ /** The write grants and re-denials one policy resolves to. */
587
+ interface WritableRootSet {
588
+ /** Subtrees that end up writable, broadest first. */
589
+ readonly roots: readonly string[];
590
+ /** Subtrees inside those roots that are not writable, in application order. */
591
+ readonly denied: readonly string[];
592
+ }
593
+ /**
594
+ * The writable subtrees and their re-denials, flattened from {@link grantLayers}
595
+ * for callers that only need the two lists. Order is preserved; a narrower grant
596
+ * beneath a denial appears in `roots` after the denial it reopens.
597
+ */
598
+ declare function writableRoots(policy: SandboxPolicy, options?: WritableRootOptions): WritableRootSet;
599
+ /** Subtrees whose contents must not be readable, for backends that can mask. */
600
+ declare function unreadablePaths(policy: SandboxPolicy, options?: WritableRootOptions): readonly string[];
601
+ //#endregion
602
+ //#region src/fence.d.ts
603
+ /**
604
+ * Build the fence for one policy.
605
+ * @param policy - the same policy the process backends receive.
606
+ * @param resolver - filesystem facts used to defeat symlinked paths.
607
+ * @param options - platform temp roots and protected-subpath behaviour.
608
+ */
609
+ declare function createFsFence(policy: SandboxPolicy, resolver: PathResolver, options?: WritableRootOptions): FsFence;
610
+ //#endregion
611
+ //#region src/path.d.ts
612
+ /**
613
+ * Pure, dependency-free path algebra shared by every sandbox backend.
614
+ *
615
+ * This package is Universal: it must never import `node:path`, so the lexical
616
+ * rules both POSIX and Win32 backends rely on live here as string operations.
617
+ * Filesystem-dependent resolution (realpath, existence) is injected through
618
+ * {@link PathResolver} by the Node-elevated provider instead.
619
+ */
620
+ /** Which lexical dialect a path is written in. */
621
+ type PathFlavor = 'posix' | 'win32';
622
+ /** Detect the dialect of an absolute path from its own shape. */
623
+ declare function detectFlavor(path: string): PathFlavor;
624
+ /** Whether the path is absolute in either dialect. */
625
+ declare function isAbsolutePath(path: string): boolean;
626
+ /**
627
+ * Collapse separators and resolve `.` / `..` lexically, without touching the
628
+ * filesystem. A `..` that would escape the root is dropped, matching how both
629
+ * bwrap and Seatbelt treat an over-popped absolute path.
630
+ */
631
+ declare function normalizePath(path: string): string;
632
+ /** Normalized segments below the root prefix; the root itself has none. */
633
+ declare function pathSegments(path: string): readonly string[];
634
+ /**
635
+ * Specificity rank used to order overlapping policy entries. A deeper path is
636
+ * more specific, so it is applied later and wins over a broader ancestor.
637
+ */
638
+ declare function pathDepth(path: string): number;
639
+ /** Whether two paths identify the same location lexically. */
640
+ declare function samePath(left: string, right: string): boolean;
641
+ /**
642
+ * Whether `candidate` is `root` itself or lies beneath it. Comparison is
643
+ * segment-wise, so `/repo-secrets` is never treated as inside `/repo`.
644
+ */
645
+ declare function containsPath(root: string, candidate: string): boolean;
646
+ /** Append relative segments to an absolute base, normalizing the result. */
647
+ declare function joinPath(base: string, ...parts: readonly string[]): string;
648
+ /** The parent of a normalized path, or `undefined` at a filesystem root. */
649
+ declare function parentPath(path: string): string | undefined;
650
+ /** Every ancestor of `path` from the filesystem root down to `path` itself. */
651
+ declare function ancestorPaths(path: string): readonly string[];
652
+ /** Drop paths already covered by a broader entry in the same list. */
653
+ declare function dedupeRoots(roots: readonly string[]): readonly string[];
654
+ //#endregion
655
+ //#region src/resources.d.ts
656
+ /**
657
+ * Resource limits — a third axis, and the one this package enforces weakest.
658
+ *
659
+ * A filesystem boundary can be completely correct while the host falls over: a
660
+ * fork storm, a growing allocation, or a loop that never ends costs nothing in
661
+ * file effects. Measured against the current backends, 150 processes, 2 GB of
662
+ * memory and 20 000 files met no resistance at all.
663
+ *
664
+ * What a host can actually promise differs so much that the promise has to be
665
+ * reported rather than assumed. A real quota needs cgroup v2 or a Job Object;
666
+ * without one, limits can still be observed and acted on, which stops a runaway
667
+ * but does not prevent the spike that precedes it.
668
+ */
669
+ /** What a confined execution may consume. Every field is optional. */
670
+ interface ResourceLimits {
671
+ /** Wall-clock time before the execution is ended. */
672
+ readonly wallClockMs?: number;
673
+ /** Resident memory across the whole process tree. */
674
+ readonly memoryBytes?: number;
675
+ /** Processes in the tree, including the sandbox process itself. */
676
+ readonly processes?: number;
677
+ /** Accumulated CPU time across the tree. */
678
+ readonly cpuMs?: number;
679
+ }
680
+ /**
681
+ * How a host enforces those limits.
682
+ *
683
+ * `quota` means the kernel refuses to exceed them. `monitor` means they are
684
+ * sampled and the execution is ended once exceeded — which bounds a runaway but
685
+ * not the spike between two samples. `none` means nothing is watching.
686
+ */
687
+ type ResourceEnforcement = 'quota' | 'monitor' | 'none';
688
+ /** Why a supervised execution was ended. */
689
+ type ResourceBreach = 'wall-clock' | 'memory' | 'processes' | 'cpu';
690
+ /** What a supervised execution actually consumed. */
691
+ interface ResourceUsage {
692
+ /** Highest resident memory observed across the tree. */
693
+ readonly peakMemoryBytes: number;
694
+ /** Highest process count observed in the tree. */
695
+ readonly peakProcesses: number;
696
+ /** Highest accumulated CPU time observed. */
697
+ readonly cpuMs: number;
698
+ /** Wall-clock time the execution ran for. */
699
+ readonly wallClockMs: number;
700
+ }
701
+ /** Whether any limit is set at all, so a caller can skip supervision entirely. */
702
+ declare function hasResourceLimits(limits: ResourceLimits): boolean;
703
+ /**
704
+ * The first limit the usage exceeds, or `undefined` while it is within them.
705
+ * Checked in the order a runaway usually announces itself.
706
+ */
707
+ declare function breachedLimit(usage: ResourceUsage, limits: ResourceLimits): ResourceBreach | undefined;
708
+ //#endregion
709
+ //#region src/violation.d.ts
710
+ /** The enforcement layer that observed a violation. */
711
+ type SandboxViolationBackend = 'bwrap' | 'seatbelt' | 'windows-acl' | 'fence' | 'custom';
712
+ /** Why a file effect was refused, normalized across backend dialects. */
713
+ type SandboxViolationReason = 'operation-not-permitted' | 'permission-denied' | 'read-only-filesystem' | 'policy-denied' | 'runner-failure';
714
+ /** One observed violation, retaining the evidence that identified it. */
715
+ interface SandboxViolation {
716
+ readonly backend: SandboxViolationBackend;
717
+ readonly reason: SandboxViolationReason;
718
+ readonly mode: string;
719
+ /** The path involved, when the backend named one. */
720
+ readonly path?: string;
721
+ /** Bounded evidence excerpt; never the whole output. */
722
+ readonly snippet: string;
723
+ }
724
+ /**
725
+ * Build a violation record from a classified outcome.
726
+ * @returns the record, or `undefined` when the outcome was not a violation.
727
+ */
728
+ declare function sandboxViolation(classification: SandboxClassification, backend: SandboxViolationBackend, mode: string): SandboxViolation | undefined;
729
+ //#endregion
730
+ export { BASELINE_ACCESS, type CommandOutcome, type ConfinedArgv, type ConfinedSandboxMode, DEFAULT_EXEC_OUTCOMES, type ExecCapability, type ExecClassification, type ExecOutcome, type FileSystemAccess, type FileSystemEntry, type FsFence, type GrantLayer, type GrantOrigin, NETWORK_MODES, type NetworkEnforcement, type NetworkMode, PROTECTED_SUBPATHS, type PathFlavor, type PathResolver, type ResourceBreach, type ResourceEnforcement, type ResourceLimits, type ResourceUsage, type RunnerFailureRule, SANDBOX_MODES, type SandboxApproval, type SandboxApprovalGrant, type SandboxApprovalScope, type SandboxClassification, type SandboxClassificationInput, SandboxDeniedError, type SandboxEnforcement, type SandboxExecutionPolicy, type SandboxMode, type SandboxOutcomeKind, type SandboxPolicy, type SandboxPolicyDefaults, SandboxPolicyError, type SandboxPolicyRequest, type SandboxProvider, SandboxUnavailableError, type SandboxViolation, type SandboxViolationBackend, type SandboxViolationReason, type WritableRootOptions, type WritableRootSet, accessFor, accessInLayers, ancestorPaths, annotateStderr, approveSandboxEscalation, breachedLimit, classifyExec, classifyOutcome, confiningPolicy, containsPath, createFsFence, dedupeRoots, detectFlavor, entriesWithin, grantLayers, hasResourceLimits, isAbsolutePath, isConfinedMode, isNetworkMode, isSandboxApproval, isSandboxApprovalSpent, isSandboxMode, joinPath, modeAuthority, narrowNetwork, narrowPolicy, networkAuthority, normalizePath, orderEntries, parentPath, pathDepth, pathSegments, requireSandboxApproval, resolveSandboxPolicy, samePath, sandboxViolation, splitCommands, tokenizeScript, unreadablePaths, writableRoots };
731
+ //# sourceMappingURL=index.d.ts.map