@anvia/sandbox 1.1.6 → 1.1.8
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.
- package/README.md +24 -60
- package/dist/{chunk-D5XZQ2ES.js → chunk-ZMZH4DBC.js} +21 -2
- package/dist/chunk-ZMZH4DBC.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-D5XZQ2ES.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @anvia/sandbox
|
|
2
2
|
|
|
3
|
-
Docker-backed
|
|
3
|
+
Give agents a workspace where they can run commands and work with files. Docker-backed sandboxes keep execution infrastructure explicit and under your application’s control.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @anvia/sandbox @anvia/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
Requires Node.js 20.12 or later and an accessible Docker daemon. Pull an image before creating a sandbox.
|
|
4
14
|
|
|
5
15
|
```ts
|
|
6
16
|
import { createDockerSandboxTools, DockerSandboxClient } from "@anvia/sandbox";
|
|
@@ -18,67 +28,21 @@ const tools = createDockerSandboxTools({
|
|
|
18
28
|
sandbox: sandbox.runtime,
|
|
19
29
|
tools: ["exec_command", "read_file", "write_file", "list_files"],
|
|
20
30
|
});
|
|
31
|
+
// Pass tools to new Agent({ id, model, tools }).
|
|
21
32
|
```
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
and `createSandbox()` and `resumeSandbox()` never pull images. `DockerSandbox` owns the container and,
|
|
25
|
-
for an ephemeral workspace, its Docker volume. Dispose it asynchronously or call `destroy()`.
|
|
26
|
-
|
|
27
|
-
`stop()` preserves the container and workspace. `resumeSandbox({ id })` creates a fresh live handle for
|
|
28
|
-
that container; workspace files persist, while the in-memory managed-process registry starts empty. A
|
|
29
|
-
caller-owned `{ type: "docker-volume", name }` workspace is never deleted by the sandbox.
|
|
30
|
-
|
|
31
|
-
Networking is explicit. Use `{ mode: "none" }` or `{ mode: "bridge", ports: [...] }`; published ports
|
|
32
|
-
bind only to `127.0.0.1`. Runtime methods use object arguments, propagate abort signals, and expose
|
|
33
|
-
command and process output as bytes. Tool wrappers decode UTF-8 strictly and return structured values.
|
|
34
|
+
## What you get
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`command` and `args` input in that configuration.
|
|
36
|
+
- Command execution, file access, and managed processes.
|
|
37
|
+
- Ephemeral workspaces or caller-owned Docker volumes.
|
|
38
|
+
- Explicit networking, resource limits, and command policies.
|
|
39
|
+
- Stop/resume lifecycle and automatic disposal.
|
|
40
|
+
- Read-only sandbox inspection in Anvia Studio.
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
example `notes/result.txt`) or absolute paths inside the sandbox workdir (for example
|
|
44
|
-
`/workspace/notes/result.txt` with the default workdir). Paths outside the sandbox workdir are
|
|
45
|
-
rejected.
|
|
42
|
+
`await using` destroys the owned container and ephemeral volume at scope exit. Keep the scope open while agents use its tools. Command policies filter executable names; they do not restrict what an allowed interpreter can execute.
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
executables by default, including path-qualified names such as `/bin/sh`. To permit an allowlisted
|
|
49
|
-
shell, set `exec.commands.allowShellInterpreters` to the boolean `true`. Omitted or `false` keeps the
|
|
50
|
-
guard enabled; non-boolean values such as `"false"` are rejected when creating tools. This guard
|
|
51
|
-
checks executable names only: allowlisted runtimes such as Node.js or Python can still launch other
|
|
52
|
-
commands, so the command policy does not restrict what those programs can execute.
|
|
53
|
-
|
|
54
|
-
`resources.sharedMemoryMb` maps to a private Docker `/dev/shm` size. A security configuration may use
|
|
55
|
-
explicit `dropCapabilities` and `addCapabilities` arrays, plus
|
|
56
|
-
`seccompProfile: { type: "path", path }` with an absolute host path. These options are used by
|
|
57
|
-
`@anvia/browser` to keep Chromium's own process sandbox enabled.
|
|
58
|
-
|
|
59
|
-
`createSandbox()` accepts `containerRuntime` — the runtime name registered in the Docker daemon (for
|
|
60
|
-
example `runsc` for gVisor). The runtime must already be registered (`runsc install` followed by a
|
|
61
|
-
daemon restart); creation fails with a `runtime_not_found` error otherwise. `resumeSandbox()` keeps
|
|
62
|
-
the container's original runtime. Under gVisor, workspace files persist through
|
|
63
|
-
`stop()`/`resumeSandbox()` because they live on a Docker volume, but changes to the container's
|
|
64
|
-
writable rootfs outside the workspace do not survive a resume, and `security.seccompProfile` is not
|
|
65
|
-
enforced the way it is on the default runtime because the gVisor sentry mediates application
|
|
66
|
-
syscalls itself.
|
|
67
|
-
|
|
68
|
-
Studio does not discover sandboxes through tool metadata. Register a read-only inspector explicitly:
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
const studio = new Studio([agent], {
|
|
72
|
-
sandboxes: [
|
|
73
|
-
{
|
|
74
|
-
inspector: sandbox.inspector({ files: true, ports: true, processes: true }),
|
|
75
|
-
agentIds: [agent.id],
|
|
76
|
-
toolNames: tools.map((tool) => tool.name),
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
});
|
|
80
|
-
```
|
|
44
|
+
## Learn more
|
|
81
45
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
46
|
+
- [Usage guide](https://github.com/anvia-hq/anvia/blob/main/docs/packages/tool-sandbox.md)
|
|
47
|
+
- [Anvia](https://github.com/anvia-hq/anvia/blob/main/README.md)
|
|
48
|
+
- [Contributing](https://github.com/anvia-hq/anvia/blob/main/CONTRIBUTING.md)
|
|
@@ -87,12 +87,31 @@ async function assertDockerCli(args, options) {
|
|
|
87
87
|
const result = await runDockerCli(args, options);
|
|
88
88
|
if (result.exitCode !== 0) {
|
|
89
89
|
throw new DockerSandboxError(
|
|
90
|
-
`Docker command failed: docker ${args.join(" ")}`,
|
|
90
|
+
`Docker command failed: docker ${redactCliArgs(args).join(" ")}`,
|
|
91
91
|
"docker_command_failed",
|
|
92
92
|
result
|
|
93
93
|
);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
+
var envValueFlags = /* @__PURE__ */ new Set(["--env", "-e"]);
|
|
97
|
+
function redactCliArgs(args) {
|
|
98
|
+
return args.map((arg, index) => {
|
|
99
|
+
const flag = args[index - 1];
|
|
100
|
+
if (flag !== void 0 && envValueFlags.has(flag)) return redactEnvAssignment(arg);
|
|
101
|
+
for (const envFlag of envValueFlags) {
|
|
102
|
+
const prefix = `${envFlag}=`;
|
|
103
|
+
if (arg.startsWith(prefix))
|
|
104
|
+
return `${prefix}${redactEnvAssignment(arg.slice(prefix.length))}`;
|
|
105
|
+
}
|
|
106
|
+
if (arg.startsWith("-e") && !arg.startsWith("-e=") && arg.length > 2)
|
|
107
|
+
return `-e${redactEnvAssignment(arg.slice(2))}`;
|
|
108
|
+
return arg;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function redactEnvAssignment(value) {
|
|
112
|
+
const separator = value.indexOf("=");
|
|
113
|
+
return separator === -1 ? value : `${value.slice(0, separator + 1)}<redacted>`;
|
|
114
|
+
}
|
|
96
115
|
function createOutputCollector(maxBytes, onChunk) {
|
|
97
116
|
const chunks = [];
|
|
98
117
|
let length = 0;
|
|
@@ -131,4 +150,4 @@ export {
|
|
|
131
150
|
assertDockerCli,
|
|
132
151
|
decodeUtf8
|
|
133
152
|
};
|
|
134
|
-
//# sourceMappingURL=chunk-
|
|
153
|
+
//# sourceMappingURL=chunk-ZMZH4DBC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/docker-cli.ts"],"sourcesContent":["export type DockerSandboxErrorCode =\n | \"docker_unavailable\"\n | \"docker_command_failed\"\n | \"image_not_found\"\n | \"runtime_not_found\"\n | \"volume_not_found\"\n | \"sandbox_not_found\"\n | \"invalid_state\"\n | \"invalid_path\"\n | \"timeout\"\n | \"file_too_large\"\n | \"tool_policy\"\n | \"port\"\n | \"process\";\n\nexport class DockerSandboxError extends Error {\n constructor(\n message: string,\n readonly code: DockerSandboxErrorCode,\n readonly details?: unknown,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = \"DockerSandboxError\";\n }\n}\n\nexport function dockerSandboxError(\n message: string,\n code: DockerSandboxErrorCode,\n cause?: unknown,\n details?: unknown,\n): DockerSandboxError {\n return new DockerSandboxError(\n message,\n code,\n details,\n cause === undefined ? undefined : { cause },\n );\n}\n","import { spawn } from \"node:child_process\";\nimport { DockerSandboxError } from \"./errors\";\n\nexport interface DockerCliResult {\n stdout: Uint8Array;\n stderr: Uint8Array;\n exitCode: number;\n durationMs: number;\n timedOut: boolean;\n stdoutTruncated: boolean;\n stderrTruncated: boolean;\n}\n\nexport interface DockerCliOptions {\n dockerPath: string;\n timeoutMs?: number | undefined;\n maxOutputBytes?: number | undefined;\n input?: string | Uint8Array | undefined;\n signal?: AbortSignal | undefined;\n onStdout?: (chunk: Uint8Array) => void;\n onStderr?: (chunk: Uint8Array) => void;\n}\n\nconst defaultMaxOutputBytes = 1024 * 1024;\n\nexport async function runDockerCli(\n args: string[],\n options: DockerCliOptions,\n): Promise<DockerCliResult> {\n const startedAt = Date.now();\n const maxOutputBytes = options.maxOutputBytes ?? defaultMaxOutputBytes;\n const stdout = createOutputCollector(maxOutputBytes, options.onStdout);\n const stderr = createOutputCollector(maxOutputBytes, options.onStderr);\n\n return new Promise((resolve, reject) => {\n const child = spawn(options.dockerPath, args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n let timedOut = false;\n let settled = false;\n\n const timeout =\n options.timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGKILL\");\n }, options.timeoutMs);\n\n const abort = () => {\n child.kill(\"SIGKILL\");\n };\n\n if (options.signal?.aborted === true) {\n abort();\n } else {\n options.signal?.addEventListener(\"abort\", abort, { once: true });\n }\n\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.accept(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.accept(chunk));\n\n child.on(\"error\", (error) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n options.signal?.removeEventListener(\"abort\", abort);\n\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n reject(\n new DockerSandboxError(\"Docker CLI was not found.\", \"docker_unavailable\", undefined, {\n cause: error,\n }),\n );\n return;\n }\n\n reject(error);\n });\n\n child.on(\"close\", (code) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n options.signal?.removeEventListener(\"abort\", abort);\n\n if (options.signal?.aborted === true) {\n reject(options.signal.reason ?? new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n\n resolve({\n stdout: stdout.bytes(),\n stderr: stderr.bytes(),\n exitCode: code ?? 1,\n durationMs: Date.now() - startedAt,\n timedOut,\n stdoutTruncated: stdout.truncated,\n stderrTruncated: stderr.truncated,\n });\n });\n\n if (options.input !== undefined) {\n child.stdin.end(options.input);\n } else {\n child.stdin.end();\n }\n });\n}\n\nexport async function assertDockerCli(args: string[], options: DockerCliOptions): Promise<void> {\n const result = await runDockerCli(args, options);\n\n if (result.exitCode !== 0) {\n throw new DockerSandboxError(\n `Docker command failed: docker ${redactCliArgs(args).join(\" \")}`,\n \"docker_command_failed\",\n result,\n );\n }\n}\n\n/** Flags whose following value is a `KEY=VALUE` assignment that may carry a secret. */\nconst envValueFlags = new Set([\"--env\", \"-e\"]);\n\n/**\n * Replace secret values in a Docker argv before it is embedded in an error message, so\n * environment values passed with `--env`/`-e` never reach logs or traces. The variable\n * name is kept visible to keep the failure diagnosable. Both the separated\n * (`--env KEY=VALUE`) and equals-delimited (`--env=KEY=VALUE`) spellings are covered, as\n * well as the attached short-flag spelling (`-eKEY=VALUE`) that Docker's flag parser\n * accepts for the `-e` shorthand.\n */\nfunction redactCliArgs(args: string[]): string[] {\n return args.map((arg, index) => {\n const flag = args[index - 1];\n if (flag !== undefined && envValueFlags.has(flag)) return redactEnvAssignment(arg);\n for (const envFlag of envValueFlags) {\n const prefix = `${envFlag}=`;\n if (arg.startsWith(prefix))\n return `${prefix}${redactEnvAssignment(arg.slice(prefix.length))}`;\n }\n if (arg.startsWith(\"-e\") && !arg.startsWith(\"-e=\") && arg.length > 2)\n return `-e${redactEnvAssignment(arg.slice(2))}`;\n return arg;\n });\n}\n\nfunction redactEnvAssignment(value: string): string {\n const separator = value.indexOf(\"=\");\n return separator === -1 ? value : `${value.slice(0, separator + 1)}<redacted>`;\n}\n\nfunction createOutputCollector(maxBytes: number, onChunk?: (chunk: Uint8Array) => void) {\n const chunks: Buffer[] = [];\n let length = 0;\n let truncated = false;\n\n return {\n get truncated() {\n return truncated;\n },\n accept(chunk: Buffer) {\n onChunk?.(chunk);\n\n if (length >= maxBytes) {\n truncated = true;\n return;\n }\n\n const remaining = maxBytes - length;\n const next = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;\n chunks.push(next);\n length += next.length;\n\n if (next.length < chunk.length) {\n truncated = true;\n }\n },\n bytes() {\n const bytes = Buffer.concat(chunks, length);\n return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength).slice();\n },\n };\n}\n\nexport function decodeUtf8(bytes: Uint8Array): string {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anvia/sandbox",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "Sandboxed workspace tools for Anvia agents.",
|
|
5
5
|
"author": "anvia",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"tsup": "^8.5.0",
|
|
37
37
|
"typescript": "^5.9.3",
|
|
38
38
|
"vitest": "^4.1.11",
|
|
39
|
-
"@anvia/core": "1.
|
|
39
|
+
"@anvia/core": "1.6.1"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@anvia/core": "^1.
|
|
42
|
+
"@anvia/core": "^1.6.1"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=20.12.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/docker-cli.ts"],"sourcesContent":["export type DockerSandboxErrorCode =\n | \"docker_unavailable\"\n | \"docker_command_failed\"\n | \"image_not_found\"\n | \"runtime_not_found\"\n | \"volume_not_found\"\n | \"sandbox_not_found\"\n | \"invalid_state\"\n | \"invalid_path\"\n | \"timeout\"\n | \"file_too_large\"\n | \"tool_policy\"\n | \"port\"\n | \"process\";\n\nexport class DockerSandboxError extends Error {\n constructor(\n message: string,\n readonly code: DockerSandboxErrorCode,\n readonly details?: unknown,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = \"DockerSandboxError\";\n }\n}\n\nexport function dockerSandboxError(\n message: string,\n code: DockerSandboxErrorCode,\n cause?: unknown,\n details?: unknown,\n): DockerSandboxError {\n return new DockerSandboxError(\n message,\n code,\n details,\n cause === undefined ? undefined : { cause },\n );\n}\n","import { spawn } from \"node:child_process\";\nimport { DockerSandboxError } from \"./errors\";\n\nexport interface DockerCliResult {\n stdout: Uint8Array;\n stderr: Uint8Array;\n exitCode: number;\n durationMs: number;\n timedOut: boolean;\n stdoutTruncated: boolean;\n stderrTruncated: boolean;\n}\n\nexport interface DockerCliOptions {\n dockerPath: string;\n timeoutMs?: number | undefined;\n maxOutputBytes?: number | undefined;\n input?: string | Uint8Array | undefined;\n signal?: AbortSignal | undefined;\n onStdout?: (chunk: Uint8Array) => void;\n onStderr?: (chunk: Uint8Array) => void;\n}\n\nconst defaultMaxOutputBytes = 1024 * 1024;\n\nexport async function runDockerCli(\n args: string[],\n options: DockerCliOptions,\n): Promise<DockerCliResult> {\n const startedAt = Date.now();\n const maxOutputBytes = options.maxOutputBytes ?? defaultMaxOutputBytes;\n const stdout = createOutputCollector(maxOutputBytes, options.onStdout);\n const stderr = createOutputCollector(maxOutputBytes, options.onStderr);\n\n return new Promise((resolve, reject) => {\n const child = spawn(options.dockerPath, args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n let timedOut = false;\n let settled = false;\n\n const timeout =\n options.timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGKILL\");\n }, options.timeoutMs);\n\n const abort = () => {\n child.kill(\"SIGKILL\");\n };\n\n if (options.signal?.aborted === true) {\n abort();\n } else {\n options.signal?.addEventListener(\"abort\", abort, { once: true });\n }\n\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.accept(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.accept(chunk));\n\n child.on(\"error\", (error) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n options.signal?.removeEventListener(\"abort\", abort);\n\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n reject(\n new DockerSandboxError(\"Docker CLI was not found.\", \"docker_unavailable\", undefined, {\n cause: error,\n }),\n );\n return;\n }\n\n reject(error);\n });\n\n child.on(\"close\", (code) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n options.signal?.removeEventListener(\"abort\", abort);\n\n if (options.signal?.aborted === true) {\n reject(options.signal.reason ?? new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n\n resolve({\n stdout: stdout.bytes(),\n stderr: stderr.bytes(),\n exitCode: code ?? 1,\n durationMs: Date.now() - startedAt,\n timedOut,\n stdoutTruncated: stdout.truncated,\n stderrTruncated: stderr.truncated,\n });\n });\n\n if (options.input !== undefined) {\n child.stdin.end(options.input);\n } else {\n child.stdin.end();\n }\n });\n}\n\nexport async function assertDockerCli(args: string[], options: DockerCliOptions): Promise<void> {\n const result = await runDockerCli(args, options);\n\n if (result.exitCode !== 0) {\n throw new DockerSandboxError(\n `Docker command failed: docker ${args.join(\" \")}`,\n \"docker_command_failed\",\n result,\n );\n }\n}\n\nfunction createOutputCollector(maxBytes: number, onChunk?: (chunk: Uint8Array) => void) {\n const chunks: Buffer[] = [];\n let length = 0;\n let truncated = false;\n\n return {\n get truncated() {\n return truncated;\n },\n accept(chunk: Buffer) {\n onChunk?.(chunk);\n\n if (length >= maxBytes) {\n truncated = true;\n return;\n }\n\n const remaining = maxBytes - length;\n const next = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;\n chunks.push(next);\n length += next.length;\n\n if (next.length < chunk.length) {\n truncated = true;\n }\n },\n bytes() {\n const bytes = Buffer.concat(chunks, length);\n return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength).slice();\n },\n };\n}\n\nexport function decodeUtf8(bytes: Uint8Array): string {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n}\n"],"mappings":"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","names":[]}
|