windows-development-sandbox 1.0.5 → 1.1.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.
- package/LICENSE +21 -0
- package/README.md +22 -6
- package/config.example.json +17 -0
- package/dist/api.js +2 -1
- package/dist/cli/args.d.ts +20 -0
- package/dist/cli/args.js +35 -3
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/agent-commands.d.ts +6 -0
- package/dist/cli/commands/agent-commands.js +61 -0
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +3 -0
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +101 -0
- package/dist/cli/commands/data/collect.d.ts +11 -0
- package/dist/cli/commands/data/collect.js +54 -0
- package/dist/cli/commands/data/index.d.ts +4 -0
- package/dist/cli/commands/data/index.js +45 -0
- package/dist/cli/commands/data/remove.d.ts +2 -0
- package/dist/cli/commands/data/remove.js +30 -0
- package/dist/cli/commands/index.d.ts +5 -3
- package/dist/cli/commands/index.js +5 -3
- package/dist/cli/commands/launch.d.ts +26 -2
- package/dist/cli/commands/launch.js +220 -39
- package/dist/cli/commands/maintenance.js +33 -4
- package/dist/cli/commands/project.d.ts +29 -0
- package/dist/cli/commands/project.js +63 -0
- package/dist/cli/commands/projects.d.ts +14 -0
- package/dist/cli/commands/projects.js +146 -0
- package/dist/cli/commands/repair/checks.d.ts +2 -0
- package/dist/cli/commands/{repair.js → repair/checks.js} +19 -181
- package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
- package/dist/cli/commands/repair/finders.js +108 -0
- package/dist/cli/commands/repair/index.d.ts +12 -0
- package/dist/cli/commands/repair/index.js +61 -0
- package/dist/cli/commands/repair/types.d.ts +26 -0
- package/dist/cli/commands/repair/types.js +3 -0
- package/dist/cli/commands/restart.d.ts +6 -0
- package/dist/cli/commands/restart.js +85 -0
- package/dist/cli/commands/sessions.d.ts +2 -1
- package/dist/cli/commands/sessions.js +189 -45
- package/dist/cli/commands/setup/checks.d.ts +13 -0
- package/dist/cli/commands/setup/checks.js +71 -0
- package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
- package/dist/cli/commands/setup/facts.js +60 -0
- package/dist/cli/commands/setup/index.d.ts +2 -0
- package/dist/cli/commands/setup/index.js +43 -0
- package/dist/cli/commands/user-config.d.ts +8 -0
- package/dist/cli/commands/user-config.js +41 -0
- package/dist/cli/history.d.ts +11 -0
- package/dist/cli/history.js +21 -0
- package/dist/cli/index.d.ts +7 -1
- package/dist/cli/index.js +38 -13
- package/dist/cli/manifest.d.ts +8 -0
- package/dist/cli/manifest.js +35 -15
- package/dist/cli/menu-multi.d.ts +12 -0
- package/dist/cli/menu-multi.js +82 -0
- package/dist/cli/menu.d.ts +5 -13
- package/dist/cli/menu.js +149 -46
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/settings/edit.d.ts +13 -0
- package/dist/cli/settings/edit.js +19 -0
- package/dist/cli/settings/index.d.ts +8 -0
- package/dist/cli/settings/index.js +131 -0
- package/dist/cli/settings/prompts.d.ts +7 -0
- package/dist/cli/settings/prompts.js +48 -0
- package/dist/cli/settings/schema.d.ts +33 -0
- package/dist/cli/settings/schema.js +29 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.js +4 -1
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +8 -0
- package/dist/core/local-config.js +18 -5
- package/dist/core/project-policy.d.ts +15 -0
- package/dist/core/project-policy.js +35 -0
- package/dist/core/prompt.d.ts +4 -0
- package/dist/core/prompt.js +15 -0
- package/dist/core/safe-paths.d.ts +4 -0
- package/dist/core/safe-paths.js +37 -0
- package/dist/core/schema/common.d.ts +17 -0
- package/dist/core/schema/common.js +15 -0
- package/dist/core/schema/config.d.ts +594 -0
- package/dist/core/schema/config.js +63 -0
- package/dist/core/schema/databases.d.ts +33 -0
- package/dist/core/schema/databases.js +35 -0
- package/dist/core/schema/harness.d.ts +12 -0
- package/dist/core/schema/harness.js +17 -0
- package/dist/core/schema/json-schema.d.ts +5 -0
- package/dist/core/schema/json-schema.js +25 -0
- package/dist/core/schema/profile.d.ts +27 -0
- package/dist/core/schema/profile.js +29 -0
- package/dist/core/schema/projects.d.ts +9 -0
- package/dist/core/schema/projects.js +11 -0
- package/dist/core/schema/sandbox.d.ts +50 -0
- package/dist/core/schema/sandbox.js +36 -0
- package/dist/core/schema/toolchains.d.ts +93 -0
- package/dist/core/schema/toolchains.js +83 -0
- package/dist/core/schema.d.ts +6 -604
- package/dist/core/schema.js +8 -264
- package/dist/core/types.d.ts +29 -1
- package/dist/core/util.d.ts +7 -0
- package/dist/core/util.js +12 -3
- package/dist/core/workspace.d.ts +0 -5
- package/dist/core/workspace.js +12 -46
- package/dist/guest/boot-env.d.ts +4 -0
- package/dist/guest/boot-env.js +25 -0
- package/dist/guest/cmdline.d.ts +12 -0
- package/dist/guest/cmdline.js +23 -0
- package/dist/guest/daemon/exec.d.ts +5 -0
- package/dist/guest/daemon/exec.js +32 -0
- package/dist/guest/daemon/project.d.ts +14 -0
- package/dist/guest/daemon/project.js +132 -0
- package/dist/guest/daemon/session.d.ts +13 -0
- package/dist/guest/daemon/session.js +139 -0
- package/dist/guest/daemon/shutdown.d.ts +1 -0
- package/dist/guest/daemon/shutdown.js +21 -0
- package/dist/guest/daemon/state.d.ts +17 -0
- package/dist/guest/daemon/state.js +31 -0
- package/dist/guest/daemon.js +8 -141
- package/dist/guest/db.js +18 -3
- package/dist/guest/env.d.ts +2 -0
- package/dist/guest/env.js +4 -2
- package/dist/guest/guide.d.ts +6 -2
- package/dist/guest/guide.js +5 -4
- package/dist/guest/helpers.d.ts +3 -0
- package/dist/guest/helpers.js +26 -0
- package/dist/guest/main.js +17 -7
- package/dist/guest/ports.d.ts +6 -0
- package/dist/guest/ports.js +22 -0
- package/dist/guest/project-context.d.ts +7 -0
- package/dist/guest/project-context.js +10 -0
- package/dist/guest/project-env.d.ts +20 -0
- package/dist/guest/project-env.js +20 -0
- package/dist/guest/project-status.d.ts +5 -0
- package/dist/guest/project-status.js +17 -0
- package/dist/guest/ptyhost.d.ts +0 -18
- package/dist/guest/ptyhost.js +5 -63
- package/dist/guest/setup/agents.d.ts +5 -2
- package/dist/guest/setup/agents.js +22 -8
- package/dist/guest/setup/claude.js +11 -3
- package/dist/guest/setup/environment.d.ts +13 -3
- package/dist/guest/setup/environment.js +14 -10
- package/dist/guest/setup/hooks.d.ts +9 -1
- package/dist/guest/setup/hooks.js +28 -14
- package/dist/guest/toolchains/catalogue.d.ts +4 -0
- package/dist/guest/toolchains/catalogue.js +2 -2
- package/dist/guest/toolchains/cli.js +25 -17
- package/dist/guest/toolchains/common.d.ts +18 -1
- package/dist/guest/toolchains/common.js +34 -11
- package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
- package/dist/guest/toolchains/databases/dynamodb.js +52 -0
- package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
- package/dist/guest/toolchains/databases/index.js +62 -0
- package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
- package/dist/guest/toolchains/databases/mongodb.js +31 -0
- package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
- package/dist/guest/toolchains/databases/mysql.js +38 -0
- package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
- package/dist/guest/toolchains/databases/postgres.js +38 -0
- package/dist/guest/toolchains/databases/redis.d.ts +3 -0
- package/dist/guest/toolchains/databases/redis.js +16 -0
- package/dist/guest/toolchains/databases/server.d.ts +14 -0
- package/dist/guest/toolchains/databases/server.js +52 -0
- package/dist/guest/toolchains/git.js +25 -16
- package/dist/guest/toolchains/go.js +1 -1
- package/dist/guest/toolchains/index.d.ts +13 -9
- package/dist/guest/toolchains/index.js +40 -82
- package/dist/guest/toolchains/java.js +5 -5
- package/dist/guest/toolchains/leftovers.d.ts +2 -0
- package/dist/guest/toolchains/leftovers.js +26 -0
- package/dist/guest/toolchains/managers.js +12 -10
- package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
- package/dist/guest/toolchains/native/cpp.d.ts +4 -0
- package/dist/guest/toolchains/native/cpp.js +38 -0
- package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
- package/dist/guest/toolchains/native/dotnet.js +103 -0
- package/dist/guest/toolchains/native/mesa.d.ts +7 -0
- package/dist/guest/toolchains/native/mesa.js +46 -0
- package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
- package/dist/guest/toolchains/native/msvc/environment.js +57 -0
- package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/index.js +48 -0
- package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/packages.js +156 -0
- package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
- package/dist/guest/toolchains/native/msvc/tree.js +21 -0
- package/dist/guest/toolchains/native/rust.d.ts +3 -0
- package/dist/guest/toolchains/native/rust.js +45 -0
- package/dist/guest/toolchains/node.js +26 -6
- package/dist/guest/toolchains/plan.d.ts +22 -0
- package/dist/guest/toolchains/plan.js +96 -0
- package/dist/guest/toolchains/pwsh.js +1 -1
- package/dist/guest/toolchains/python.js +17 -10
- package/dist/guest/toolchains/runtimes.d.ts +2 -0
- package/dist/guest/toolchains/runtimes.js +6 -8
- package/dist/guest/toolchains/scripting/php.d.ts +3 -0
- package/dist/guest/toolchains/scripting/php.js +71 -0
- package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
- package/dist/guest/toolchains/scripting/ruby.js +47 -0
- package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
- package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
- package/dist/guest/view.js +21 -1
- package/dist/host/accounts.js +14 -5
- package/dist/host/agent-commands.d.ts +25 -0
- package/dist/host/agent-commands.js +145 -0
- package/dist/host/attach.d.ts +17 -0
- package/dist/host/attach.js +69 -0
- package/dist/host/channel.d.ts +23 -22
- package/dist/host/channel.js +66 -84
- package/dist/host/claude-profile.js +7 -3
- package/dist/host/cleanup.js +2 -11
- package/dist/host/database-plan.d.ts +12 -0
- package/dist/host/database-plan.js +52 -0
- package/dist/host/generate.d.ts +4 -12
- package/dist/host/generate.js +48 -135
- package/dist/host/join.d.ts +28 -0
- package/dist/host/join.js +76 -0
- package/dist/host/launch/mappings.d.ts +3 -0
- package/dist/host/launch/mappings.js +50 -0
- package/dist/host/launch/projects.d.ts +15 -0
- package/dist/host/launch/projects.js +56 -0
- package/dist/host/launch/stage.d.ts +11 -0
- package/dist/host/launch/stage.js +66 -0
- package/dist/host/launch/wsb.d.ts +10 -0
- package/dist/host/launch/wsb.js +34 -0
- package/dist/host/pc-scan.d.ts +6 -0
- package/dist/host/pc-scan.js +37 -0
- package/dist/host/platform.d.ts +2 -0
- package/dist/host/platform.js +13 -1
- package/dist/host/project-scan.d.ts +1 -17
- package/dist/host/project-scan.js +2 -86
- package/dist/host/projects.d.ts +100 -0
- package/dist/host/projects.js +147 -0
- package/dist/host/repo-guard.d.ts +15 -2
- package/dist/host/repo-guard.js +103 -12
- package/dist/host/terminal.js +3 -0
- package/dist/host/test-guard.d.ts +6 -0
- package/dist/host/test-guard.js +15 -0
- package/dist/host/window.js +7 -3
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +10997 -2794
- package/dist/runtime/db.js +6428 -464
- package/dist/runtime/main.js +7359 -1135
- package/dist/runtime/view.js +5922 -47
- package/dist/selftest/accounts.test.d.ts +2 -0
- package/dist/selftest/accounts.test.js +44 -0
- package/dist/selftest/agent-commands.test.d.ts +2 -0
- package/dist/selftest/agent-commands.test.js +47 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +37 -0
- package/dist/selftest/cli.test.d.ts +2 -2
- package/dist/selftest/cli.test.js +115 -27
- package/dist/selftest/cmdline.test.d.ts +2 -0
- package/dist/selftest/cmdline.test.js +45 -0
- package/dist/selftest/config.test.d.ts +2 -3
- package/dist/selftest/config.test.js +36 -72
- package/dist/selftest/data.test.d.ts +2 -2
- package/dist/selftest/data.test.js +77 -70
- package/dist/selftest/detect.test.d.ts +2 -2
- package/dist/selftest/detect.test.js +54 -38
- package/dist/selftest/fixtures.d.ts +44 -4
- package/dist/selftest/fixtures.js +40 -23
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +63 -0
- package/dist/selftest/git-access.test.d.ts +2 -0
- package/dist/selftest/git-access.test.js +26 -0
- package/dist/selftest/guest-boot.test.d.ts +2 -0
- package/dist/selftest/guest-boot.test.js +76 -0
- package/dist/selftest/harness.d.ts +44 -7
- package/dist/selftest/harness.js +62 -21
- package/dist/selftest/host-sim.test.d.ts +2 -0
- package/dist/selftest/host-sim.test.js +93 -0
- package/dist/selftest/hostile-input.test.d.ts +2 -0
- package/dist/selftest/hostile-input.test.js +54 -0
- package/dist/selftest/index.d.ts +8 -1
- package/dist/selftest/index.js +134 -35
- package/dist/selftest/links.test.d.ts +2 -0
- package/dist/selftest/links.test.js +50 -0
- package/dist/selftest/live-daemon.test.d.ts +2 -0
- package/dist/selftest/live-daemon.test.js +91 -0
- package/dist/selftest/live-git.test.d.ts +2 -0
- package/dist/selftest/live-git.test.js +60 -0
- package/dist/selftest/live-projects.test.d.ts +2 -0
- package/dist/selftest/live-projects.test.js +127 -0
- package/dist/selftest/live.d.ts +65 -0
- package/dist/selftest/live.js +109 -0
- package/dist/selftest/local-config.test.d.ts +2 -0
- package/dist/selftest/local-config.test.js +107 -0
- package/dist/selftest/mirror.test.d.ts +2 -3
- package/dist/selftest/mirror.test.js +92 -65
- package/dist/selftest/network.test.d.ts +2 -0
- package/dist/selftest/network.test.js +28 -0
- package/dist/selftest/personalization.test.d.ts +2 -0
- package/dist/selftest/personalization.test.js +53 -0
- package/dist/selftest/projects.test.d.ts +2 -0
- package/dist/selftest/projects.test.js +254 -0
- package/dist/selftest/repair.test.d.ts +2 -0
- package/dist/selftest/repair.test.js +42 -0
- package/dist/selftest/repo-guard.test.d.ts +2 -0
- package/dist/selftest/repo-guard.test.js +63 -0
- package/dist/selftest/secrets.test.d.ts +2 -0
- package/dist/selftest/secrets.test.js +71 -0
- package/dist/selftest/setup.test.d.ts +2 -2
- package/dist/selftest/setup.test.js +45 -22
- package/dist/selftest/suite.d.ts +13 -0
- package/dist/selftest/suite.js +1 -0
- package/dist/selftest/sync.test.d.ts +2 -3
- package/dist/selftest/sync.test.js +90 -78
- package/dist/selftest/test-guard.test.d.ts +2 -0
- package/dist/selftest/test-guard.test.js +49 -0
- package/dist/selftest/toolchains.test.d.ts +2 -0
- package/dist/selftest/toolchains.test.js +103 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/download.d.ts +3 -1
- package/dist/shared/download.js +19 -4
- package/dist/shared/paths.d.ts +5 -0
- package/dist/shared/paths.js +6 -0
- package/dist/shared/projects.d.ts +53 -0
- package/dist/shared/projects.js +67 -0
- package/dist/shared/rundir.d.ts +10 -1
- package/dist/shared/rundir.js +12 -4
- package/dist/shared/safe-remove.js +11 -0
- package/dist/shared/schema.d.ts +443 -0
- package/dist/shared/schema.js +41 -2
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.js +3 -1
- package/dist/sync/history.d.ts +23 -12
- package/dist/sync/history.js +52 -20
- package/dist/sync/watcher.d.ts +8 -0
- package/dist/sync/watcher.js +23 -4
- package/package.json +6 -6
- package/sandbox/bootstrap.ps1 +258 -258
- package/sandbox/cpucap.ps1 +51 -0
- package/sandbox/logon.cmd +18 -16
- package/sandbox/openssl.cs +42 -0
- package/sandbox/ptyhost.cs +15 -4
- package/sandbox/trust.cmd +35 -35
- package/schemas/config.schema.json +100 -1
- package/schemas/sandbox-config.schema.json +17 -1
- package/dist/cli/commands/data.d.ts +0 -46
- package/dist/cli/commands/data.js +0 -249
- package/dist/cli/commands/setup.js +0 -152
- package/dist/cli/settings.d.ts +0 -11
- package/dist/cli/settings.js +0 -156
- package/dist/guest/toolchains/databases.js +0 -226
- package/dist/guest/toolchains/msvc.d.ts +0 -10
- package/dist/guest/toolchains/msvc.js +0 -216
- package/dist/guest/toolchains/native.d.ts +0 -12
- package/dist/guest/toolchains/native.js +0 -220
- package/dist/guest/toolchains/scripting.d.ts +0 -10
- package/dist/guest/toolchains/scripting.js +0 -137
- package/dist/selftest/guest.test.d.ts +0 -3
- package/dist/selftest/guest.test.js +0 -61
- package/dist/selftest/helpers.test.d.ts +0 -2
- package/dist/selftest/helpers.test.js +0 -125
- package/dist/selftest/host.test.d.ts +0 -2
- package/dist/selftest/host.test.js +0 -71
- package/dist/selftest/live.test.d.ts +0 -7
- package/dist/selftest/live.test.js +0 -127
- package/dist/selftest/security.test.d.ts +0 -3
- package/dist/selftest/security.test.js +0 -143
- /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
// Attacks a compromised sandbox could try against this PC, and the guards that stop them.
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { activateAccount, saveAccount } from '../host/accounts.js';
|
|
6
|
-
import { isSandboxAddress, resetRunDir, sendRequest } from '../host/channel.js';
|
|
7
|
-
import { pruneStorage } from '../host/cleanup.js';
|
|
8
|
-
import { guardRepo, snapshotRepo } from '../host/repo-guard.js';
|
|
9
|
-
import { assertNoLinks, ensureDir, safeText } from '../core/util.js';
|
|
10
|
-
import { assertSafeMapping } from '../core/workspace.js';
|
|
11
|
-
import { sandboxGlobalState } from '../host/claude-profile.js';
|
|
12
|
-
import { findSecretFiles, stripMcpServers, stripSettings } from '../host/secrets.js';
|
|
13
|
-
import { detectDatabases } from '../core/detect-databases.js';
|
|
14
|
-
import { removeRefusal, safeRemove } from '../shared/safe-remove.js';
|
|
15
|
-
import { gitDecision } from '../guest/gitguard.js';
|
|
16
|
-
import { gitDenyRules } from '../guest/gitrules.js';
|
|
17
|
-
import { encodeFrame, FrameReader, MAX_FRAME_BYTES, OutputFilter } from '../shared/pty-client.js';
|
|
18
|
-
import { safeId } from '../shared/rundir.js';
|
|
19
|
-
import { GuestRequest, SessionStatus } from '../shared/schema.js';
|
|
20
|
-
import { syncHistoryIn } from '../sync/history.js';
|
|
21
|
-
export const securityTests = async (t, f) => {
|
|
22
|
-
const { check, rejects, write } = t;
|
|
23
|
-
const at = f.at;
|
|
24
|
-
t.section('links planted by the sandbox');
|
|
25
|
-
const victim = at('victim');
|
|
26
|
-
write(path.join(victim, 'precious.txt'), 'keep me');
|
|
27
|
-
const data = at('evil-storage', 'data', 'd');
|
|
28
|
-
ensureDir(path.join(data, 'state'));
|
|
29
|
-
fs.symlinkSync(victim, path.join(data, 'state', 'claude'), 'junction');
|
|
30
|
-
check(await rejects(() => assertNoLinks(data, path.join(data, 'state', 'claude', 'x.json')), 'is a link'), 'any host write under the data folder through a junction is refused');
|
|
31
|
-
check(await rejects(() => activateAccount(at('evil-storage'), data, 'any'), 'Unknown account') && await rejects(() => saveAccount(at('evil-storage'), data, 'a', null), 'is a link'), 'accounts never read or write through a sandbox junction');
|
|
32
|
-
const guestDot = at('link-guest');
|
|
33
|
-
ensureDir(path.join(guestDot, 'projects'));
|
|
34
|
-
fs.symlinkSync(victim, path.join(guestDot, 'projects', 'C--Workspace-my-app'), 'junction');
|
|
35
|
-
write(at('link-host', 'projects', 'D--Code-my-app', 's.jsonl'), '{}\n');
|
|
36
|
-
syncHistoryIn({ hostClaudeDir: at('link-host'), guestClaudeDir: guestDot, hostWorkspace: 'D:\\Code\\my-app', guestWorkspace: 'C:\\Workspace\\my-app' });
|
|
37
|
-
check(!fs.existsSync(path.join(victim, 's.jsonl')), 'history sync never writes through a junction inside the sandbox state');
|
|
38
|
-
fs.symlinkSync(path.join(victim, 'precious.txt'), path.join(guestDot, 'history.jsonl'), 'file');
|
|
39
|
-
write(at('link-host', 'history.jsonl'), '{"display":"x","timestamp":1}\n');
|
|
40
|
-
syncHistoryIn({ hostClaudeDir: at('link-host'), guestClaudeDir: guestDot, hostWorkspace: 'D:\\Code\\my-app', guestWorkspace: 'C:\\Workspace\\my-app' });
|
|
41
|
-
check(fs.readFileSync(path.join(victim, 'precious.txt'), 'utf8') === 'keep me', 'a file symlink planted in place of history.jsonl is replaced, not written through');
|
|
42
|
-
ensureDir(path.join(data, 'run'));
|
|
43
|
-
fs.symlinkSync(victim, path.join(data, 'run', 'requests'), 'junction');
|
|
44
|
-
check(await rejects(() => sendRequest(data, { kind: 'ping', id: 'p1', createdAt: '' }), 'is a link') && !fs.existsSync(path.join(victim, 'p1.json')), 'requests are never written through a junction');
|
|
45
|
-
fs.symlinkSync(victim, path.join(data, 'run', 'sessions'), 'junction');
|
|
46
|
-
resetRunDir(data);
|
|
47
|
-
check(fs.existsSync(path.join(victim, 'precious.txt')) && !fs.existsSync(path.join(data, 'run', 'sessions')), 'resetting the run folder removes a junction itself, never its target');
|
|
48
|
-
fs.symlinkSync(victim, path.join(data, 'logs'), 'junction');
|
|
49
|
-
const old = new Date(Date.now() - 60 * 86400000);
|
|
50
|
-
fs.utimesSync(path.join(victim, 'precious.txt'), old, old);
|
|
51
|
-
pruneStorage(at('evil-storage'));
|
|
52
|
-
check(fs.existsSync(path.join(victim, 'precious.txt')), 'cleanup never walks into a junction (old files on this PC stay)');
|
|
53
|
-
t.section('host <-> sandbox channel');
|
|
54
|
-
const validRequest = (r) => GuestRequest.safeParse(r).success;
|
|
55
|
-
const validSession = (s) => SessionStatus.safeParse(s).success;
|
|
56
|
-
check(await rejects(() => safeId('../evil'), 'invalid id') && !validRequest({ kind: 'shell', id: 'a' }) && !validRequest({ kind: 'exec', id: 'a/b', command: ['x'], cwd: 'C:\\', timeoutSeconds: 1 }) && validRequest({ kind: 'ping', id: 'abc', createdAt: 'x' }) && !validRequest({ kind: 'ping', id: 'abc', createdAt: 'x', extra: 1 }), 'only known request kinds with safe ids are accepted');
|
|
57
|
-
const goodSession = { id: 's1', launchId: 'l', harness: 'claude', cwd: 'C:\\Workspace', message: 'ok', phase: 'running', updatedAt: 'x', startedAt: 'x', attach: { addresses: ['172.20.1.5'], port: 5000, token: 't' } };
|
|
58
|
-
check(validSession(goodSession) && !validSession({ ...goodSession, attach: { ...goodSession.attach, port: 70000 } }) && !validSession({ ...goodSession, phase: 'pwned' }), 'guest session status is shape-checked');
|
|
59
|
-
const ifaces = { 'vEthernet (Default Switch)': [{ address: '172.20.0.1', netmask: '255.255.240.0', family: 'IPv4', internal: false, mac: '', cidr: null }], Ethernet: [{ address: '192.168.1.10', netmask: '255.255.255.0', family: 'IPv4', internal: false, mac: '', cidr: null }] };
|
|
60
|
-
check(isSandboxAddress('172.20.5.9', ifaces) && !isSandboxAddress('192.168.1.20', ifaces) && !isSandboxAddress('127.0.0.1', ifaces) && !isSandboxAddress('8.8.8.8', ifaces) && !isSandboxAddress('172.20.0.1', ifaces), 'attach only connects into the sandbox\'s Hyper-V subnet (not LAN, loopback, internet or this PC itself)');
|
|
61
|
-
const filter = new OutputFilter();
|
|
62
|
-
const filtered = Buffer.concat([filter.push(Buffer.from('a\x1b]52;c;ZXZpbA==\x07b\x1b]0;title\x07')), filter.push(Buffer.from('c\x1b]5')), filter.push(Buffer.from('2;c;cGF5\x1b\\d'))]).toString('latin1');
|
|
63
|
-
check(filtered === 'a' + 'b\x1b]0;title\x07' + 'c' + 'd', 'clipboard writes (OSC 52) from the sandbox are stripped, even split across chunks; titles pass');
|
|
64
|
-
check(await rejects(() => new FrameReader().push(Buffer.concat([Buffer.from([16]), Buffer.from([0, 0, 0, 0x7f])])), 'frame too large') && new FrameReader().push(encodeFrame(16, Buffer.alloc(MAX_FRAME_BYTES))).length === 1, 'oversized frames are rejected instead of buffered');
|
|
65
|
-
t.section('git access modes');
|
|
66
|
-
const allowed = (args, mode = 'readonly') => gitDecision(args, mode).allow;
|
|
67
|
-
check(['status', 'log --oneline', 'diff HEAD~1', 'show HEAD', 'blame a.ts', 'branch', 'branch -a', 'branch --contains HEAD', 'tag', 'tag -l v*', 'remote -v', 'config --get user.name', 'stash list', '-C sub status', '--no-pager log'].every((a) => allowed(a.split(' '))), 'readonly: reading commands are allowed');
|
|
68
|
-
check(['commit -m x', 'push', 'add .', 'checkout main', 'switch -c x', 'reset --hard', 'stash', 'stash pop', 'branch new', 'branch -D x', 'tag v1', 'merge x', 'rebase main', 'fetch', 'pull', 'config user.name x', 'remote add o u', '-c core.hooksPath=x status', '--git-dir=x status', 'clean -fd', 'apply p.diff'].every((a) => !allowed(a.split(' '))), 'readonly: anything that writes (or overrides config) is refused');
|
|
69
|
-
check(!allowed(['status'], 'none') && !allowed(['--version'], 'none') && allowed(['commit'], 'full'), 'none: every git call is refused; full: everything allowed');
|
|
70
|
-
const roRules = gitDenyRules('readonly');
|
|
71
|
-
const noneRules = gitDenyRules('none');
|
|
72
|
-
check(gitDenyRules('full').length === 0 && roRules.includes('Bash(git push:*)') && !roRules.some((r) => r.startsWith('Read(')) && noneRules.includes('Bash(git:*)') && noneRules.includes('Read(**/.git/**)'), 'Claude managed deny rules match each mode');
|
|
73
|
-
t.section('repository guard (zero-click code execution on this PC)');
|
|
74
|
-
const repo = at('repo');
|
|
75
|
-
write(path.join(repo, '.git', 'config'), '[core]\n\trepositoryformatversion = 0\n[remote "origin"]\n\turl = https://example.com/x.git\n');
|
|
76
|
-
write(path.join(repo, '.git', 'hooks', 'pre-commit.sample'), 'sample');
|
|
77
|
-
write(path.join(repo, '.git', 'hooks', 'pre-push'), 'existing hook');
|
|
78
|
-
const before = snapshotRepo(repo);
|
|
79
|
-
write(path.join(repo, '.git', 'hooks', 'post-checkout'), 'calc.exe');
|
|
80
|
-
write(path.join(repo, '.git', 'hooks', 'pre-push'), 'changed hook');
|
|
81
|
-
fs.appendFileSync(path.join(repo, '.git', 'config'), '[core]\n\tfsmonitor = calc.exe\n\tsshCommand = evil\n[include]\n\tpath = C:/evil.cfg\n[branch "feature"]\n\tremote = origin\n');
|
|
82
|
-
const findings = guardRepo(repo, before);
|
|
83
|
-
const config = fs.readFileSync(path.join(repo, '.git', 'config'), 'utf8');
|
|
84
|
-
check(!fs.existsSync(path.join(repo, '.git', 'hooks', 'post-checkout')) && !fs.existsSync(path.join(repo, '.git', 'hooks', 'pre-push')) && fs.existsSync(path.join(repo, '.git', 'hooks', 'pre-commit.sample')) && findings.filter((x) => x.kind === 'hook').length === 2, 'new or changed git hooks are quarantined (samples untouched)');
|
|
85
|
-
check(/# \[agent-sandbox\] disabled.*fsmonitor/.test(config) && /# \[agent-sandbox\] disabled.*sshCommand/.test(config) && /# \[agent-sandbox\] disabled.*path = C:\/evil/.test(config) && /^\tremote = origin$/m.test(config) && /^\turl = https/m.test(config), 'dangerous git config added in the sandbox is disabled; harmless changes and existing lines stay');
|
|
86
|
-
check(guardRepo(repo, before).length === 0, 'the guard is idempotent');
|
|
87
|
-
const repo2 = at('repo2');
|
|
88
|
-
write(path.join(repo2, '.git', 'config'), '[core]\n\tbare = false\n');
|
|
89
|
-
write(path.join(repo2, 'build.cmd'), 'echo legit');
|
|
90
|
-
const before2 = snapshotRepo(repo2);
|
|
91
|
-
write(path.join(repo2, 'node.exe'), 'MZ');
|
|
92
|
-
write(path.join(repo2, 'git.cmd'), 'calc');
|
|
93
|
-
write(path.join(repo2, 'sandbox.cmd'), 'calc');
|
|
94
|
-
write(path.join(repo2, 'opencode.ps1'), 'calc');
|
|
95
|
-
write(path.join(repo2, '.git', 'commondir'), 'C:/evil');
|
|
96
|
-
fs.appendFileSync(path.join(repo2, '.git', 'config'), '[core] fsmonitor = calc.exe\n');
|
|
97
|
-
const found2 = guardRepo(repo2, before2);
|
|
98
|
-
check(!fs.existsSync(path.join(repo2, 'node.exe')) && !fs.existsSync(path.join(repo2, 'git.cmd')) && fs.existsSync(path.join(repo2, 'build.cmd')) && !fs.existsSync(path.join(repo2, 'sandbox.cmd')) && found2.filter((x) => x.kind === 'command').length === 4, 'executables planted under command names (node.exe, git.cmd, sandbox.cmd, opencode.ps1) are quarantined; other scripts stay');
|
|
99
|
-
check(!fs.existsSync(path.join(repo2, '.git', 'commondir')) && /# \[agent-sandbox\] disabled.*fsmonitor/.test(fs.readFileSync(path.join(repo2, '.git', 'config'), 'utf8')), 'a planted commondir and a key on the same line as its [section] are neutralised');
|
|
100
|
-
const repo3 = at('repo3');
|
|
101
|
-
write(path.join(repo3, '.git', 'config'), '[core]\n');
|
|
102
|
-
const before3 = snapshotRepo(repo3);
|
|
103
|
-
fs.rmSync(path.join(repo3, '.git'), { recursive: true });
|
|
104
|
-
fs.symlinkSync(victim, path.join(repo3, '.git'), 'junction');
|
|
105
|
-
check(guardRepo(repo3, before3).some((x) => x.kind === 'git' && x.detail.includes('replaced')), '.git replaced by a link is reported loudly');
|
|
106
|
-
t.section('what reaches the sandbox, and what reaches your terminal');
|
|
107
|
-
const state = sandboxGlobalState({ primaryApiKey: 'sk-secret', oauthAccount: { emailAddress: 'me' }, mcpServers: { a: {} }, theme: 'dark', projects: { 'D:/Code/app': { mcpServers: { db: { env: { TOKEN: 'mine' } } } }, 'D:/Other': { mcpServers: { x: { env: { TOKEN: 'other' } } } } } }, 'D:\\Code\\app');
|
|
108
|
-
check(!('primaryApiKey' in state) && !('oauthAccount' in state) && JSON.stringify(state).includes('mine') && !JSON.stringify(state).includes('other') && state.theme === 'dark', 'the sandbox gets your MCP servers and this project, never API keys, your account or other projects');
|
|
109
|
-
const secrets = { strip: true, allow: ['AWS_REGION'], patterns: ['ACME_*'], warnFiles: true };
|
|
110
|
-
const removed = [];
|
|
111
|
-
const settings = stripSettings({ apiKeyHelper: 'aws-vault exec prod -- get-key', model: 'opus', env: {
|
|
112
|
-
AWS_ACCESS_KEY_ID: 'x', AWS_SECRET_ACCESS_KEY: 'y', AWS_REGION: 'eu-west-2', AZURE_CLIENT_SECRET: 'z', ARM_CLIENT_ID: 'id', CLOUDFLARE_API_TOKEN: 't',
|
|
113
|
-
GOOGLE_APPLICATION_CREDENTIALS: 'C:\\key.json', PROD_DB_HOST: 'db.internal', STRIPE_KEY: 'k', ACME_THING: 'a', DATABASE_URL: 'postgres://u:p@h/db',
|
|
114
|
-
INNOCENT: 'AKIAABCDEFGHIJKLMNOP', SAFE_URL: 'https://example.com', DISABLE_TELEMETRY: '1', CLAUDE_CODE_USE_BEDROCK: '1',
|
|
115
|
-
} }, secrets, removed);
|
|
116
|
-
const env = settings.env;
|
|
117
|
-
check(!('apiKeyHelper' in settings) && settings.model === 'opus' && Object.keys(env).sort().join() === 'AWS_REGION,CLAUDE_CODE_USE_BEDROCK,DISABLE_TELEMETRY,SAFE_URL' && removed.some((r) => r.endsWith('AWS_SECRET_ACCESS_KEY')), 'settings.json: AWS, Azure, Google, Cloudflare, prod, payment and database credentials (by name or by value) stay on this PC; secrets.allow and ordinary settings pass');
|
|
118
|
-
const mcp = stripMcpServers({ mcpServers: { gh: { command: 'npx', args: ['server', '--api-key', 'plain', '--verbose', 'ghp_abcdefghijklmnopqrstuvwxyz0123456789'], env: { GITHUB_TOKEN: 'g', LOG_LEVEL: 'debug' } } },
|
|
119
|
-
projects: { 'D:/app': { mcpServers: { api: { type: 'http', url: 'https://x', headers: { Authorization: 'Bearer abcdefghijklmnopqrstuvwxyz', Accept: 'json' } } } } } }, secrets, []);
|
|
120
|
-
const gh = mcp.mcpServers.gh;
|
|
121
|
-
const api = (mcp.projects['D:/app']).mcpServers.api;
|
|
122
|
-
check(JSON.stringify(gh.args) === '["server","--api-key","[removed by agent-sandbox]","--verbose","[removed by agent-sandbox]"]' && JSON.stringify(gh.env) === '{"LOG_LEVEL":"debug"}' && JSON.stringify(api.headers) === '{"Accept":"json"}', 'MCP servers: token env vars, Authorization headers and key arguments are removed, the rest of the server works as before');
|
|
123
|
-
check(JSON.stringify(stripSettings({ env: { AWS_SECRET_ACCESS_KEY: 'y' } }, { ...secrets, strip: false }, [])) === '{"env":{"AWS_SECRET_ACCESS_KEY":"y"}}', 'secrets.strip = false copies settings unchanged');
|
|
124
|
-
const sneaky = stripMcpServers({ mcpServers: { r: { type: 'http', url: 'https://mcp.example.com/mcp?api_key=0123456789abcdef®ion=eu', headers: { 'X-API-Key': 'k', 'x-auth-token': 't', Accept: 'json' }, args: ['--api-key=deadbeefdeadbeef', '--header', 'Authorization: Bearer abcdefghijklmnopqrstuvwxyz', '--port=8080'] } } }, secrets, []);
|
|
125
|
-
const r = sneaky.mcpServers.r;
|
|
126
|
-
check(r.url === 'https://mcp.example.com/mcp?api_key=REMOVED®ion=eu' && JSON.stringify(r.headers) === '{"Accept":"json"}' && r.args[0] === '[removed by agent-sandbox]' && r.args[2] === '[removed by agent-sandbox]' && r.args[3] === '--port=8080', 'MCP servers: X-API-Key headers, ?api_key= in URLs, --flag=value keys and "Authorization: Bearer" arguments are removed too');
|
|
127
|
-
const evil = ['Server=a;Database=a;'.repeat(12000), 'services:\n x:\n image: ' + 'postgres'.repeat(20000) + '!\n', 'Host=a;'.repeat(30000)].map((text, i) => ({ path: ['.env', 'compose.yaml', 'appsettings.json'][i], text }));
|
|
128
|
-
const started = Date.now();
|
|
129
|
-
detectDatabases(evil);
|
|
130
|
-
check(Date.now() - started < 2000, 'database detection stays fast on hostile workspace files (no catastrophic backtracking): ' + (Date.now() - started) + ' ms');
|
|
131
|
-
const profile = os.homedir();
|
|
132
|
-
const refused = [profile, path.dirname(profile), 'C:\\', 'C:\\Windows', '', 'relative\\folder', at('outside-root', 'x')].map((p) => removeRefusal(p, at('safe-root')));
|
|
133
|
-
const allowedTarget = at('safe-root', 'tools', 'go');
|
|
134
|
-
t.write(path.join(allowedTarget, 'go.exe'), 'x');
|
|
135
|
-
safeRemove(allowedTarget, at('safe-root'));
|
|
136
|
-
check(refused.every((r) => r !== null) && !fs.existsSync(allowedTarget), 'deletes can never reach your profile, a folder above it, a drive root, Windows, a relative path or anything outside their root');
|
|
137
|
-
const ws = at('secret-ws');
|
|
138
|
-
for (const file of ['.env', '.env.example', 'server.pem', 'README.md', 'api/credentials.json', 'node_modules/x/.env'])
|
|
139
|
-
t.write(path.join(ws, file), 'x');
|
|
140
|
-
check(findSecretFiles(ws).sort().join() === '.env,api\\credentials.json,server.pem', 'secret-looking workspace files are found for the launch warning (templates and dependencies ignored)');
|
|
141
|
-
check(safeText('ok \x1b[31mred\x1b[0m \x1b]52;c;ZXZpbA==\x07\x1b]0;title\x07\x1b[2J\x07end') === 'ok \x1b[31mred\x1b[0m end', 'sandbox text printed on this PC keeps colours but loses every other escape sequence');
|
|
142
|
-
check(await rejects(() => assertSafeMapping(path.join(t.packageRoot, 'dist'), t.packageRoot, at('storage')), 'overlaps') && await rejects(() => assertSafeMapping(at('storage', 'data'), t.packageRoot, at('storage')), 'overlaps'), 'extra mappings may not reach agent-sandbox\'s own folders');
|
|
143
|
-
};
|
|
File without changes
|