windows-development-sandbox 1.16.0 → 1.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +92 -92
  2. package/config.example.json +6 -0
  3. package/dist/app/features/access/access.run.js +1 -1
  4. package/dist/bin/agent-sandbox.js +2 -2
  5. package/dist/bin/{chunk-7EXDUNE7.js → chunk-QLGGAG3D.js} +1 -1
  6. package/dist/bin/{chunk-HIC4CUMY.js → chunk-REE3EMHD.js} +73 -16
  7. package/dist/bin/{chunk-PXTLKIV4.js → chunk-XYUVIVJT.js} +1 -1
  8. package/dist/bin/claude-host.js +2 -2
  9. package/dist/bin/claude-sbx.js +2 -2
  10. package/dist/bin/codex-sbx.js +2 -2
  11. package/dist/bin/gemini-sbx.js +2 -2
  12. package/dist/bin/sandbox.js +2 -2
  13. package/dist/bin/sbx-hide.js +2 -2
  14. package/dist/bin/sbx-show.js +2 -2
  15. package/dist/cli/commands/web.js +1 -1
  16. package/dist/core/config/defaults.js +1 -0
  17. package/dist/core/detect/toolchains.d.ts +1 -0
  18. package/dist/core/detect/toolchains.js +7 -0
  19. package/dist/core/schema/config.d.ts +21 -0
  20. package/dist/core/schema/sandbox.js +1 -1
  21. package/dist/core/schema/toolchains.d.ts +6 -0
  22. package/dist/core/schema/toolchains.js +10 -0
  23. package/dist/guest/agents/agent-files.d.ts +6 -0
  24. package/dist/guest/agents/agent-files.js +14 -0
  25. package/dist/guest/agents/local-state.d.ts +2 -0
  26. package/dist/guest/agents/local-state.js +13 -1
  27. package/dist/guest/boot/boot.js +4 -1
  28. package/dist/guest/setup/browser-gpu.d.ts +1 -0
  29. package/dist/guest/setup/browser-gpu.js +18 -0
  30. package/dist/guest/toolchains/native/rust-extras.js +3 -1
  31. package/dist/guest/toolchains/native/unreal.d.ts +9 -0
  32. package/dist/guest/toolchains/native/unreal.js +198 -0
  33. package/dist/guest/toolchains/native/vcruntime.d.ts +3 -0
  34. package/dist/guest/toolchains/native/vcruntime.js +2 -2
  35. package/dist/guest/toolchains/plan.js +10 -2
  36. package/dist/host/engines/hcs/live-folders.js +1 -1
  37. package/dist/host/github/token.js +1 -1
  38. package/dist/host/profile/accounts.js +1 -1
  39. package/dist/host/profile/agent-accounts.js +1 -1
  40. package/dist/host/profile/plugin-share.d.ts +5 -0
  41. package/dist/host/profile/plugin-share.js +22 -1
  42. package/dist/host/secrets/secret-store.d.ts +3 -3
  43. package/dist/host/secrets/secret-store.js +37 -10
  44. package/dist/host/vpn/login.js +1 -1
  45. package/dist/runtime/agent.js +16 -1
  46. package/dist/runtime/daemon.js +531 -337
  47. package/dist/runtime/db.js +17 -1
  48. package/dist/runtime/main.js +694 -472
  49. package/dist/runtime/netproxy.js +16 -1
  50. package/dist/runtime/view.js +16 -1
  51. package/dist/selftest/accounts.test.js +7 -0
  52. package/dist/selftest/agent-paths.test.js +12 -1
  53. package/dist/selftest/detect.test.js +7 -0
  54. package/dist/selftest/live-network.test.js +5 -0
  55. package/dist/selftest/support/fixtures.d.ts +6 -0
  56. package/dist/shared/channel/schema.d.ts +18 -0
  57. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  58. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  59. package/native/csharp/HostTools/Program.cs +6 -1
  60. package/native/csharp/HostTools/Protect.cs +41 -0
  61. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  62. package/package.json +1 -1
  63. package/schemas/config.schema.json +34 -1
  64. package/schemas/sandbox-config.schema.json +34 -1
