windows-development-sandbox 1.5.0 → 1.7.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/README.md +1 -0
- package/config.example.json +17 -1
- package/dist/cli/args.d.ts +13 -2
- package/dist/cli/args.js +95 -24
- package/dist/cli/commands/accounts.js +45 -17
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +9 -2
- package/dist/cli/commands/data/clean.js +32 -9
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/launch/add-root.d.ts +3 -2
- package/dist/cli/commands/launch/add-root.js +11 -10
- package/dist/cli/commands/launch/launch.js +57 -4
- package/dist/cli/commands/launch/prepare.js +3 -1
- package/dist/cli/commands/maintenance/audit.js +1 -1
- package/dist/cli/commands/maintenance/clean.d.ts +1 -1
- package/dist/cli/commands/maintenance/clean.js +17 -3
- package/dist/cli/commands/maintenance/config.d.ts +1 -1
- package/dist/cli/commands/maintenance/config.js +112 -10
- package/dist/cli/commands/maintenance/doctor.d.ts +1 -1
- package/dist/cli/commands/maintenance/doctor.js +2 -4
- package/dist/cli/commands/maintenance/migrate.d.ts +2 -1
- package/dist/cli/commands/maintenance/migrate.js +19 -4
- package/dist/cli/commands/maintenance/sync.js +2 -0
- package/dist/cli/commands/maintenance/update.d.ts +2 -0
- package/dist/cli/commands/maintenance/update.js +27 -4
- package/dist/cli/commands/projects/here.js +12 -7
- package/dist/cli/commands/projects/projects.js +26 -17
- package/dist/cli/commands/registry.js +7 -0
- package/dist/cli/commands/repair/checks/agents.d.ts +2 -0
- package/dist/cli/commands/repair/checks/agents.js +70 -0
- package/dist/cli/commands/repair/checks/common.d.ts +8 -0
- package/dist/cli/commands/repair/checks/common.js +22 -0
- package/dist/cli/commands/repair/checks/data.d.ts +2 -0
- package/dist/cli/commands/repair/checks/data.js +241 -0
- package/dist/cli/commands/repair/checks/index.d.ts +2 -0
- package/dist/cli/commands/repair/checks/index.js +20 -0
- package/dist/cli/commands/repair/checks/install.d.ts +5 -0
- package/dist/cli/commands/repair/checks/install.js +318 -0
- package/dist/cli/commands/repair/checks/machine.d.ts +2 -0
- package/dist/cli/commands/repair/checks/machine.js +232 -0
- package/dist/cli/commands/repair/checks/network.d.ts +6 -0
- package/dist/cli/commands/repair/checks/network.js +60 -0
- package/dist/cli/commands/repair/checks/pc.d.ts +4 -0
- package/dist/cli/commands/repair/checks/pc.js +55 -0
- package/dist/cli/commands/repair/checks/sandbox.d.ts +2 -0
- package/dist/cli/commands/repair/checks/sandbox.js +267 -0
- package/dist/cli/commands/repair/checks/settings.d.ts +2 -0
- package/dist/cli/commands/repair/checks/settings.js +169 -0
- package/dist/cli/commands/repair/checks/vpn.d.ts +2 -0
- package/dist/cli/commands/repair/checks/vpn.js +53 -0
- package/dist/cli/commands/repair/engine.d.ts +32 -0
- package/dist/cli/commands/repair/engine.js +127 -0
- package/dist/cli/commands/repair/env.d.ts +7 -0
- package/dist/cli/commands/repair/env.js +49 -0
- package/dist/cli/commands/repair/index.d.ts +1 -9
- package/dist/cli/commands/repair/index.js +2 -54
- package/dist/cli/commands/repair/log.d.ts +17 -0
- package/dist/cli/commands/repair/log.js +82 -0
- package/dist/cli/commands/repair/printer.d.ts +32 -0
- package/dist/cli/commands/repair/printer.js +132 -0
- package/dist/cli/commands/repair/run.d.ts +33 -0
- package/dist/cli/commands/repair/run.js +133 -0
- package/dist/cli/commands/repair/types.d.ts +97 -9
- package/dist/cli/commands/repair/types.js +11 -7
- package/dist/cli/commands/resume.js +2 -2
- package/dist/cli/commands/sessions/list.js +5 -2
- package/dist/cli/commands/sessions/status.d.ts +2 -0
- package/dist/cli/commands/sessions/status.js +27 -6
- package/dist/cli/commands/setup/index.d.ts +5 -0
- package/dist/cli/commands/setup/index.js +25 -1
- package/dist/cli/commands/storage/index.d.ts +2 -0
- package/dist/cli/commands/storage/index.js +2 -0
- package/dist/cli/commands/storage/offer.d.ts +3 -0
- package/dist/cli/commands/storage/offer.js +85 -0
- package/dist/cli/commands/storage/storage.d.ts +8 -0
- package/dist/cli/commands/storage/storage.js +171 -0
- package/dist/cli/commands/user-config.js +1 -1
- package/dist/cli/commands/vpn.d.ts +4 -0
- package/dist/cli/commands/vpn.js +75 -0
- package/dist/cli/commands/web.d.ts +4 -0
- package/dist/cli/commands/web.js +95 -0
- package/dist/cli/history.js +1 -1
- package/dist/cli/index.js +46 -3
- package/dist/cli/manifest.d.ts +303 -29
- package/dist/cli/manifest.js +173 -43
- package/dist/cli/menu/add-agent.d.ts +47 -0
- package/dist/cli/menu/add-agent.js +168 -0
- package/dist/cli/menu/apps.d.ts +31 -2
- package/dist/cli/menu/apps.js +44 -5
- package/dist/cli/menu/command-fields.d.ts +18 -0
- package/dist/cli/menu/command-fields.js +61 -0
- package/dist/cli/menu/command-form.d.ts +22 -0
- package/dist/cli/menu/command-form.js +169 -46
- package/dist/cli/menu/data-folder.d.ts +18 -0
- package/dist/cli/menu/data-folder.js +99 -0
- package/dist/cli/menu/menu.d.ts +1 -1
- package/dist/cli/menu/menu.js +11 -8
- package/dist/cli/menu/pages/accounts-page.js +55 -24
- package/dist/cli/menu/pages/agents-page.d.ts +66 -0
- package/dist/cli/menu/pages/agents-page.js +338 -0
- package/dist/cli/menu/pages/apps-page.js +1 -2
- package/dist/cli/menu/pages/list-page.d.ts +16 -3
- package/dist/cli/menu/pages/list-page.js +40 -8
- package/dist/cli/menu/pages/maintenance-page.d.ts +9 -0
- package/dist/cli/menu/pages/maintenance-page.js +161 -72
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
- package/dist/cli/menu/pages/sandbox-page.js +132 -43
- package/dist/cli/menu/pages/sessions-page.d.ts +4 -0
- package/dist/cli/menu/pages/sessions-page.js +41 -25
- package/dist/cli/menu/pages/settings-page.d.ts +8 -4
- package/dist/cli/menu/pages/settings-page.js +36 -13
- package/dist/cli/menu/settings/categories.d.ts +2 -0
- package/dist/cli/menu/settings/categories.js +19 -44
- package/dist/cli/menu/settings/collections/collection-editor.d.ts +7 -1
- package/dist/cli/menu/settings/collections/collection-editor.js +12 -8
- package/dist/cli/menu/settings/collections/map-editor.js +5 -1
- package/dist/cli/menu/settings/collections/object-list-editor.js +2 -2
- package/dist/cli/menu/settings/collections/object-map-editor.js +2 -2
- package/dist/cli/menu/settings/collections/string-list-editor.d.ts +11 -1
- package/dist/cli/menu/settings/collections/string-list-editor.js +23 -0
- package/dist/cli/menu/settings/index.d.ts +1 -1
- package/dist/cli/menu/settings/index.js +1 -1
- package/dist/cli/menu/settings/object-editor.d.ts +5 -1
- package/dist/cli/menu/settings/object-editor.js +15 -4
- package/dist/cli/menu/settings/path-kind.d.ts +20 -0
- package/dist/cli/menu/settings/path-kind.js +44 -0
- package/dist/cli/menu/settings/schema-node.d.ts +9 -0
- package/dist/cli/menu/settings/schema-node.js +26 -0
- package/dist/cli/menu/settings/value-editor.d.ts +5 -0
- package/dist/cli/menu/settings/value-editor.js +51 -18
- package/dist/cli/menu/several.js +7 -5
- package/dist/cli/menu/shell.d.ts +37 -10
- package/dist/cli/menu/shell.js +90 -10
- package/dist/cli/menu/vpn-login.d.ts +16 -0
- package/dist/cli/menu/vpn-login.js +66 -0
- package/dist/cli/output/print.d.ts +2 -0
- package/dist/cli/output/print.js +8 -1
- package/dist/cli/services/accounts-service.d.ts +2 -0
- package/dist/cli/services/accounts-service.js +2 -2
- package/dist/cli/services/health-service.d.ts +0 -6
- package/dist/cli/services/health-service.js +5 -42
- package/dist/cli/services/index.d.ts +3 -1
- package/dist/cli/services/index.js +3 -1
- package/dist/cli/services/migrate-service.js +1 -1
- package/dist/cli/services/update-service.d.ts +2 -0
- package/dist/cli/services/update-service.js +13 -6
- package/dist/cli/services/vpn-service.d.ts +28 -0
- package/dist/cli/services/vpn-service.js +46 -0
- package/dist/cli/services/web-service.d.ts +22 -0
- package/dist/cli/services/web-service.js +111 -0
- package/dist/cli/tui/app.d.ts +6 -3
- package/dist/cli/tui/app.js +12 -5
- package/dist/cli/tui/dialogs/path-listing.d.ts +43 -0
- package/dist/cli/tui/dialogs/path-listing.js +127 -0
- package/dist/cli/tui/dialogs/path-picker.d.ts +67 -0
- package/dist/cli/tui/dialogs/path-picker.js +423 -0
- package/dist/cli/tui/dialogs/text-screen.d.ts +6 -0
- package/dist/cli/tui/dialogs/text-screen.js +10 -3
- package/dist/cli/tui/frame.d.ts +7 -10
- package/dist/cli/tui/frame.js +19 -10
- package/dist/cli/tui/index.d.ts +4 -3
- package/dist/cli/tui/index.js +2 -1
- package/dist/cli/tui/list/list-screen.d.ts +14 -2
- package/dist/cli/tui/list/list-screen.js +68 -32
- package/dist/cli/tui/list/list-types.d.ts +8 -4
- package/dist/cli/tui/nav.d.ts +29 -0
- package/dist/cli/tui/nav.js +67 -0
- package/dist/cli/validate.d.ts +1 -1
- package/dist/cli/validate.js +3 -3
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.js +11 -0
- package/dist/core/config/defaults.js +5 -1
- package/dist/core/config/loader.js +12 -2
- package/dist/core/local-config/overrides.js +1 -0
- package/dist/core/prompt.d.ts +19 -0
- package/dist/core/prompt.js +43 -3
- package/dist/core/schema/common.d.ts +6 -0
- package/dist/core/schema/common.js +8 -0
- package/dist/core/schema/config.d.ts +23 -0
- package/dist/core/schema/config.js +10 -1
- package/dist/core/schema/harness.d.ts +1 -0
- package/dist/core/schema/harness.js +8 -2
- package/dist/core/schema/index.d.ts +3 -1
- package/dist/core/schema/index.js +3 -1
- package/dist/core/schema/profile.d.ts +1 -0
- package/dist/core/schema/profile.js +3 -0
- package/dist/core/schema/redact.d.ts +13 -0
- package/dist/core/schema/redact.js +75 -0
- package/dist/core/schema/sandbox.js +1 -0
- package/dist/core/schema/vpn.d.ts +17 -0
- package/dist/core/schema/vpn.js +22 -0
- package/dist/core/schema/web.d.ts +6 -0
- package/dist/core/schema/web.js +14 -0
- package/dist/core/types.d.ts +26 -0
- package/dist/guest/agents/agent-files.d.ts +49 -0
- package/dist/guest/agents/agent-files.js +63 -0
- package/dist/guest/agents/agent-installer.js +2 -9
- package/dist/guest/agents/local-state.d.ts +12 -0
- package/dist/guest/agents/local-state.js +198 -0
- package/dist/guest/agents/sbx-agent.d.ts +2 -0
- package/dist/guest/agents/sbx-agent.js +35 -0
- package/dist/guest/boot/boot.d.ts +4 -0
- package/dist/guest/boot/boot.js +50 -11
- package/dist/guest/daemon/daemon.js +4 -1
- package/dist/guest/daemon/exec.js +2 -1
- package/dist/guest/daemon/idle-shutdown.js +2 -0
- package/dist/guest/daemon/sessions.js +4 -1
- package/dist/guest/entries/agent.d.ts +1 -0
- package/dist/guest/entries/agent.js +10 -0
- package/dist/guest/entries/daemon.js +2 -0
- package/dist/guest/runtime/csc.d.ts +1 -0
- package/dist/guest/runtime/csc.js +1 -0
- package/dist/guest/runtime/secrets.d.ts +12 -0
- package/dist/guest/runtime/secrets.js +63 -0
- package/dist/guest/setup/claude-profile.d.ts +2 -1
- package/dist/guest/setup/claude-profile.js +12 -1
- package/dist/guest/setup/environment.d.ts +6 -0
- package/dist/guest/setup/environment.js +56 -6
- package/dist/guest/setup/path-plan.d.ts +32 -0
- package/dist/guest/setup/path-plan.js +67 -0
- package/dist/guest/setup/shell-path.d.ts +1 -0
- package/dist/guest/setup/shell-path.js +22 -0
- package/dist/guest/toolchains/languages/python.js +1 -1
- package/dist/guest/toolchains/runtimes.js +2 -2
- package/dist/guest/vpn/index.d.ts +1 -0
- package/dist/guest/vpn/index.js +1 -0
- package/dist/guest/vpn/openvpn.d.ts +9 -0
- package/dist/guest/vpn/openvpn.js +47 -0
- package/dist/guest/vpn/tunnel.d.ts +17 -0
- package/dist/guest/vpn/tunnel.js +78 -0
- package/dist/guest/vpn/vpn.d.ts +6 -0
- package/dist/guest/vpn/vpn.js +126 -0
- package/dist/host/channel/attach.d.ts +3 -1
- package/dist/host/channel/attach.js +1 -1
- package/dist/host/channel/channel.d.ts +7 -0
- package/dist/host/channel/channel.js +26 -2
- package/dist/host/health/acl-read.d.ts +17 -0
- package/dist/host/health/acl-read.js +45 -0
- package/dist/host/health/config-backup.d.ts +11 -0
- package/dist/host/health/config-backup.js +73 -0
- package/dist/host/health/index.d.ts +6 -0
- package/dist/host/health/index.js +7 -0
- package/dist/host/health/install-state.d.ts +33 -0
- package/dist/host/health/install-state.js +110 -0
- package/dist/host/health/path-copies.d.ts +32 -0
- package/dist/host/health/path-copies.js +82 -0
- package/dist/host/health/stale.d.ts +45 -0
- package/dist/host/health/stale.js +120 -0
- package/dist/host/health/system.d.ts +37 -0
- package/dist/host/health/system.js +87 -0
- package/dist/host/install/copies.d.ts +13 -0
- package/dist/host/install/copies.js +63 -0
- package/dist/host/install/index.d.ts +1 -0
- package/dist/host/install/index.js +1 -0
- package/dist/host/launch/generate.js +25 -1
- package/dist/host/launch/session-file.js +4 -1
- package/dist/host/launch/stage.js +1 -1
- package/dist/host/migrate/backup.js +3 -0
- package/dist/host/migrate/migrations/config-file.d.ts +2 -0
- package/dist/host/migrate/migrations/config-file.js +7 -0
- package/dist/host/migrate/migrations/follow-machine-storage.js +9 -1
- package/dist/host/migrate/migrations/pin-storage-root.d.ts +4 -0
- package/dist/host/migrate/migrations/pin-storage-root.js +46 -0
- package/dist/host/migrate/migrations/unpin-config-defaults.js +4 -1
- package/dist/host/migrate/registry.js +2 -1
- package/dist/host/profile/accounts.d.ts +25 -1
- package/dist/host/profile/accounts.js +96 -13
- package/dist/host/profile/agent-accounts.d.ts +5 -1
- package/dist/host/profile/agent-accounts.js +35 -5
- package/dist/host/profile/index.d.ts +2 -1
- package/dist/host/profile/index.js +1 -0
- package/dist/host/profile/plugin-share.d.ts +26 -0
- package/dist/host/profile/plugin-share.js +146 -0
- package/dist/host/projects/join.d.ts +7 -1
- package/dist/host/projects/join.js +21 -4
- package/dist/host/projects/registry.d.ts +16 -10
- package/dist/host/projects/registry.js +74 -6
- package/dist/host/sandbox/platform.d.ts +2 -0
- package/dist/host/sandbox/platform.js +21 -2
- package/dist/host/secrets/index.d.ts +2 -0
- package/dist/host/secrets/index.js +2 -0
- package/dist/host/secrets/launch-secrets.d.ts +17 -0
- package/dist/host/secrets/launch-secrets.js +93 -0
- package/dist/host/secrets/secret-store.d.ts +25 -0
- package/dist/host/secrets/secret-store.js +89 -0
- package/dist/host/sharing/acl.d.ts +16 -2
- package/dist/host/sharing/acl.js +35 -10
- package/dist/host/sharing/current.js +11 -2
- package/dist/host/sharing/doctor.d.ts +2 -0
- package/dist/host/sharing/doctor.js +44 -0
- package/dist/host/sharing/group-access.d.ts +6 -0
- package/dist/host/sharing/group-access.js +24 -0
- package/dist/host/sharing/index.d.ts +2 -1
- package/dist/host/sharing/index.js +2 -1
- package/dist/host/sharing/lifecycle.d.ts +8 -2
- package/dist/host/sharing/lifecycle.js +16 -7
- package/dist/host/storage/cleanup.d.ts +2 -0
- package/dist/host/storage/cleanup.js +2 -1
- package/dist/host/storage/history.d.ts +21 -0
- package/dist/host/storage/history.js +49 -0
- package/dist/host/storage/index.d.ts +4 -0
- package/dist/host/storage/index.js +4 -0
- package/dist/host/storage/move.d.ts +95 -0
- package/dist/host/storage/move.js +384 -0
- package/dist/host/storage/offer.d.ts +11 -0
- package/dist/host/storage/offer.js +36 -0
- package/dist/host/storage/root.d.ts +18 -0
- package/dist/host/storage/root.js +77 -0
- package/dist/host/tools/windows-terminal.d.ts +2 -0
- package/dist/host/tools/windows-terminal.js +23 -5
- package/dist/host/update/index.d.ts +1 -1
- package/dist/host/update/index.js +1 -1
- package/dist/host/update/notice.d.ts +2 -0
- package/dist/host/update/notice.js +13 -1
- package/dist/host/update/update-state.d.ts +6 -0
- package/dist/host/update/update-state.js +2 -0
- package/dist/host/update/updater.d.ts +19 -0
- package/dist/host/update/updater.js +88 -12
- package/dist/host/vpn/index.d.ts +4 -0
- package/dist/host/vpn/index.js +4 -0
- package/dist/host/vpn/login.d.ts +24 -0
- package/dist/host/vpn/login.js +79 -0
- package/dist/host/vpn/nordvpn.d.ts +21 -0
- package/dist/host/vpn/nordvpn.js +77 -0
- package/dist/host/vpn/prepare.d.ts +17 -0
- package/dist/host/vpn/prepare.js +77 -0
- package/dist/host/vpn/status.d.ts +12 -0
- package/dist/host/vpn/status.js +53 -0
- package/dist/host/web/auth.d.ts +38 -0
- package/dist/host/web/auth.js +96 -0
- package/dist/host/web/backend.d.ts +14 -0
- package/dist/host/web/backend.js +1 -0
- package/dist/host/web/bridge.d.ts +27 -0
- package/dist/host/web/bridge.js +144 -0
- package/dist/host/web/browser.d.ts +1 -0
- package/dist/host/web/browser.js +10 -0
- package/dist/host/web/control.d.ts +17 -0
- package/dist/host/web/control.js +67 -0
- package/dist/host/web/index.d.ts +9 -0
- package/dist/host/web/index.js +8 -0
- package/dist/host/web/runner.d.ts +14 -0
- package/dist/host/web/runner.js +45 -0
- package/dist/host/web/server.d.ts +40 -0
- package/dist/host/web/server.js +218 -0
- package/dist/host/web/state.d.ts +20 -0
- package/dist/host/web/state.js +41 -0
- package/dist/host/web/static-files.d.ts +13 -0
- package/dist/host/web/static-files.js +57 -0
- package/dist/runtime/agent.js +27614 -0
- package/dist/runtime/daemon.js +1012 -618
- package/dist/runtime/db.js +236 -106
- package/dist/runtime/main.js +1093 -250
- package/dist/runtime/netproxy.js +102 -3
- package/dist/runtime/view.js +102 -3
- package/dist/selftest/accounts.test.js +40 -0
- package/dist/selftest/agent-paths.test.d.ts +2 -0
- package/dist/selftest/agent-paths.test.js +115 -0
- package/dist/selftest/channel.test.js +5 -0
- package/dist/selftest/cli.test.js +34 -4
- package/dist/selftest/credentials.test.d.ts +2 -0
- package/dist/selftest/credentials.test.js +198 -0
- package/dist/selftest/generate.test.js +1 -1
- package/dist/selftest/health.test.d.ts +2 -0
- package/dist/selftest/health.test.js +470 -0
- package/dist/selftest/host-sim.test.js +2 -0
- package/dist/selftest/index.js +19 -0
- package/dist/selftest/layout.test.js +2 -0
- package/dist/selftest/menu-screens.d.ts +7 -0
- package/dist/selftest/menu-screens.js +65 -16
- package/dist/selftest/menu.test.js +574 -21
- package/dist/selftest/migrate.test.js +1 -1
- package/dist/selftest/personalization.test.js +3 -1
- package/dist/selftest/projects.test.js +39 -1
- package/dist/selftest/shared-use.test.js +64 -2
- package/dist/selftest/sharing.test.js +50 -1
- package/dist/selftest/storage.test.d.ts +2 -0
- package/dist/selftest/storage.test.js +363 -0
- package/dist/selftest/support/fixtures.d.ts +14 -0
- package/dist/selftest/support/releases.js +1 -1
- package/dist/selftest/toolchains.test.js +21 -0
- package/dist/selftest/update-service.test.js +87 -3
- package/dist/selftest/vpn.test.d.ts +2 -0
- package/dist/selftest/vpn.test.js +279 -0
- package/dist/selftest/web.test.d.ts +2 -0
- package/dist/selftest/web.test.js +304 -0
- package/dist/shared/apps/catalogue.js +31 -0
- package/dist/shared/channel/index.d.ts +1 -0
- package/dist/shared/channel/index.js +1 -0
- package/dist/shared/channel/run-dir.d.ts +6 -1
- package/dist/shared/channel/run-dir.js +12 -1
- package/dist/shared/channel/schema.d.ts +19 -0
- package/dist/shared/channel/schema.js +11 -0
- package/dist/shared/channel/secrets.d.ts +28 -0
- package/dist/shared/channel/secrets.js +44 -0
- package/dist/shared/vpn/download.d.ts +13 -0
- package/dist/shared/vpn/download.js +14 -0
- package/dist/shared/vpn/kill-switch.d.ts +18 -0
- package/dist/shared/vpn/kill-switch.js +81 -0
- package/dist/shared/vpn/ovpn.d.ts +23 -0
- package/dist/shared/vpn/ovpn.js +244 -0
- package/dist/shared/web/index.d.ts +1 -0
- package/dist/shared/web/index.js +1 -0
- package/dist/shared/web/protocol.d.ts +61 -0
- package/dist/shared/web/protocol.js +35 -0
- package/dist/sync/sync-cycle.js +5 -1
- package/dist/sync/sync-state.d.ts +1 -0
- package/dist/sync/sync-state.js +1 -0
- package/dist/sync/watcher.js +10 -0
- package/dist/web/index.html +13 -0
- package/dist/web/main-CXESL347.js +45 -0
- package/dist/web/main-VLR7DVQ4.css +1 -0
- package/native/csharp/FolderSecurity.cs +29 -0
- package/native/scripts/protect-data.ps1 +29 -0
- package/native/scripts/read-folder-sddl.ps1 +10 -0
- package/native/scripts/set-folder-access.ps1 +49 -7
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +22 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +530 -0
- package/node_modules/ws/lib/receiver.js +743 -0
- package/node_modules/ws/lib/sender.js +607 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +562 -0
- package/node_modules/ws/lib/websocket.js +1421 -0
- package/node_modules/ws/package.json +74 -0
- package/node_modules/ws/wrapper.mjs +21 -0
- package/package.json +15 -5
- package/sandbox/csharp/EnvBroadcast.cs +33 -0
- package/sandbox/csharp/PathSync.cs +90 -0
- package/sandbox/scripts/install-shell-path.ps1 +73 -0
- package/sandbox/scripts/set-machine-environment.ps1 +37 -4
- package/schemas/config.schema.json +79 -4
- package/schemas/sandbox-config.schema.json +14 -1
- package/dist/cli/commands/repair/checks.d.ts +0 -2
- package/dist/cli/commands/repair/checks.js +0 -246
- package/dist/cli/menu/pages/start-page.d.ts +0 -43
- package/dist/cli/menu/pages/start-page.js +0 -192
- package/dist/cli/tui/tab-bar.d.ts +0 -9
- package/dist/cli/tui/tab-bar.js +0 -33
|
@@ -64,7 +64,7 @@ export async function installFirst(t, root, scope = 'user', autoUpdate = false)
|
|
|
64
64
|
/** What a user gets by running the command: the shim, through cmd.exe. */
|
|
65
65
|
export function runShim(layout) {
|
|
66
66
|
const shim = path.join(layout.binDir, 'sandbox.cmd');
|
|
67
|
-
const run = spawnSync(SYSTEM.cmd(), ['/d', '/s', '/c', '""' + shim + '" --version"'], {
|
|
67
|
+
const run = spawnSync(SYSTEM.cmd(), ['/d', '/s', '/c', '""' + shim + '" --version --short"'], {
|
|
68
68
|
encoding: 'utf8',
|
|
69
69
|
windowsVerbatimArguments: true,
|
|
70
70
|
windowsHide: true,
|
|
@@ -207,6 +207,27 @@ export const toolchainsSuite = {
|
|
|
207
207
|
chosen.skipped.length === 1 &&
|
|
208
208
|
appsNeedMesa({ install: ['godot'], custom: {} }) &&
|
|
209
209
|
!appsNeedMesa({ install: ['jq'], custom: {} }), 'apps: 7-Zip first when an installer needs it; an app with its own git is skipped unless git access is "full"; engines bring Mesa');
|
|
210
|
+
// The release files the entries were written against (GitHub, 2026-10): the x64 build matches, the others do not.
|
|
211
|
+
const pick = (id, files) => {
|
|
212
|
+
const source = APPS.find((x) => x.id === id)?.source;
|
|
213
|
+
const re = new RegExp(source && 'github' in source ? source.asset : '$^');
|
|
214
|
+
return files.filter((f) => re.test(f));
|
|
215
|
+
};
|
|
216
|
+
t.equal([
|
|
217
|
+
pick('ast-grep', [
|
|
218
|
+
'app-aarch64-pc-windows-msvc.zip',
|
|
219
|
+
'app-i686-pc-windows-msvc.zip',
|
|
220
|
+
'app-x86_64-pc-windows-msvc.zip',
|
|
221
|
+
]),
|
|
222
|
+
pick('jj', ['jj-v0.46.0-aarch64-pc-windows-msvc.zip', 'jj-v0.46.0-x86_64-pc-windows-msvc.zip']),
|
|
223
|
+
pick('rust-analyzer', ['rust-analyzer-i686-pc-windows-msvc.zip', 'rust-analyzer-x86_64-pc-windows-msvc.zip']),
|
|
224
|
+
], [
|
|
225
|
+
['app-x86_64-pc-windows-msvc.zip'],
|
|
226
|
+
['jj-v0.46.0-x86_64-pc-windows-msvc.zip'],
|
|
227
|
+
['rust-analyzer-x86_64-pc-windows-msvc.zip'],
|
|
228
|
+
], 'apps: ast-grep, Jujutsu and rust-analyzer pick the x64 Windows release file');
|
|
229
|
+
t.check(appsToInstall({ install: ['jj', 'ast-grep', 'rust-analyzer'], custom: {} }, 'readonly').skipped.length === 1 &&
|
|
230
|
+
appsToInstall({ install: ['jj'], custom: {} }, 'full').entries.some((e) => e.id === 'jj'), 'apps: Jujutsu brings its own git, so it needs git access "full"; the other two do not');
|
|
210
231
|
const custom = (app) => ConfigSchema.shape.apps.shape.custom.safeParse({ x: app }).success;
|
|
211
232
|
t.check(custom({ name: 'A', github: 'o/r', asset: 'win.*\\.zip$', exe: 'a.exe' }) &&
|
|
212
233
|
custom({ name: 'B', url: 'https://x.org/b.zip', exe: 'b.exe' }) &&
|
|
@@ -7,11 +7,11 @@ import { spawnSync } from 'node:child_process';
|
|
|
7
7
|
import { parseCli } from '../cli/args.js';
|
|
8
8
|
import { describeResult, runUpdate } from '../cli/commands/maintenance/update.js';
|
|
9
9
|
import { UpdateService, runsAsSystem } from '../cli/services/index.js';
|
|
10
|
-
import { InstallLayout, InstallRecordFile, UpdateTask } from '../host/install/index.js';
|
|
10
|
+
import { InstallLayout, InstallRecordFile, UpdateTask, shimText } from '../host/install/index.js';
|
|
11
11
|
import { nativeScript } from '../host/native.js';
|
|
12
12
|
import { FolderFeed, UpdateLock, updateNotice } from '../host/update/index.js';
|
|
13
13
|
import { SYSTEM } from '../shared/system.js';
|
|
14
|
-
import { installFirst, publish, runShim } from './support/releases.js';
|
|
14
|
+
import { NODE, installFirst, publish, runShim } from './support/releases.js';
|
|
15
15
|
/** A scheduled task that only remembers what it was asked. */
|
|
16
16
|
class FakeTask {
|
|
17
17
|
calls = [];
|
|
@@ -97,6 +97,89 @@ async function service(t) {
|
|
|
97
97
|
t.equal([dueOff, savedOn, dueSoon, due, svc.autoUpdateDue()], [false, true, false, true, false], 'never due while off; the setting is in install.json; not due a moment after a look, due after a day, never while a sandbox runs');
|
|
98
98
|
sandbox = false;
|
|
99
99
|
}
|
|
100
|
+
/** A failed install puts the old version back (pointers, shims), turns automatic updates off and says so once. */
|
|
101
|
+
async function failedInstall(t) {
|
|
102
|
+
t.section('update service: a failed install is undone');
|
|
103
|
+
const root = t.dir('fail', 'install');
|
|
104
|
+
const feed = t.at('fail', 'feed');
|
|
105
|
+
const layout = await installFirst(t, root, 'machine', true);
|
|
106
|
+
const task = new FakeTask();
|
|
107
|
+
const clock = { at: new Date('2026-10-14T12:00:00Z') };
|
|
108
|
+
const svc = new UpdateService(layout, {
|
|
109
|
+
source: new FolderFeed(feed),
|
|
110
|
+
sandboxRunning: () => false,
|
|
111
|
+
task,
|
|
112
|
+
version: '1.0.0',
|
|
113
|
+
userDir: t.dir('fail', 'user'),
|
|
114
|
+
now: () => clock.at,
|
|
115
|
+
});
|
|
116
|
+
const record = new InstallRecordFile(root);
|
|
117
|
+
publish(t, feed, '1.0.1', 'good');
|
|
118
|
+
fs.rmSync(path.join(feed, 'windows-development-sandbox-1.0.1.tgz'));
|
|
119
|
+
const offline = await svc.run();
|
|
120
|
+
t.check(offline?.kind === 'failed' &&
|
|
121
|
+
!offline.blocked &&
|
|
122
|
+
!offline.autoUpdateOff &&
|
|
123
|
+
offline.rolledBack &&
|
|
124
|
+
record.read()?.autoUpdate === true &&
|
|
125
|
+
task.calls.length === 0 &&
|
|
126
|
+
svc.status().autoOffText === null &&
|
|
127
|
+
svc.notice() === null, 'a download that never arrived (the network) changes nothing and leaves automatic updates on');
|
|
128
|
+
const goodShim = fs.readFileSync(path.join(layout.binDir, 'sandbox.cmd'), 'utf8');
|
|
129
|
+
t.check(goodShim === shimText('sandbox', 'cmd'), 'the shim text is the standard one before the failed update');
|
|
130
|
+
fs.writeFileSync(path.join(layout.binDir, 'sandbox.cmd'), '@echo off\r\nrem marker\r\n');
|
|
131
|
+
publish(t, feed, '1.0.2', 'broken');
|
|
132
|
+
clock.at = new Date(clock.at.getTime() + 3600_000);
|
|
133
|
+
const bad = await svc.run();
|
|
134
|
+
t.check(bad?.kind === 'failed' &&
|
|
135
|
+
bad.blocked &&
|
|
136
|
+
bad.rolledBack &&
|
|
137
|
+
bad.autoUpdateOff &&
|
|
138
|
+
layout.activeVersion() === '1.0.0' &&
|
|
139
|
+
layout.activeNode() === NODE &&
|
|
140
|
+
fs.readFileSync(path.join(layout.binDir, 'sandbox.cmd'), 'utf8') === goodShim &&
|
|
141
|
+
runShim(layout) === '1.0.0' &&
|
|
142
|
+
!layout.installedVersions().includes('1.0.2'), 'a release that does not start is undone: pointers, shims and the version folder are as before');
|
|
143
|
+
t.check(record.read()?.autoUpdate === false &&
|
|
144
|
+
task.calls.join() === 'remove' &&
|
|
145
|
+
svc.status().autoUpdate === false &&
|
|
146
|
+
svc.status().state.autoOff?.version === '1.0.2', 'automatic updates are off in install.json, the scheduled task is removed, the reason is recorded');
|
|
147
|
+
const told = svc.notice();
|
|
148
|
+
if (told)
|
|
149
|
+
svc.markNoticeSeen(told);
|
|
150
|
+
t.check(told?.text ===
|
|
151
|
+
'The update to 1.0.2 failed and was undone (you are on 1.0.0). Automatic updates are off; turn them back on in Maintenance > Updates or with sandbox update --auto on.' &&
|
|
152
|
+
svc.notice() === null &&
|
|
153
|
+
svc.status().autoOffText === told.text, 'the user is told once, in plain words; the status carries the same sentence for the menu', told?.text);
|
|
154
|
+
svc.setAuto(true);
|
|
155
|
+
t.check(svc.status().autoOffText === null && record.read()?.autoUpdate === true, 'turning updates back on clears the remark');
|
|
156
|
+
// The same failure when the task cannot be removed (no admin): not an error; install.json is what the task reads.
|
|
157
|
+
const root2 = t.dir('fail2', 'install');
|
|
158
|
+
const feed2 = t.at('fail2', 'feed');
|
|
159
|
+
const layout2 = await installFirst(t, root2, 'machine', true);
|
|
160
|
+
const task2 = new FakeTask();
|
|
161
|
+
task2.fail = true;
|
|
162
|
+
publish(t, feed2, '1.0.1', 'broken');
|
|
163
|
+
const svc2 = new UpdateService(layout2, {
|
|
164
|
+
source: new FolderFeed(feed2),
|
|
165
|
+
sandboxRunning: () => false,
|
|
166
|
+
task: task2,
|
|
167
|
+
version: '1.0.0',
|
|
168
|
+
});
|
|
169
|
+
const bad2 = await svc2.run();
|
|
170
|
+
t.check(bad2?.kind === 'failed' &&
|
|
171
|
+
bad2.autoUpdateOff &&
|
|
172
|
+
new InstallRecordFile(root2).read()?.autoUpdate === false &&
|
|
173
|
+
(await runUpdate(svc2, { check: false, rollback: false, unattended: true }, () => undefined)) === 0 &&
|
|
174
|
+
layout2.activeVersion() === '1.0.0', 'a task that cannot be removed does not stop the rollback; an unattended run with updates off does nothing');
|
|
175
|
+
publish(t, feed2, '1.0.2', 'good');
|
|
176
|
+
const ok2 = await svc2.run();
|
|
177
|
+
fs.writeFileSync(path.join(layout2.binDir, 'sandbox.cmd'), '@echo off\r\nrem marker\r\n');
|
|
178
|
+
const back = ok2?.kind === 'installed' ? svc2.rollback() : null;
|
|
179
|
+
t.check(back === '1.0.0' &&
|
|
180
|
+
fs.readFileSync(path.join(layout2.binDir, 'sandbox.cmd'), 'utf8') === goodShim &&
|
|
181
|
+
runShim(layout2) === '1.0.0', 'sandbox update --rollback writes the shims of the version it returns to');
|
|
182
|
+
}
|
|
100
183
|
function pure(t) {
|
|
101
184
|
t.section('update notice and options');
|
|
102
185
|
const state = { lastResult: { version: '1.5.0', ok: true, at: '2026-10-14T03:00:00Z' } };
|
|
@@ -154,7 +237,7 @@ async function command(t) {
|
|
|
154
237
|
describeResult(waiting, false).code === 1 &&
|
|
155
238
|
describeResult(waiting, true).code === 0 &&
|
|
156
239
|
describeResult({ kind: 'installed', version: '1.0.1', previous: '1.0.0' }, false).line.includes('from 1.0.0 to 1.0.1') &&
|
|
157
|
-
describeResult({ kind: 'failed', version: '1.0.1', reason: 'x', blocked: true }, false).code === 1, 'exit codes: a running sandbox fails by hand but just waits unattended; a failed update exits 1');
|
|
240
|
+
describeResult({ kind: 'failed', version: '1.0.1', reason: 'x', blocked: true, rolledBack: true, autoUpdateOff: true }, false).code === 1, 'exit codes: a running sandbox fails by hand but just waits unattended; a failed update exits 1');
|
|
158
241
|
}
|
|
159
242
|
function script(t) {
|
|
160
243
|
t.section('set-update-task.ps1');
|
|
@@ -181,6 +264,7 @@ export const updateServiceSuite = {
|
|
|
181
264
|
title: 'sandbox update, automatic updates, the update notice',
|
|
182
265
|
run: async (t) => {
|
|
183
266
|
await service(t);
|
|
267
|
+
await failedInstall(t);
|
|
184
268
|
pure(t);
|
|
185
269
|
await command(t);
|
|
186
270
|
script(t);
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// The VPN: profiles are untrusted (programs refused), the kill switch rules, NordVPN server choice, the saved login,
|
|
2
|
+
// the settings rules for projects, and that the login never lands in session.json or the launch folder.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { DEFAULTS } from '../core/config/index.js';
|
|
6
|
+
import { applyOverrides, loosenedRequests, NEVER_LOOSER, normalizeOverrides } from '../core/local-config/index.js';
|
|
7
|
+
import { ConfigSchema, LocalOverridesSchema } from '../core/schema/index.js';
|
|
8
|
+
import { SecretStore } from '../host/secrets/index.js';
|
|
9
|
+
import { clearSecrets } from '../host/secrets/index.js';
|
|
10
|
+
import { generateSandbox } from '../host/launch/index.js';
|
|
11
|
+
import { findCountry, nordProfile, prepareVpn, recommendedServer, VpnLogin, vpnLine, vpnNotes, } from '../host/vpn/index.js';
|
|
12
|
+
import { RunDir } from '../shared/channel/index.js';
|
|
13
|
+
import { OPENVPN_MSI } from '../shared/vpn/download.js';
|
|
14
|
+
import { cleanServers, killSwitchCommands, removeKillSwitchCommands, tunnelAddressFromLog, tunnelCommands, } from '../shared/vpn/kill-switch.js';
|
|
15
|
+
import { assertSafeOvpn, inspectOvpn, PROGRAM_DIRECTIVES } from '../shared/vpn/ovpn.js';
|
|
16
|
+
const PROFILE = [
|
|
17
|
+
'client',
|
|
18
|
+
'dev tun',
|
|
19
|
+
'proto udp',
|
|
20
|
+
'remote 185.1.2.3 1194',
|
|
21
|
+
'remote-random',
|
|
22
|
+
'resolv-retry infinite',
|
|
23
|
+
'nobind',
|
|
24
|
+
'tun-mtu 1500',
|
|
25
|
+
'tun-mtu-extra 32',
|
|
26
|
+
'mssfix 1450',
|
|
27
|
+
'persist-key',
|
|
28
|
+
'persist-tun',
|
|
29
|
+
'ping 15',
|
|
30
|
+
'ping-restart 0',
|
|
31
|
+
'ping-timer-rem',
|
|
32
|
+
'reneg-sec 0',
|
|
33
|
+
'comp-lzo no',
|
|
34
|
+
'remote-cert-tls server',
|
|
35
|
+
'auth-user-pass',
|
|
36
|
+
'verb 3',
|
|
37
|
+
'pull',
|
|
38
|
+
'fast-io',
|
|
39
|
+
'cipher AES-256-CBC',
|
|
40
|
+
'auth SHA512',
|
|
41
|
+
'<ca>',
|
|
42
|
+
'-----BEGIN CERTIFICATE-----',
|
|
43
|
+
'MIIDxx',
|
|
44
|
+
'-----END CERTIFICATE-----',
|
|
45
|
+
'</ca>',
|
|
46
|
+
'key-direction 1',
|
|
47
|
+
'<tls-auth>',
|
|
48
|
+
'-----BEGIN OpenVPN Static key V1-----',
|
|
49
|
+
'abcd',
|
|
50
|
+
'-----END OpenVPN Static key V1-----',
|
|
51
|
+
'</tls-auth>',
|
|
52
|
+
'',
|
|
53
|
+
].join('\r\n');
|
|
54
|
+
const PASSWORD = 'vpn-pass-planted-91c2d7';
|
|
55
|
+
const USERNAME = 'vpn-user-planted-5e8a40';
|
|
56
|
+
export const vpnSuite = {
|
|
57
|
+
name: 'vpn',
|
|
58
|
+
title: 'VPN: profile checks, kill switch rules, NordVPN, login storage, settings rules',
|
|
59
|
+
run: async (t, f) => {
|
|
60
|
+
t.section('a profile is untrusted data');
|
|
61
|
+
const good = inspectOvpn(PROFILE);
|
|
62
|
+
t.check(good.ok && good.remotes.length === 1 && good.remotes[0].host === '185.1.2.3' && good.remotes[0].proto === 'udp', 'a NordVPN-style profile is accepted and says where it connects (and with which protocol)', JSON.stringify(good));
|
|
63
|
+
const byName = inspectOvpn('remote vpn.example.com 443\r\nproto tcp-client\r\nclient');
|
|
64
|
+
t.check(byName.ok && byName.remotes[0].proto === 'tcp' && byName.remotes[0].host === 'vpn.example.com', 'the proto line counts even after the remote line, and a host name is allowed');
|
|
65
|
+
for (const name of ['script-security', 'up', 'down', 'plugin', 'route-up', 'ipchange', 'tls-verify']) {
|
|
66
|
+
const bad = inspectOvpn(PROFILE + name + ' 2 C:\\evil.cmd\r\n');
|
|
67
|
+
t.check(!bad.ok && bad.problems.some((p) => p.includes(name + ' runs a program')), name + ' is refused as something that runs a program', JSON.stringify(bad));
|
|
68
|
+
}
|
|
69
|
+
t.check(PROGRAM_DIRECTIVES.every((name) => !inspectOvpn(PROFILE + name + ' x\r\n').ok), 'every listed program-running directive is refused');
|
|
70
|
+
const odd = [
|
|
71
|
+
['--up x', 'a leading -- does not hide it'],
|
|
72
|
+
['UP x', 'upper case does not hide it'],
|
|
73
|
+
['\tscript-security 2', 'leading white space does not hide it'],
|
|
74
|
+
['"up" x', 'quotes around the name do not hide it'],
|
|
75
|
+
['made-up-directive 1', 'an unknown directive is refused, not passed on'],
|
|
76
|
+
['ca /etc/ca.crt', 'a certificate file on this machine is refused'],
|
|
77
|
+
['auth-user-pass C:\\secret.txt', 'a login file named in the profile is refused'],
|
|
78
|
+
['setenv FOO bar', 'setenv for anything but CLIENT_CERT is refused'],
|
|
79
|
+
['<connection>', 'a <connection> section (it could hide more) is refused'],
|
|
80
|
+
];
|
|
81
|
+
for (const [line, why] of odd)
|
|
82
|
+
t.check(!inspectOvpn(PROFILE + line + '\r\n').ok, why, line);
|
|
83
|
+
t.check(!inspectOvpn(PROFILE.replace('</ca>', '')).ok &&
|
|
84
|
+
!inspectOvpn('client\r\n').ok &&
|
|
85
|
+
!inspectOvpn('x'.repeat(300 * 1024)).ok &&
|
|
86
|
+
!inspectOvpn(PROFILE.replace('<tls-auth>', '<tls-auth>\r\nup evil\r\n<ca>')).ok, 'an unclosed section, no remote, an oversized file and a section inside a section are refused');
|
|
87
|
+
t.check(inspectOvpn(PROFILE.replace('</ca>', '</ca>\r\n# up /bin/evil\r\n; plugin evil.dll\r\n')).ok, 'comments are comments (a commented-out up line is not a directive)');
|
|
88
|
+
await t.throws(() => assertSafeOvpn(PROFILE + 'script-security 2\r\nup evil.cmd\r\n', 'uk1.nordvpn.com'), 'uk1.nordvpn.com was refused', 'a refused profile names its source and the reason');
|
|
89
|
+
t.section('the kill switch as netsh commands');
|
|
90
|
+
const servers = [
|
|
91
|
+
{ ip: '185.1.2.3', port: 1194, proto: 'udp' },
|
|
92
|
+
{ ip: '185.1.2.3', port: 1194, proto: 'udp' },
|
|
93
|
+
{ ip: 'not-an-ip', port: 1194, proto: 'udp' },
|
|
94
|
+
{ ip: '185.9.9.9', port: 443, proto: 'tcp' },
|
|
95
|
+
];
|
|
96
|
+
t.equal(cleanServers(servers).map((s) => s.ip + ':' + s.port), ['185.1.2.3:1194', '185.9.9.9:443'], 'servers are valid and each once');
|
|
97
|
+
const phase1 = killSwitchCommands(servers, 'C:\\AgentLaunch\\ptyhost.exe').map((c) => c.join(' '));
|
|
98
|
+
const policyAt = phase1.findIndex((c) => c.includes('firewallpolicy blockinbound,blockoutbound'));
|
|
99
|
+
const firstAllow = phase1.findIndex((c) => c.includes('action=allow'));
|
|
100
|
+
t.check(policyAt > 0 && policyAt < firstAllow, 'outbound is blocked by default before any allow rule is added', phase1.join('\n'));
|
|
101
|
+
t.check(phase1.some((c) => c.includes('name=AgentSandbox-VPN-Loopback') && c.includes('remoteip=127.0.0.0/8')) &&
|
|
102
|
+
phase1.some((c) => c.includes('name=AgentSandbox-VPN-Server-1') &&
|
|
103
|
+
c.includes('protocol=udp') &&
|
|
104
|
+
c.includes('remoteip=185.1.2.3') &&
|
|
105
|
+
c.includes('remoteport=1194')) &&
|
|
106
|
+
phase1.some((c) => c.includes('name=AgentSandbox-VPN-Server-2') && c.includes('protocol=tcp') && c.includes('remoteport=443')) &&
|
|
107
|
+
phase1.some((c) => c.includes('name=AgentSandbox-VPN-Host') &&
|
|
108
|
+
c.includes('remoteip=LocalSubnet') &&
|
|
109
|
+
c.includes('program=C:\\AgentLaunch\\ptyhost.exe')) &&
|
|
110
|
+
phase1.some((c) => c.includes('name=AgentSandbox-VPN-DHCP')), 'it allows loopback, each VPN server endpoint, the attach channel on the Hyper-V network and DHCP');
|
|
111
|
+
t.check(!phase1.some((c) => c.includes('localip=') || (c.includes('action=allow') && c.includes('remoteip=any'))), 'nothing else is allowed before the tunnel exists (no blanket rule)');
|
|
112
|
+
t.check(killSwitchCommands([], null).every((c) => !c.join(' ').includes('Server-1') || c[2] === 'delete') &&
|
|
113
|
+
!killSwitchCommands([], null).some((c) => c.join(' ').includes('name=AgentSandbox-VPN-Host') && c[2] === 'add'), 'with no servers and no session host only the block and the loopback rules remain (the failure case: offline)');
|
|
114
|
+
t.equal(tunnelCommands('10.8.0.6')
|
|
115
|
+
.map((c) => c.join(' '))
|
|
116
|
+
.filter((c) => c.includes(' add ')), [
|
|
117
|
+
'advfirewall firewall add rule name=AgentSandbox-VPN-Tunnel dir=out action=allow enable=yes profile=any protocol=any localip=10.8.0.6',
|
|
118
|
+
], 'once the tunnel is up, traffic from its own address (the tunnel) is allowed');
|
|
119
|
+
await t.throws(() => tunnelCommands('10.8.0.6; calc'), 'Not an IPv4', 'a tunnel address that is not an address makes no rule');
|
|
120
|
+
t.check(removeKillSwitchCommands().some((c) => c.join(' ').includes('blockinbound,allowoutbound')) &&
|
|
121
|
+
removeKillSwitchCommands().some((c) => c.join(' ').includes('name=AgentSandbox-VPN-Tunnel')), 'taking it away restores the default and deletes the rules');
|
|
122
|
+
t.equal([
|
|
123
|
+
tunnelAddressFromLog('... net_addr_v4_add: 10.100.0.2/16 dev Wintun ...'),
|
|
124
|
+
tunnelAddressFromLog('ifconfig 10.8.0.6 255.255.255.0'),
|
|
125
|
+
tunnelAddressFromLog('nothing'),
|
|
126
|
+
], ['10.100.0.2', '10.8.0.6', null], "the tunnel's address is read from OpenVPN's log");
|
|
127
|
+
t.check(OPENVPN_MSI.x64.sha256.length === 64 &&
|
|
128
|
+
OPENVPN_MSI.arm64.sha256.length === 64 &&
|
|
129
|
+
OPENVPN_MSI.base.startsWith('https://'), 'the OpenVPN download has a checksum for each architecture (downloads.requireChecksum passes)');
|
|
130
|
+
t.section('NordVPN: the server for the country and protocol');
|
|
131
|
+
const asked = [];
|
|
132
|
+
const fake = (url) => {
|
|
133
|
+
asked.push(url);
|
|
134
|
+
if (url.endsWith('/servers/countries'))
|
|
135
|
+
return Promise.resolve(JSON.stringify([
|
|
136
|
+
{ id: 227, name: 'United Kingdom', code: 'GB' },
|
|
137
|
+
{ id: 81, name: 'Germany', code: 'DE' },
|
|
138
|
+
]));
|
|
139
|
+
if (url.includes('/servers/recommendations'))
|
|
140
|
+
return Promise.resolve(JSON.stringify([{ hostname: 'uk2201.nordvpn.com', station: '185.1.2.3' }]));
|
|
141
|
+
if (url.endsWith('.ovpn'))
|
|
142
|
+
return Promise.resolve(PROFILE);
|
|
143
|
+
return Promise.reject(new Error('unexpected ' + url));
|
|
144
|
+
};
|
|
145
|
+
t.check(findCountry([{ id: 227, name: 'United Kingdom', code: 'GB' }], 'uk') === 227 &&
|
|
146
|
+
findCountry([{ id: 81, name: 'Germany', code: 'DE' }], 'germany') === 81, 'uk and Germany are found by code, alias or name');
|
|
147
|
+
await t.throws(() => findCountry([{ id: 81, name: 'Germany', code: 'DE' }], 'atlantis'), 'no country', 'an unknown country says so');
|
|
148
|
+
const server = await recommendedServer('udp', 'uk', fake);
|
|
149
|
+
t.check(server.hostname === 'uk2201.nordvpn.com' &&
|
|
150
|
+
asked.some((u) => u.includes('filters[country_id]=227') && u.includes('openvpn_udp')), 'the recommendation asks for the country and the protocol', asked.join('\n'));
|
|
151
|
+
t.check((await nordProfile(server, 'udp', fake)) === PROFILE &&
|
|
152
|
+
asked.some((u) => u === 'https://downloads.nordcdn.com/configs/files/ovpn_udp/servers/uk2201.nordvpn.com.udp.ovpn'), "the server's own profile is fetched from NordVPN's download host");
|
|
153
|
+
await t.throws(() => recommendedServer('udp', '', () => Promise.resolve(JSON.stringify([{ hostname: 'evil.example.com; calc', station: '1.2.3.4' }]))), 'did not recommend', 'an answer that is not a NordVPN host name is refused (it becomes part of a URL and a file name)');
|
|
154
|
+
t.section('the saved login');
|
|
155
|
+
const storage = t.dir('vpn-storage');
|
|
156
|
+
const login = new VpnLogin(storage, SecretStore.memory());
|
|
157
|
+
t.check(login.state('nordvpn') === 'none', 'nothing is saved at first');
|
|
158
|
+
await t.throws(() => login.save('nordvpn', { username: 'a\nb', password: 'x' }), 'line breaks', 'a login with a line break is refused (it goes on two lines of a file)');
|
|
159
|
+
login.save('nordvpn', { username: USERNAME, password: PASSWORD });
|
|
160
|
+
const file = path.join(storage, 'vpn', 'nordvpn.json');
|
|
161
|
+
t.check(login.state('nordvpn') === 'saved' &&
|
|
162
|
+
!fs.readFileSync(file, 'utf8').includes(USERNAME) &&
|
|
163
|
+
!fs.readFileSync(file, 'utf8').includes(PASSWORD), 'it is saved sealed: neither value is in the file');
|
|
164
|
+
t.equal(login.read('nordvpn'), { username: USERNAME, password: PASSWORD }, 'and it opens for the launch');
|
|
165
|
+
fs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace(/"sealed": "[^"]*"/, '"sealed": "dpapi1:AAAA"'));
|
|
166
|
+
t.check(login.state('nordvpn') === 'unreadable', 'a login this Windows user cannot open is reported as unreadable');
|
|
167
|
+
login.remove('nordvpn');
|
|
168
|
+
t.check(login.state('nordvpn') === 'none', 'logout deletes it');
|
|
169
|
+
t.section('settings: what a project may and may not do');
|
|
170
|
+
t.check(DEFAULTS.vpn.enabled === false && DEFAULTS.vpn.provider === 'nordvpn' && DEFAULTS.vpn.killSwitch === true, 'the VPN is off by default, NordVPN, with the kill switch on');
|
|
171
|
+
t.check(ConfigSchema.safeParse(DEFAULTS).success, 'the defaults are valid');
|
|
172
|
+
t.check(!ConfigSchema.safeParse({ ...DEFAULTS, vpn: { ...DEFAULTS.vpn, country: 'uk; calc' } }).success &&
|
|
173
|
+
!ConfigSchema.safeParse({ ...DEFAULTS, vpn: { ...DEFAULTS.vpn, protocol: 'icmp' } }).success &&
|
|
174
|
+
!ConfigSchema.safeParse({ ...DEFAULTS, vpn: { ...DEFAULTS.vpn, username: 'x' } }).success, 'a country with odd characters, an unknown protocol and a login setting are all invalid (no login setting exists)');
|
|
175
|
+
for (const bad of [
|
|
176
|
+
{ vpn: { enabled: true } },
|
|
177
|
+
{ vpn: { provider: 'openvpn-file' } },
|
|
178
|
+
{ vpn: { configFile: 'C:\\evil.ovpn' } },
|
|
179
|
+
{ vpn: { country: 'uk' } },
|
|
180
|
+
{ vpn: { username: 'x', password: 'y' } },
|
|
181
|
+
{ vpn: { credentials: 'x' } },
|
|
182
|
+
{ vpn: { servers: ['1.2.3.4'] } },
|
|
183
|
+
])
|
|
184
|
+
t.check(!LocalOverridesSchema.safeParse(bad).success, "a project's .sandbox-config.json cannot set " + Object.keys(bad.vpn).join(', '));
|
|
185
|
+
t.check(LocalOverridesSchema.safeParse({ vpn: { killSwitch: true } }).success, 'it can ask for the kill switch');
|
|
186
|
+
t.check(NEVER_LOOSER.includes('vpn.killSwitch'), 'the kill switch is a protection a project may only tighten');
|
|
187
|
+
const mine = structuredClone(DEFAULTS);
|
|
188
|
+
mine.vpn.enabled = true;
|
|
189
|
+
const loosen = normalizeOverrides({ vpn: { killSwitch: false } }, f.workA, 'x');
|
|
190
|
+
t.check(applyOverrides(mine, loosen).vpn.killSwitch === true &&
|
|
191
|
+
loosenedRequests(mine, loosen).some((r) => r.key === 'vpn.killSwitch'), 'a project cannot turn the kill switch off (it is flagged and ignored)');
|
|
192
|
+
const off = structuredClone(mine);
|
|
193
|
+
off.vpn.killSwitch = false;
|
|
194
|
+
t.check(applyOverrides(off, normalizeOverrides({ vpn: { killSwitch: true } }, f.workA, 'x')).vpn.killSwitch === true, 'but it can turn it on');
|
|
195
|
+
t.section('the login never lands in session.json, the launch folder or --json');
|
|
196
|
+
const config = f.makeConfig();
|
|
197
|
+
config.vpn = { ...config.vpn, enabled: true, country: 'uk' };
|
|
198
|
+
const store = new VpnLogin(config.storage.root, SecretStore.memory());
|
|
199
|
+
store.save('nordvpn', { username: USERNAME, password: PASSWORD });
|
|
200
|
+
const run = await f.run({}, config);
|
|
201
|
+
const prepared = await prepareVpn({
|
|
202
|
+
config: run.config,
|
|
203
|
+
storageRoot: config.storage.root,
|
|
204
|
+
login: store,
|
|
205
|
+
fetchText: fake,
|
|
206
|
+
});
|
|
207
|
+
t.check(prepared !== null &&
|
|
208
|
+
prepared.session.server === 'uk2201.nordvpn.com' &&
|
|
209
|
+
prepared.credentials.password === PASSWORD, 'a launch picks the server, checks its profile and holds the login in memory');
|
|
210
|
+
await t.throws(() => prepareVpn({
|
|
211
|
+
config: run.config,
|
|
212
|
+
storageRoot: config.storage.root,
|
|
213
|
+
login: store,
|
|
214
|
+
fetchText: fake,
|
|
215
|
+
sandboxRunning: true,
|
|
216
|
+
}), 'already', 'the login is not handed over while a sandbox is running (one that is not being started now)');
|
|
217
|
+
await t.throws(() => prepareVpn({
|
|
218
|
+
config: run.config,
|
|
219
|
+
storageRoot: config.storage.root,
|
|
220
|
+
login: store,
|
|
221
|
+
fetchText: (url) => (url.endsWith('.ovpn') ? Promise.resolve(PROFILE + 'up evil.cmd\r\n') : fake(url)),
|
|
222
|
+
}), 'refused', 'a profile that would run a program stops the launch before anything is written');
|
|
223
|
+
await t.throws(() => prepareVpn({
|
|
224
|
+
config: run.config,
|
|
225
|
+
storageRoot: config.storage.root,
|
|
226
|
+
login: new VpnLogin(t.dir('vpn-empty'), SecretStore.memory()),
|
|
227
|
+
fetchText: fake,
|
|
228
|
+
}), 'sandbox vpn login', 'no saved login says how to save one');
|
|
229
|
+
clearSecrets(config.storage.root, run.dataPath);
|
|
230
|
+
const withVpn = generateSandbox({
|
|
231
|
+
packageRoot: t.packageRoot,
|
|
232
|
+
run,
|
|
233
|
+
harnessArgs: [],
|
|
234
|
+
probe: f.probe,
|
|
235
|
+
pcLanguages: ['python', 'rust', 'cpp', 'dotnet'],
|
|
236
|
+
...(prepared ? { vpn: prepared } : {}),
|
|
237
|
+
});
|
|
238
|
+
const session = fs.readFileSync(withVpn.sessionPath, 'utf8');
|
|
239
|
+
const everything = [];
|
|
240
|
+
const walk = (d) => {
|
|
241
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
242
|
+
const p = path.join(d, e.name);
|
|
243
|
+
if (e.isDirectory())
|
|
244
|
+
walk(p);
|
|
245
|
+
else
|
|
246
|
+
everything.push(fs.readFileSync(p, 'latin1'));
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
walk(withVpn.launchPath);
|
|
250
|
+
t.check(!session.includes(PASSWORD) &&
|
|
251
|
+
!session.includes(USERNAME) &&
|
|
252
|
+
!everything.some((text) => text.includes(PASSWORD) || text.includes(USERNAME)), 'neither the service user name nor the password is in session.json or any file of the launch folder');
|
|
253
|
+
const vpnSession = f.readJson(withVpn.sessionPath).vpn;
|
|
254
|
+
t.check(vpnSession.server === 'uk2201.nordvpn.com' &&
|
|
255
|
+
vpnSession.killSwitch === true &&
|
|
256
|
+
fs.readFileSync(path.join(withVpn.launchPath, 'vpn', 'server.ovpn'), 'utf8') === PROFILE, 'session.json says which server and whether the kill switch is on; the checked profile is in the launch folder');
|
|
257
|
+
const handed = f.readJson(new RunDir(run.dataPath).secretFile(withVpn.launchId));
|
|
258
|
+
t.equal(handed.vpn, { username: USERNAME, password: PASSWORD }, 'the login is only in the one-shot file for the sandbox');
|
|
259
|
+
clearSecrets(config.storage.root, run.dataPath);
|
|
260
|
+
t.check(vpnNotes(config.vpn).some((n) => n.includes('can read the VPN login') && n.includes('turn the tunnel off')) &&
|
|
261
|
+
vpnNotes(config.vpn).some((n) => n.includes('kill switch on')) &&
|
|
262
|
+
vpnNotes({ ...config.vpn, killSwitch: false }).some((n) => n.includes('kill switch OFF')) &&
|
|
263
|
+
vpnNotes({ ...config.vpn, enabled: false }).length === 0, 'the launch warns that the agent can read the login and turn the tunnel off, and says what the kill switch does');
|
|
264
|
+
t.section('what the host says about the tunnel');
|
|
265
|
+
const status = (state, extra = {}) => vpnLine({ state, message: 'm', killSwitch: true, updatedAt: 'now', ...extra }, '203.0.113.9');
|
|
266
|
+
t.check(status('up', { publicIp: '185.1.2.3', server: 'uk2201.nordvpn.com' }).level === 'ok' &&
|
|
267
|
+
status('up', { publicIp: '203.0.113.9' }).level === 'bad' &&
|
|
268
|
+
status('failed', { message: 'login refused' }).text.includes('NO internet') === false &&
|
|
269
|
+
status('failed', { message: 'login refused' }).text.includes('no internet') &&
|
|
270
|
+
vpnLine({ state: 'failed', message: 'x', killSwitch: false, updatedAt: 'now' }).text.includes('normal connection') &&
|
|
271
|
+
status('starting').level === 'warn', "up with another address is fine, up with this PC's own address is flagged, a failure says what the kill switch did");
|
|
272
|
+
t.check(!vpnLine({
|
|
273
|
+
state: 'failed',
|
|
274
|
+
message: 'bad \u001b]52;c;ZXZpbA==\u0007 text',
|
|
275
|
+
killSwitch: true,
|
|
276
|
+
updatedAt: 'n',
|
|
277
|
+
}).text.includes('\u001b'), 'text the sandbox wrote loses its escape sequences before it is printed');
|
|
278
|
+
},
|
|
279
|
+
};
|