windows-development-sandbox 1.0.5 → 1.4.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 +25 -8
- package/config.example.json +55 -5
- package/dist/api.js +2 -1
- package/dist/cli/args.d.ts +23 -0
- package/dist/cli/args.js +38 -4
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/accounts.js +2 -2
- 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 +5 -1
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +106 -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 +6 -4
- package/dist/cli/commands/index.js +6 -4
- package/dist/cli/commands/launch.d.ts +26 -2
- package/dist/cli/commands/launch.js +256 -42
- package/dist/cli/commands/maintenance.d.ts +2 -0
- package/dist/cli/commands/maintenance.js +67 -5
- 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 +42 -15
- package/dist/cli/manifest.d.ts +8 -0
- package/dist/cli/manifest.js +40 -15
- package/dist/cli/menu/apps.d.ts +21 -0
- package/dist/cli/menu/apps.js +122 -0
- package/dist/cli/menu/command-form.d.ts +28 -0
- package/dist/cli/menu/command-form.js +219 -0
- package/dist/cli/menu/index.d.ts +26 -0
- package/dist/cli/menu/index.js +180 -0
- package/dist/cli/menu/settings/categories.d.ts +9 -0
- package/dist/cli/menu/settings/categories.js +28 -0
- package/dist/cli/menu/settings/config-file.d.ts +40 -0
- package/dist/cli/menu/settings/config-file.js +71 -0
- package/dist/cli/menu/settings/editors.d.ts +30 -0
- package/dist/cli/menu/settings/editors.js +305 -0
- package/dist/cli/menu/settings/index.d.ts +32 -0
- package/dist/cli/menu/settings/index.js +136 -0
- package/dist/cli/menu/settings/schema-node.d.ts +62 -0
- package/dist/cli/menu/settings/schema-node.js +104 -0
- package/dist/cli/menu/several.d.ts +20 -0
- package/dist/cli/menu/several.js +102 -0
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/tui/app.d.ts +39 -0
- package/dist/cli/tui/app.js +52 -0
- package/dist/cli/tui/dialogs.d.ts +57 -0
- package/dist/cli/tui/dialogs.js +162 -0
- package/dist/cli/tui/frame.d.ts +18 -0
- package/dist/cli/tui/frame.js +27 -0
- package/dist/cli/tui/index.d.ts +7 -0
- package/dist/cli/tui/index.js +8 -0
- package/dist/cli/tui/list-screen.d.ts +101 -0
- package/dist/cli/tui/list-screen.js +299 -0
- package/dist/cli/tui/terminal.d.ts +37 -0
- package/dist/cli/tui/terminal.js +89 -0
- package/dist/cli/tui/text.d.ts +20 -0
- package/dist/cli/tui/text.js +105 -0
- package/dist/cli/tui/theme.d.ts +45 -0
- package/dist/cli/tui/theme.js +30 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.js +12 -6
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +23 -0
- package/dist/core/local-config.js +79 -7
- 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/apps.d.ts +40 -0
- package/dist/core/schema/apps.js +27 -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 +659 -0
- package/dist/core/schema/config.js +75 -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 +32 -0
- package/dist/core/schema/profile.js +31 -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 +78 -0
- package/dist/core/schema/sandbox.js +64 -0
- package/dist/core/schema/toolchains.d.ts +96 -0
- package/dist/core/schema/toolchains.js +88 -0
- package/dist/core/schema.d.ts +6 -604
- package/dist/core/schema.js +8 -264
- package/dist/core/types.d.ts +35 -1
- package/dist/core/util.d.ts +9 -0
- package/dist/core/util.js +14 -3
- package/dist/core/workspace.d.ts +0 -5
- package/dist/core/workspace.js +33 -50
- package/dist/guest/apps/index.d.ts +10 -0
- package/dist/guest/apps/index.js +206 -0
- package/dist/guest/apps/resolvers.d.ts +8 -0
- package/dist/guest/apps/resolvers.js +90 -0
- package/dist/guest/audit-parse.d.ts +7 -0
- package/dist/guest/audit-parse.js +24 -0
- package/dist/guest/audit.d.ts +4 -0
- package/dist/guest/audit.js +47 -0
- 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/relays.d.ts +1 -0
- package/dist/guest/daemon/relays.js +18 -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 +12 -141
- package/dist/guest/db.js +18 -3
- package/dist/guest/env.d.ts +3 -0
- package/dist/guest/env.js +7 -3
- 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 +31 -8
- package/dist/guest/netproxy.d.ts +1 -0
- package/dist/guest/netproxy.js +100 -0
- 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 +17 -65
- package/dist/guest/setup/agents.d.ts +5 -2
- package/dist/guest/setup/agents.js +22 -8
- package/dist/guest/setup/claude.d.ts +0 -4
- package/dist/guest/setup/claude.js +59 -5
- 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/setup/network.d.ts +9 -0
- package/dist/guest/setup/network.js +97 -4
- package/dist/guest/setup/user.js +4 -0
- 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 +56 -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 +24 -0
- package/dist/guest/toolchains/plan.js +140 -0
- package/dist/guest/toolchains/pwsh.js +1 -1
- package/dist/guest/toolchains/python.js +43 -12
- 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/toolchains/vscode-profile.d.ts +30 -0
- package/dist/guest/toolchains/vscode-profile.js +69 -0
- package/dist/guest/toolchains/vscode.d.ts +17 -0
- package/dist/guest/toolchains/vscode.js +87 -1
- 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 +70 -0
- package/dist/host/audit/collect.d.ts +34 -0
- package/dist/host/audit/collect.js +126 -0
- package/dist/host/audit/log.d.ts +46 -0
- package/dist/host/audit/log.js +143 -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 +10 -12
- package/dist/host/generate.js +58 -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 +54 -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/motw.d.ts +17 -0
- package/dist/host/motw.js +74 -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 +16 -1
- package/dist/host/port-forward.d.ts +8 -0
- package/dist/host/port-forward.js +37 -0
- 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 +25 -5
- package/dist/host/repo-guard.js +242 -23
- package/dist/host/stops.d.ts +67 -0
- package/dist/host/stops.js +126 -0
- package/dist/host/terminal.d.ts +8 -0
- package/dist/host/terminal.js +48 -1
- 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/workspace-view/index.d.ts +20 -0
- package/dist/host/workspace-view/index.js +53 -0
- package/dist/host/workspace-view/rules.d.ts +26 -0
- package/dist/host/workspace-view/rules.js +93 -0
- package/dist/host/workspace-view/view.d.ts +50 -0
- package/dist/host/workspace-view/view.js +281 -0
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +11791 -2821
- package/dist/runtime/db.js +6585 -466
- package/dist/runtime/main.js +10238 -1724
- package/dist/runtime/netproxy.js +25936 -0
- package/dist/runtime/view.js +6183 -59
- 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/audit.test.d.ts +2 -0
- package/dist/selftest/audit.test.js +57 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +44 -0
- package/dist/selftest/cli.test.d.ts +2 -2
- package/dist/selftest/cli.test.js +145 -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 +89 -4
- package/dist/selftest/fixtures.js +43 -24
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +68 -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 +137 -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-network.test.d.ts +2 -0
- package/dist/selftest/live-network.test.js +63 -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 +120 -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 +40 -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 +121 -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 +130 -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 +133 -0
- package/dist/selftest/workspace-view.test.d.ts +2 -0
- package/dist/selftest/workspace-view.test.js +89 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/apps.d.ts +49 -0
- package/dist/shared/apps.js +114 -0
- package/dist/shared/download.d.ts +3 -1
- package/dist/shared/download.js +19 -4
- package/dist/shared/netpolicy.d.ts +27 -0
- package/dist/shared/netpolicy.js +51 -0
- 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/pty-client.d.ts +9 -4
- package/dist/shared/pty-client.js +139 -17
- package/dist/shared/rundir.d.ts +12 -1
- package/dist/shared/rundir.js +14 -4
- package/dist/shared/safe-remove.js +11 -0
- package/dist/shared/schema.d.ts +453 -0
- package/dist/shared/schema.js +43 -2
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.js +6 -3
- package/dist/sync/history.d.ts +29 -12
- package/dist/sync/history.js +72 -25
- package/dist/sync/watcher.d.ts +8 -0
- package/dist/sync/watcher.js +99 -8
- package/package.json +7 -6
- package/sandbox/bootstrap.ps1 +258 -258
- package/sandbox/cargoshim.cs +56 -0
- package/sandbox/cpucap.cs +33 -0
- package/sandbox/cpucap.ps1 +20 -0
- package/sandbox/logon.cmd +24 -16
- package/sandbox/memlog.ps1 +34 -0
- package/sandbox/openssl.cs +42 -0
- package/sandbox/pagefile.cs +31 -0
- package/sandbox/pagefile.ps1 +21 -0
- package/sandbox/ptyhost.cs +120 -11
- package/sandbox/trust.cmd +35 -35
- package/schemas/config.schema.json +403 -3
- package/schemas/sandbox-config.schema.json +143 -13
- 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/menu.d.ts +0 -22
- package/dist/cli/menu.js +0 -187
- 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
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Several projects in one running sandbox. Windows Sandbox fixes its mapped folders at boot, so a project can only
|
|
2
|
+
// join a running sandbox when it is already visible inside:
|
|
3
|
+
// - the launch folder (mapped directly, as always);
|
|
4
|
+
// - a folder inside a project root (projects.roots: whole folders of projects, mapped at every start), including
|
|
5
|
+
// projects created after boot;
|
|
6
|
+
// - projects mapped at boot because they were in use when the sandbox was restarted to add them;
|
|
7
|
+
// - a folder shared into the running sandbox with `wsb share` (Windows 11 24H2+, untested).
|
|
8
|
+
// Roots, boot-mapped projects and shares appear under C:\Projects (roots at their PC path with guestWorkspace "mirror").
|
|
9
|
+
// Pure functions only: used by the host (registration, commands), the guest (daemon checks) and the tests.
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { isValidGuestPath } from './guest-paths.js';
|
|
12
|
+
/** Where roots, boot-mapped projects and shared folders appear inside the sandbox. */
|
|
13
|
+
export const PROJECTS_GUEST = 'C:\\Projects';
|
|
14
|
+
const norm = (p) => path.win32.resolve(p).replace(/[\\/]+$/, '').toLowerCase();
|
|
15
|
+
/** `child` is `parent` or somewhere below it (Windows paths, case-insensitive). */
|
|
16
|
+
export const isWithin = (parent, child) => {
|
|
17
|
+
const a = norm(parent);
|
|
18
|
+
const b = norm(child);
|
|
19
|
+
return a === b || b.startsWith(a.endsWith('\\') ? a : a + '\\');
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A project id (also its folder name under C:\Projects) for a folder: its name with only letters, digits, '-' and
|
|
23
|
+
* '_' (ids become file names on both sides), at most 48 characters, made unique against `taken` with -2, -3, ...
|
|
24
|
+
*/
|
|
25
|
+
export const hubName = (hostDir, taken) => {
|
|
26
|
+
const base = path.win32.basename(path.win32.resolve(hostDir)).replace(/[^A-Za-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 48) || 'project';
|
|
27
|
+
const used = new Set([...taken].map((t) => t.toLowerCase()));
|
|
28
|
+
if (!used.has(base.toLowerCase()))
|
|
29
|
+
return base;
|
|
30
|
+
for (let i = 2;; i++)
|
|
31
|
+
if (!used.has((base + '-' + i).toLowerCase()))
|
|
32
|
+
return base + '-' + i;
|
|
33
|
+
};
|
|
34
|
+
/** The project a folder on this PC belongs to: the most specific one that contains it (a project inside another wins). */
|
|
35
|
+
export const projectForHostDir = (projects, hostDir) => projects.filter((p) => isWithin(p.host, hostDir)).sort((a, b) => b.host.length - a.host.length)[0] ?? null;
|
|
36
|
+
/** The same for a folder inside the sandbox. */
|
|
37
|
+
export const projectForGuestDir = (projects, guestDir) => projects.filter((p) => isWithin(p.guest, guestDir)).sort((a, b) => b.guest.length - a.guest.length)[0] ?? null;
|
|
38
|
+
/** Where a folder on this PC appears inside the sandbox, given the mapped folder (project or root) that contains it. */
|
|
39
|
+
export const guestDirFor = (mapped, hostDir) => {
|
|
40
|
+
const rel = path.win32.relative(path.win32.resolve(mapped.host), path.win32.resolve(hostDir));
|
|
41
|
+
return rel ? path.win32.join(mapped.guest, rel) : mapped.guest;
|
|
42
|
+
};
|
|
43
|
+
/** The other way: where a folder inside the sandbox is on this PC, given the mapped folder that contains it. */
|
|
44
|
+
export const hostDirFor = (mapped, guestDir) => {
|
|
45
|
+
const rel = path.win32.relative(path.win32.resolve(mapped.guest), path.win32.resolve(guestDir));
|
|
46
|
+
return rel ? path.win32.join(mapped.host, rel) : mapped.host;
|
|
47
|
+
};
|
|
48
|
+
/** Strictly below `parent` (never `parent` itself). */
|
|
49
|
+
export const isBelow = (parent, child) => isWithin(parent, child) && norm(parent) !== norm(child);
|
|
50
|
+
/** A guest folder some project can be in: below C:\Projects, or inside one of the roots (their guest paths). */
|
|
51
|
+
export const insideProjects = (guestDir, roots = [], projectsDir = PROJECTS_GUEST) => isBelow(projectsDir, guestDir) || roots.some((r) => isWithin(r, guestDir));
|
|
52
|
+
/**
|
|
53
|
+
* Guest folders for the project roots and the projects mapped at boot: C:\Projects\<name>, unique; with
|
|
54
|
+
* guestWorkspace "mirror" a root appears at its PC path when the sandbox can show it there.
|
|
55
|
+
*/
|
|
56
|
+
export const planProjectMappings = (roots, projects, mirror, taken = []) => {
|
|
57
|
+
const names = [...taken];
|
|
58
|
+
const place = (host, tryMirror) => {
|
|
59
|
+
const resolved = path.win32.resolve(host).replace(/[\\/]+$/, '');
|
|
60
|
+
if (tryMirror && isValidGuestPath(resolved) && !isWithin(PROJECTS_GUEST, resolved))
|
|
61
|
+
return { host: resolved, guest: resolved };
|
|
62
|
+
const name = hubName(resolved, names);
|
|
63
|
+
names.push(name);
|
|
64
|
+
return { host: resolved, guest: path.win32.join(PROJECTS_GUEST, name) };
|
|
65
|
+
};
|
|
66
|
+
return { roots: roots.map((r) => place(r, mirror)), projects: projects.map((p) => place(p, false)) };
|
|
67
|
+
};
|
|
@@ -10,6 +10,7 @@ export declare const FrameType: {
|
|
|
10
10
|
readonly Error: 18;
|
|
11
11
|
readonly Restarting: 19;
|
|
12
12
|
readonly Status: 20;
|
|
13
|
+
readonly Console: 21;
|
|
13
14
|
};
|
|
14
15
|
/** HELLO flags: control-only connections get no output and never size the console; a query only asks for STATUS. */
|
|
15
16
|
export declare const HelloFlag: {
|
|
@@ -49,12 +50,16 @@ export declare class FrameReader {
|
|
|
49
50
|
push(chunk: Buffer): Frame[];
|
|
50
51
|
}
|
|
51
52
|
/**
|
|
52
|
-
* Removes
|
|
53
|
-
*
|
|
54
|
-
*
|
|
53
|
+
* Removes the terminal "strings" an untrusted program could abuse from the sandbox's output before it reaches your
|
|
54
|
+
* terminal: clipboard writes (OSC 52), terminal macros (ConEmu's OSC 9), DCS / APC / PM / SOS, and their C1 forms.
|
|
55
|
+
* Titles, links, progress and colours pass. A byte-level state machine, so no split across chunks, leading zero
|
|
56
|
+
* (ESC]052) or huge payload slips through: a dropped string stays dropped until its terminator, however long it is.
|
|
55
57
|
*/
|
|
56
58
|
export declare class OutputFilter {
|
|
57
|
-
|
|
59
|
+
/** text: normal output; esc: after ESC; c2: after a 0xC2 lead byte; string: inside an OSC/DCS/...; stringEsc / stringC2: a possible terminator inside one. */
|
|
60
|
+
private state;
|
|
61
|
+
/** The string being read: an OSC worth keeping is collected until its terminator; null = being dropped. */
|
|
62
|
+
private body;
|
|
58
63
|
push(chunk: Buffer): Buffer;
|
|
59
64
|
}
|
|
60
65
|
/** Tries every address the sandbox published and returns the first one that answers. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Client side of the ptyhost protocol (see sandbox/ptyhost.cs). Used by the window inside the sandbox and by
|
|
2
2
|
// `--terminal` / `attach` on the host, so it only depends on Node built-ins.
|
|
3
3
|
import net from 'node:net';
|
|
4
|
-
export const FrameType = { Hello: 1, Input: 2, Resize: 3, Restart: 4, Ping: 5, Output: 16, Exit: 17, Error: 18, Restarting: 19, Status: 20 };
|
|
4
|
+
export const FrameType = { Hello: 1, Input: 2, Resize: 3, Restart: 4, Ping: 5, Output: 16, Exit: 17, Error: 18, Restarting: 19, Status: 20, Console: 21 };
|
|
5
5
|
/** HELLO flags: control-only connections get no output and never size the console; a query only asks for STATUS. */
|
|
6
6
|
export const HelloFlag = { ControlOnly: 1, Query: 2 };
|
|
7
7
|
/** Viewers ping this often; ptyhost counts a viewer as gone after 15 s of silence (e.g. its terminal tab was closed). */
|
|
@@ -47,23 +47,140 @@ export class FrameReader {
|
|
|
47
47
|
return frames;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
/** OSC commands that may reach your terminal: window title (0, 1, 2), links (8), progress (9;4), shell marks (133). */
|
|
51
|
+
const KEPT_OSC = (body) => {
|
|
52
|
+
const number = /^(\d{1,4})(;|$)/.exec(body);
|
|
53
|
+
if (!number)
|
|
54
|
+
return false;
|
|
55
|
+
const n = Number(number[1]);
|
|
56
|
+
return n <= 2 || n === 8 || n === 133 || (n === 9 && body.startsWith(number[1] + ';4;'));
|
|
57
|
+
};
|
|
58
|
+
/** Longest OSC kept (a title or a link); anything longer is dropped whole. */
|
|
59
|
+
const MAX_KEPT_STRING = 4096;
|
|
50
60
|
/**
|
|
51
|
-
* Removes
|
|
52
|
-
*
|
|
53
|
-
*
|
|
61
|
+
* Removes the terminal "strings" an untrusted program could abuse from the sandbox's output before it reaches your
|
|
62
|
+
* terminal: clipboard writes (OSC 52), terminal macros (ConEmu's OSC 9), DCS / APC / PM / SOS, and their C1 forms.
|
|
63
|
+
* Titles, links, progress and colours pass. A byte-level state machine, so no split across chunks, leading zero
|
|
64
|
+
* (ESC]052) or huge payload slips through: a dropped string stays dropped until its terminator, however long it is.
|
|
54
65
|
*/
|
|
55
66
|
export class OutputFilter {
|
|
56
|
-
|
|
67
|
+
/** text: normal output; esc: after ESC; c2: after a 0xC2 lead byte; string: inside an OSC/DCS/...; stringEsc / stringC2: a possible terminator inside one. */
|
|
68
|
+
state = 'text';
|
|
69
|
+
/** The string being read: an OSC worth keeping is collected until its terminator; null = being dropped. */
|
|
70
|
+
body = null;
|
|
57
71
|
push(chunk) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
const out = Buffer.allocUnsafe(chunk.length + MAX_KEPT_STRING + 8);
|
|
73
|
+
let n = 0;
|
|
74
|
+
const put = (...bytes) => { for (const b of bytes)
|
|
75
|
+
out[n++] = b; };
|
|
76
|
+
/** After ESC (or the C1 form): ] opens an OSC (maybe kept), P X ^ _ open DCS / SOS / PM / APC (dropped). */
|
|
77
|
+
const opens = (b, c1) => {
|
|
78
|
+
if (b === (c1 ? 0x9d : 0x5d)) {
|
|
79
|
+
this.body = c1 ? null : [];
|
|
80
|
+
this.state = 'string';
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if ((c1 ? [0x90, 0x98, 0x9e, 0x9f] : [0x50, 0x58, 0x5e, 0x5f]).includes(b)) {
|
|
84
|
+
this.body = null;
|
|
85
|
+
this.state = 'string';
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
};
|
|
90
|
+
const endString = (terminator) => {
|
|
91
|
+
const body = this.body && Buffer.from(this.body).toString('latin1');
|
|
92
|
+
if (body !== null && KEPT_OSC(body)) {
|
|
93
|
+
put(0x1b, 0x5d);
|
|
94
|
+
out.write(body, n, 'latin1');
|
|
95
|
+
n += body.length;
|
|
96
|
+
put(...terminator);
|
|
97
|
+
}
|
|
98
|
+
this.body = null;
|
|
99
|
+
this.state = 'text';
|
|
100
|
+
};
|
|
101
|
+
const collect = (b) => { if (this.body && this.body.push(b) > MAX_KEPT_STRING)
|
|
102
|
+
this.body = null; };
|
|
103
|
+
for (const b of chunk) {
|
|
104
|
+
switch (this.state) {
|
|
105
|
+
case 'text':
|
|
106
|
+
if (b === 0x1b)
|
|
107
|
+
this.state = 'esc';
|
|
108
|
+
else if (b === 0xc2)
|
|
109
|
+
this.state = 'c2';
|
|
110
|
+
else
|
|
111
|
+
put(b);
|
|
112
|
+
break;
|
|
113
|
+
case 'esc':
|
|
114
|
+
if (opens(b, false))
|
|
115
|
+
break;
|
|
116
|
+
if (b !== 0x1b) {
|
|
117
|
+
put(0x1b, b);
|
|
118
|
+
this.state = 'text';
|
|
119
|
+
}
|
|
120
|
+
else
|
|
121
|
+
put(0x1b);
|
|
122
|
+
break;
|
|
123
|
+
case 'c2':
|
|
124
|
+
if (opens(b, true))
|
|
125
|
+
break;
|
|
126
|
+
if (b === 0x1b) {
|
|
127
|
+
put(0xc2);
|
|
128
|
+
this.state = 'esc';
|
|
129
|
+
}
|
|
130
|
+
else if (b === 0xc2)
|
|
131
|
+
put(0xc2);
|
|
132
|
+
else {
|
|
133
|
+
put(0xc2, b);
|
|
134
|
+
this.state = 'text';
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
case 'string':
|
|
138
|
+
if (b === 0x07)
|
|
139
|
+
endString([0x07]);
|
|
140
|
+
else if (b === 0x1b)
|
|
141
|
+
this.state = 'stringEsc';
|
|
142
|
+
else if (b === 0xc2)
|
|
143
|
+
this.state = 'stringC2';
|
|
144
|
+
else if (b === 0x18 || b === 0x1a) {
|
|
145
|
+
this.body = null;
|
|
146
|
+
this.state = 'text';
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
collect(b);
|
|
150
|
+
break;
|
|
151
|
+
case 'stringC2':
|
|
152
|
+
// U+009C is the C1 string terminator; any other character is part of the string.
|
|
153
|
+
if (b === 0x9c)
|
|
154
|
+
endString([0x1b, 0x5c]);
|
|
155
|
+
else {
|
|
156
|
+
collect(0xc2);
|
|
157
|
+
this.state = 'string';
|
|
158
|
+
if (b === 0x07)
|
|
159
|
+
endString([0x07]);
|
|
160
|
+
else if (b === 0x1b)
|
|
161
|
+
this.state = 'stringEsc';
|
|
162
|
+
else if (b !== 0xc2)
|
|
163
|
+
collect(b);
|
|
164
|
+
else
|
|
165
|
+
this.state = 'stringC2';
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case 'stringEsc':
|
|
169
|
+
if (b === 0x5c) {
|
|
170
|
+
endString([0x1b, 0x5c]);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
// ESC inside a string ends it (dropped) and starts a new sequence.
|
|
174
|
+
this.body = null;
|
|
175
|
+
this.state = 'esc';
|
|
176
|
+
if (!opens(b, false) && b !== 0x1b) {
|
|
177
|
+
put(0x1b, b);
|
|
178
|
+
this.state = 'text';
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
65
182
|
}
|
|
66
|
-
return
|
|
183
|
+
return out.subarray(0, n);
|
|
67
184
|
}
|
|
68
185
|
}
|
|
69
186
|
const connectOnce = (host, port, timeoutMs) => {
|
|
@@ -177,15 +294,18 @@ export const runTerminalClient = (socket, token, options = {}) => {
|
|
|
177
294
|
}
|
|
178
295
|
socket.write(encodeFrame(FrameType.Input, swapKeys ? fixWindowsKeys(chunk) : chunk));
|
|
179
296
|
};
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
// then let ptyhost repaint everything once at the new size.
|
|
297
|
+
// Windows' own ConPTY repaints the whole screen on every resize; dragging a window edge fires dozens of them, and
|
|
298
|
+
// repaints racing each other left the screen garbled. Wait until the size settles, clear what this terminal
|
|
299
|
+
// reflowed on its own, then let ptyhost repaint everything once at the new size. With conpty.dll (passthrough)
|
|
300
|
+
// nothing is repainted: this terminal's own reflow and scrollback stay, and the program redraws what it needs.
|
|
301
|
+
let passthrough = false;
|
|
183
302
|
const sendSize = () => {
|
|
184
303
|
const [c, r] = size();
|
|
185
304
|
if (c === sent[0] && r === sent[1])
|
|
186
305
|
return;
|
|
187
306
|
sent = [c, r];
|
|
188
|
-
|
|
307
|
+
if (!passthrough)
|
|
308
|
+
stdout.write('\x1b[H\x1b[J');
|
|
189
309
|
socket.write(resizeFrame(c, r));
|
|
190
310
|
};
|
|
191
311
|
const onResize = () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { resizeTimer = undefined; sendSize(); }, 120); };
|
|
@@ -205,6 +325,8 @@ export const runTerminalClient = (socket, token, options = {}) => {
|
|
|
205
325
|
for (const frame of frames) {
|
|
206
326
|
if (frame.type === FrameType.Output)
|
|
207
327
|
stdout.write(filter.push(frame.payload));
|
|
328
|
+
else if (frame.type === FrameType.Console)
|
|
329
|
+
passthrough = frame.payload.toString('latin1') === 'conpty.dll';
|
|
208
330
|
else if (frame.type === FrameType.Restarting)
|
|
209
331
|
stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
210
332
|
else if (frame.type === FrameType.Exit)
|
package/dist/shared/rundir.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { GuestRequestT, GuestResponseT, ProjectStatusT, SandboxStatusT, SessionStatusT } from './schema.js';
|
|
2
3
|
/** Anything bigger than this from the other side is ignored (a compromised sandbox must not exhaust the host). */
|
|
3
4
|
export declare const MAX_MESSAGE_BYTES: number;
|
|
4
5
|
/** Ids become file names, so only a strict alphabet is allowed (no separators, no dots). */
|
|
@@ -9,20 +10,30 @@ export declare const runPaths: (dataDir: string) => {
|
|
|
9
10
|
requests: string;
|
|
10
11
|
responses: string;
|
|
11
12
|
sessions: string;
|
|
13
|
+
projects: string;
|
|
14
|
+
/** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
|
|
15
|
+
audit: string;
|
|
12
16
|
request: (id: string) => string;
|
|
13
17
|
response: (id: string) => string;
|
|
14
18
|
session: (id: string) => string;
|
|
19
|
+
project: (id: string) => string;
|
|
15
20
|
};
|
|
16
21
|
export declare const readJsonFile: <T>(file: string) => T | null;
|
|
22
|
+
/** Reads and validates; a missing, oversized or malformed file is simply absent. */
|
|
23
|
+
export declare const readValid: <S extends z.ZodType>(schema: S, file: string) => z.output<S> | null;
|
|
17
24
|
/**
|
|
18
25
|
* Write to a uniquely named temp file and rename, so a reader on the other side of the mapped folder never sees half a
|
|
19
26
|
* file. The rename is retried briefly: antivirus or the other side's reader can hold the target for a moment.
|
|
20
27
|
*/
|
|
21
28
|
export declare const writeJsonAtomic: (file: string, value: unknown) => void;
|
|
29
|
+
/** Same for any text file (two commands at once, or a watcher and a command, never leave a half-written file). */
|
|
30
|
+
export declare const writeFileAtomic: (file: string, text: string) => void;
|
|
22
31
|
export declare const readSandboxStatus: (dataDir: string) => SandboxStatusT | null;
|
|
23
32
|
export declare const readSession: (dataDir: string, id: string) => SessionStatusT | null;
|
|
24
33
|
export declare const readResponse: (dataDir: string, id: string) => GuestResponseT | null;
|
|
25
34
|
export declare const listSessions: (dataDir: string) => SessionStatusT[];
|
|
35
|
+
export declare const readProjectStatus: (dataDir: string, name: string) => ProjectStatusT | null;
|
|
36
|
+
export declare const listProjectStatus: (dataDir: string) => ProjectStatusT[];
|
|
26
37
|
export declare const scanRequests: (dataDir: string) => {
|
|
27
38
|
valid: GuestRequestT[];
|
|
28
39
|
invalid: string[];
|
package/dist/shared/rundir.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// written atomically and parsed with the channel schemas on read. Anything that does not parse is ignored.
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import { GuestRequest, GuestResponse, Id, SandboxStatus, SessionStatus } from './schema.js';
|
|
6
|
+
import { GuestRequest, GuestResponse, Id, ProjectStatus, SandboxStatus, SessionStatus } from './schema.js';
|
|
7
|
+
import { assertTestWritable } from './test-guard.js';
|
|
7
8
|
/** Anything bigger than this from the other side is ignored (a compromised sandbox must not exhaust the host). */
|
|
8
9
|
export const MAX_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
9
10
|
/** Ids become file names, so only a strict alphabet is allowed (no separators, no dots). */
|
|
@@ -17,9 +18,13 @@ export const runPaths = (dataDir) => {
|
|
|
17
18
|
requests: path.join(run, 'requests'),
|
|
18
19
|
responses: path.join(run, 'responses'),
|
|
19
20
|
sessions: path.join(run, 'sessions'),
|
|
21
|
+
projects: path.join(run, 'projects'),
|
|
22
|
+
/** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
|
|
23
|
+
audit: path.join(run, 'audit'),
|
|
20
24
|
request: file('requests'),
|
|
21
25
|
response: file('responses'),
|
|
22
26
|
session: file('sessions'),
|
|
27
|
+
project: file('projects'),
|
|
23
28
|
};
|
|
24
29
|
};
|
|
25
30
|
export const readJsonFile = (file) => {
|
|
@@ -33,7 +38,7 @@ export const readJsonFile = (file) => {
|
|
|
33
38
|
}
|
|
34
39
|
};
|
|
35
40
|
/** Reads and validates; a missing, oversized or malformed file is simply absent. */
|
|
36
|
-
const readValid = (schema, file) => {
|
|
41
|
+
export const readValid = (schema, file) => {
|
|
37
42
|
const parsed = schema.safeParse(readJsonFile(file));
|
|
38
43
|
return parsed.success ? parsed.data : null;
|
|
39
44
|
};
|
|
@@ -42,10 +47,13 @@ const pause = (ms) => { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0
|
|
|
42
47
|
* Write to a uniquely named temp file and rename, so a reader on the other side of the mapped folder never sees half a
|
|
43
48
|
* file. The rename is retried briefly: antivirus or the other side's reader can hold the target for a moment.
|
|
44
49
|
*/
|
|
45
|
-
export const writeJsonAtomic = (file, value) =>
|
|
50
|
+
export const writeJsonAtomic = (file, value) => writeFileAtomic(file, JSON.stringify(value, null, 2) + '\n');
|
|
51
|
+
/** Same for any text file (two commands at once, or a watcher and a command, never leave a half-written file). */
|
|
52
|
+
export const writeFileAtomic = (file, text) => {
|
|
53
|
+
assertTestWritable(file);
|
|
46
54
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
47
55
|
const temp = file + '.' + process.pid + '-' + Math.random().toString(36).slice(2) + '.tmp';
|
|
48
|
-
fs.writeFileSync(temp,
|
|
56
|
+
fs.writeFileSync(temp, text, { encoding: 'utf8', flag: 'wx' });
|
|
49
57
|
for (let attempt = 1;; attempt++) {
|
|
50
58
|
try {
|
|
51
59
|
fs.renameSync(temp, file);
|
|
@@ -82,5 +90,7 @@ export const readSandboxStatus = (dataDir) => readValid(SandboxStatus, runPaths(
|
|
|
82
90
|
export const readSession = (dataDir, id) => readValid(SessionStatus, runPaths(dataDir).session(id));
|
|
83
91
|
export const readResponse = (dataDir, id) => readValid(GuestResponse, runPaths(dataDir).response(id));
|
|
84
92
|
export const listSessions = (dataDir) => scan(SessionStatus, runPaths(dataDir).sessions).valid.sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
93
|
+
export const readProjectStatus = (dataDir, name) => readValid(ProjectStatus, runPaths(dataDir).project(name));
|
|
94
|
+
export const listProjectStatus = (dataDir) => scan(ProjectStatus, runPaths(dataDir).projects).valid.slice(0, 64);
|
|
85
95
|
export const scanRequests = (dataDir) => scan(GuestRequest, runPaths(dataDir).requests);
|
|
86
96
|
export const now = () => new Date().toISOString();
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { assertTestWritable } from './test-guard.js';
|
|
6
7
|
const norm = (p) => path.resolve(p).replace(/[\\/]+$/, '').toLowerCase();
|
|
7
8
|
const isAncestorOrSame = (parent, child) => { const a = norm(parent); const b = norm(child); return a === b || b.startsWith(a + path.sep); };
|
|
8
9
|
/** Folders no delete may ever reach, or contain: profiles, Windows, program folders. */
|
|
@@ -10,6 +11,9 @@ const protectedFolders = () => [
|
|
|
10
11
|
os.homedir(), process.env.USERPROFILE, process.env.APPDATA, process.env.LOCALAPPDATA, path.join(os.homedir(), 'Documents'), path.join(os.homedir(), 'Desktop'),
|
|
11
12
|
process.env.SystemRoot, process.env.WINDIR, process.env.ProgramFiles, process.env['ProgramFiles(x86)'], process.env.ProgramData, 'C:\\Users', 'C:\\Windows',
|
|
12
13
|
].filter((p) => typeof p === 'string' && p.trim() !== '');
|
|
14
|
+
/** Folders nothing of ours lives in, so nothing below them may be deleted either. */
|
|
15
|
+
const systemFolders = () => [process.env.SystemRoot, process.env.WINDIR, process.env.ProgramFiles, process.env['ProgramFiles(x86)'], 'C:\\Windows', 'C:\\Program Files', 'C:\\Program Files (x86)']
|
|
16
|
+
.filter((p) => typeof p === 'string' && p.trim() !== '');
|
|
13
17
|
/**
|
|
14
18
|
* Why `target` may not be deleted, or null when it may. Refused: a relative path, a drive root, a protected folder or
|
|
15
19
|
* any folder that contains one, and anything that is not strictly inside `root`.
|
|
@@ -17,12 +21,18 @@ const protectedFolders = () => [
|
|
|
17
21
|
export const removeRefusal = (target, root) => {
|
|
18
22
|
if (!target || !path.isAbsolute(target) || !root || !path.isAbsolute(root))
|
|
19
23
|
return 'not an absolute path';
|
|
24
|
+
// \\?\C:\Users and \\.\ spell the same folders in a way the checks below would not recognise.
|
|
25
|
+
if (/^[\\/]{2}[?.][\\/]/.test(target) || /^[\\/]{2}[?.][\\/]/.test(root))
|
|
26
|
+
return 'a device path (\\\\?\\ or \\\\.\\)';
|
|
20
27
|
const t = norm(target);
|
|
21
28
|
if (t === norm(path.parse(t).root))
|
|
22
29
|
return 'a drive root';
|
|
23
30
|
const hit = protectedFolders().find((p) => isAncestorOrSame(t, p));
|
|
24
31
|
if (hit)
|
|
25
32
|
return 'it is, or contains, ' + hit;
|
|
33
|
+
const system = systemFolders().find((p) => isAncestorOrSame(p, t));
|
|
34
|
+
if (system)
|
|
35
|
+
return 'it is inside ' + system;
|
|
26
36
|
if (!t.startsWith(norm(root) + path.sep))
|
|
27
37
|
return 'it is not inside ' + root;
|
|
28
38
|
return null;
|
|
@@ -35,6 +45,7 @@ export const safeRemove = (target, root) => {
|
|
|
35
45
|
const refusal = removeRefusal(target, root);
|
|
36
46
|
if (refusal)
|
|
37
47
|
throw new Error('Refusing to delete ' + target + ': ' + refusal);
|
|
48
|
+
assertTestWritable(target);
|
|
38
49
|
let stat;
|
|
39
50
|
try {
|
|
40
51
|
stat = fs.lstatSync(target);
|