@@ -17,6 +17,9 @@ export const MAX_PLUGIN_BYTES = 1024 * 1024 * 1024;
17
17
  const MAX_SETTINGS_BYTES = 4 * 1024 * 1024;
18
18
  const MAX_DEPTH = 24;
19
19
  const INSTALLED = 'installed_plugins.json';
20
+ /** What Claude rewrites when a plugin or marketplace is added, removed or updated: equal files, same plugins. */
21
+ const RECORDS = [INSTALLED, 'known_marketplaces.json'];
22
+ const MAX_RECORD_BYTES = 4 * 1024 * 1024;
20
23
  function isObject(value) {
21
24
  return typeof value === 'object' && value !== null && !Array.isArray(value);
22
25
  }
@@ -69,7 +72,7 @@ export class PluginShare {
69
72
  /** When a sandbox ends: its plugins refresh the shared set. Never throws; returns the problem, if any. */
70
73
  saveQuietly(dataPath, claudeDir) {
71
74
  try {
72
- if (PluginShare.hasPlugins(claudeDir))
75
+ if (PluginShare.hasPlugins(claudeDir) && !this.matches(claudeDir))
73
76
  this.save(dataPath, claudeDir);
74
77
  return null;
75
78
  }
@@ -131,9 +134,27 @@ export class PluginShare {
131
134
  merged[key] = { ...shared[key], ...(isObject(merged[key]) ? merged[key] : {}) };
132
135
  writeSandboxJson(file, merged);
133
136
  }
137
+ /**
138
+ * Whether the shared set already holds this Claude folder's plugins: the same plugin records and plugin settings.
139
+ * Copying a big plugins folder takes seconds, so a switch between accounts with the same plugins skips it.
140
+ */
141
+ matches(claudeDir) {
142
+ if (!this.hasShared())
143
+ return false;
144
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
145
+ for (const name of RECORDS) {
146
+ const own = readUntrusted(path.join(claudeDir, 'plugins', name), MAX_RECORD_BYTES);
147
+ if (!same(own, readUntrusted(path.join(this.pluginsDir, name), MAX_RECORD_BYTES)))
148
+ return false;
149
+ }
150
+ const shared = readUntrusted(this.settingsFile, MAX_SETTINGS_BYTES);
151
+ return same(pluginSettings(claudeDir), isObject(shared) ? shared : {});
152
+ }
134
153
  /** One step when an account is switched to. */
135
154
  sync(dataPath, claudeDir) {
136
155
  if (PluginShare.hasPlugins(claudeDir)) {
156
+ if (this.matches(claudeDir))
157
+ return 'nothing';
137
158
  this.save(dataPath, claudeDir);
138
159
  return 'saved';
139
160
  }
@@ -8,12 +8,12 @@ export interface SecretBox {
8
8
  export declare class SecretStore {
9
9
  private readonly box;
10
10
  constructor(box: SecretBox);
11
- /** Windows DPAPI, current user. */
12
- static dpapi(): SecretStore;
11
+ /** Windows DPAPI, current user; with a storage folder, through its HostTools.exe (much faster than PowerShell). */
12
+ static dpapi(storageRoot?: string | null): SecretStore;
13
13
  /** The test stand-in (not secure). */
14
14
  static memory(): SecretStore;
15
15
  /** What the program uses: DPAPI, except during the self-test (it never spawns Windows for this). */
16
- static standard(): SecretStore;
16
+ static standard(storageRoot?: string | null): SecretStore;
17
17
  /** True when `value` looks like something seal() made (any box). */
18
18
  static isSealed(value: unknown): value is string;
19
19
  seal(text: string): string;
@@ -1,15 +1,38 @@
1
1
  // Encryption at rest for what must not sit in plain files (saved logins, the VPN login). The real box is Windows DPAPI
2
- // for the current user (native\scripts\protect-data.ps1: data on standard input, never on a command line); the memory
3
- // box is the self-test's stand-in and is not secure. A sealed value is a short string: "<kind>:<base64>".
2
+ // for the current user (HostTools.exe protect, else native\scripts\protect-data.ps1: data on standard input, never on
3
+ // a command line); the memory box is the self-test's stand-in and is not secure. A sealed value is a short string:
4
+ // "<kind>:<base64>".
4
5
  import { spawnSync } from 'node:child_process';
5
6
  import { fail } from '../../core/util/index.js';
6
7
  import { SYSTEM } from '../../shared/system.js';
7
8
  import { selfTestRunning } from '../../shared/test-guard.js';
8
- import { nativeScript } from '../native.js';
9
+ import { hostToolsExe, nativeScript } from '../native.js';
9
10
  const TIMEOUT_MS = 60000;
10
- /** DPAPI for the current Windows user, through powershell.exe 5.1. */
11
+ /** HostTools.exe per storage folder (null: it cannot be built there), found once per process. */
12
+ const toolsFor = new Map();
13
+ function dpapiTool(storageRoot) {
14
+ if (!toolsFor.has(storageRoot)) {
15
+ let exe = null;
16
+ try {
17
+ exe = hostToolsExe(storageRoot);
18
+ }
19
+ catch {
20
+ // Its bin folder cannot be written (another user's storage): PowerShell does the work.
21
+ }
22
+ toolsFor.set(storageRoot, exe);
23
+ }
24
+ return toolsFor.get(storageRoot) ?? null;
25
+ }
26
+ /**
27
+ * DPAPI for the current Windows user: HostTools.exe (built in <storageRoot>\bin; about 0.15 s a call) when a storage
28
+ * folder is known, else powershell.exe 5.1 (about 2 s a call, which a menu feels). Both read and write the same format.
29
+ */
11
30
  class DpapiBox {
31
+ storageRoot;
12
32
  kind = 'dpapi1';
33
+ constructor(storageRoot) {
34
+ this.storageRoot = storageRoot;
35
+ }
13
36
  seal(plain) {
14
37
  return this.run('protect', plain.toString('base64'));
15
38
  }
@@ -17,7 +40,11 @@ class DpapiBox {
17
40
  return Buffer.from(this.run('unprotect', sealed), 'base64');
18
41
  }
19
42
  run(mode, input) {
20
- const result = spawnSync(SYSTEM.powershell(), nativeScript('protect-data.ps1', ['-Mode', mode]), {
43
+ const tools = this.storageRoot ? dpapiTool(this.storageRoot) : null;
44
+ const [exe, args] = tools
45
+ ? [tools, [mode]]
46
+ : [SYSTEM.powershell(), nativeScript('protect-data.ps1', ['-Mode', mode])];
47
+ const result = spawnSync(exe, args, {
21
48
  input,
22
49
  encoding: 'utf8',
23
50
  windowsHide: true,
@@ -49,17 +76,17 @@ export class SecretStore {
49
76
  constructor(box) {
50
77
  this.box = box;
51
78
  }
52
- /** Windows DPAPI, current user. */
53
- static dpapi() {
54
- return new SecretStore(new DpapiBox());
79
+ /** Windows DPAPI, current user; with a storage folder, through its HostTools.exe (much faster than PowerShell). */
80
+ static dpapi(storageRoot = null) {
81
+ return new SecretStore(new DpapiBox(storageRoot));
55
82
  }
56
83
  /** The test stand-in (not secure). */
57
84
  static memory() {
58
85
  return new SecretStore(new MemoryBox());
59
86
  }
60
87
  /** What the program uses: DPAPI, except during the self-test (it never spawns Windows for this). */
61
- static standard() {
62
- return selfTestRunning() ? SecretStore.memory() : SecretStore.dpapi();
88
+ static standard(storageRoot = null) {
89
+ return selfTestRunning() ? SecretStore.memory() : SecretStore.dpapi(storageRoot);
63
90
  }
64
91
  /** True when `value` looks like something seal() made (any box). */
65
92
  static isSealed(value) {
@@ -27,7 +27,7 @@ export function loginProblem(c) {
27
27
  export class VpnLogin {
28
28
  storageRoot;
29
29
  secrets;
30
- constructor(storageRoot, secrets = SecretStore.standard()) {
30
+ constructor(storageRoot, secrets = SecretStore.standard(storageRoot)) {
31
31
  this.storageRoot = storageRoot;
32
32
  this.secrets = secrets;
33
33
  }
@@ -25486,7 +25486,7 @@ var SandboxSchema = external_exports.strictObject({
25486
25486
  "When the sandbox (or this PC) runs low on memory, slow builds down in steps, like a PC that gets slower instead of failing: build programs get lower CPU and memory priority, then less CPU, then compilers take turns (the waiting ones briefly suspended and trimmed to the paging file). Nothing is stopped or killed, and the agents themselves are never touched. Yours only: a project file cannot change it."
25487
25487
  ),
25488
25488
  vGpu: external_exports.boolean().describe(
25489
- "Virtual GPU. Off by default: Microsoft notes it widens the sandbox attack surface. The sandbox then uses this PC's graphics card, and with some graphics drivers heavy use can freeze the whole PC: turn it on only when a program in the sandbox needs the GPU (the mesa toolchain gives software OpenGL without it)."
25489
+ "Virtual GPU. Off by default: Microsoft notes it widens the sandbox attack surface. The sandbox then uses this PC's graphics card, and with some graphics drivers heavy use can freeze the whole PC: turn it on only when a program in the sandbox needs the GPU (the mesa toolchain gives software OpenGL without it). Browsers in the sandbox never use it (they draw in software)."
25490
25490
  ),
25491
25491
  networking: external_exports.boolean().describe("Internet access. false is the only hard network isolation Windows Sandbox offers."),
25492
25492
  clipboard: external_exports.boolean().describe(
@@ -25648,6 +25648,21 @@ var ToolchainsSchema = external_exports.strictObject({
25648
25648
  msvcVersion: external_exports.string().min(1).describe('"latest" or an MSVC tools version prefix, e.g. "14.44".'),
25649
25649
  sdkVersion: external_exports.string().min(1).describe('"latest" or a Windows SDK build, e.g. "26100" or "10.0.22621".')
25650
25650
  }),
25651
+ unreal: external_exports.strictObject({
25652
+ enabled: enabled(
25653
+ "Unreal Engine development: Visual Studio Community 2022 with the C++ game workload (MSVC, a Windows 11 SDK, .NET Framework targeting packs, Unreal integration and the Epic Games Launcher) and the DirectX End-User Runtime. A real installer: it runs every boot (~10 GB on the sandbox disk). The engine itself comes from the Epic Games Launcher or its source.",
25654
+ "a .uproject or .uplugin"
25655
+ ),
25656
+ acceptLicense: external_exports.boolean().describe(
25657
+ "Required to install: you accept the Visual Studio Community licence terms (https://visualstudio.microsoft.com/license-terms/) and the DirectX End-User Runtime licence."
25658
+ ),
25659
+ components: StringsSchema.describe(
25660
+ 'More Visual Studio workload or component IDs to add, e.g. ["Microsoft.VisualStudio.Component.VC.Llvm.Clang"].'
25661
+ ),
25662
+ keepInstallFiles: external_exports.boolean().describe(
25663
+ "Keep an offline copy of the Visual Studio install files in tools\\visualstudio (~10 GB, made once), so later boots install without downloading them again. Skipped while that disk has too little room."
25664
+ )
25665
+ }),
25651
25666
  cpp: external_exports.strictObject({
25652
25667
  enabled: enabled(
25653
25668
  "MinGW-w64 GCC with CMake, Ninja, premake5 and mingw32-make (also Rust's linker).",