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.
Files changed (448) hide show
  1. package/README.md +1 -0
  2. package/config.example.json +17 -1
  3. package/dist/cli/args.d.ts +13 -2
  4. package/dist/cli/args.js +95 -24
  5. package/dist/cli/commands/accounts.js +45 -17
  6. package/dist/cli/commands/context.d.ts +2 -0
  7. package/dist/cli/commands/context.js +9 -2
  8. package/dist/cli/commands/data/clean.js +32 -9
  9. package/dist/cli/commands/index.d.ts +1 -0
  10. package/dist/cli/commands/index.js +1 -0
  11. package/dist/cli/commands/launch/add-root.d.ts +3 -2
  12. package/dist/cli/commands/launch/add-root.js +11 -10
  13. package/dist/cli/commands/launch/launch.js +57 -4
  14. package/dist/cli/commands/launch/prepare.js +3 -1
  15. package/dist/cli/commands/maintenance/audit.js +1 -1
  16. package/dist/cli/commands/maintenance/clean.d.ts +1 -1
  17. package/dist/cli/commands/maintenance/clean.js +17 -3
  18. package/dist/cli/commands/maintenance/config.d.ts +1 -1
  19. package/dist/cli/commands/maintenance/config.js +112 -10
  20. package/dist/cli/commands/maintenance/doctor.d.ts +1 -1
  21. package/dist/cli/commands/maintenance/doctor.js +2 -4
  22. package/dist/cli/commands/maintenance/migrate.d.ts +2 -1
  23. package/dist/cli/commands/maintenance/migrate.js +19 -4
  24. package/dist/cli/commands/maintenance/sync.js +2 -0
  25. package/dist/cli/commands/maintenance/update.d.ts +2 -0
  26. package/dist/cli/commands/maintenance/update.js +27 -4
  27. package/dist/cli/commands/projects/here.js +12 -7
  28. package/dist/cli/commands/projects/projects.js +26 -17
  29. package/dist/cli/commands/registry.js +7 -0
  30. package/dist/cli/commands/repair/checks/agents.d.ts +2 -0
  31. package/dist/cli/commands/repair/checks/agents.js +70 -0
  32. package/dist/cli/commands/repair/checks/common.d.ts +8 -0
  33. package/dist/cli/commands/repair/checks/common.js +22 -0
  34. package/dist/cli/commands/repair/checks/data.d.ts +2 -0
  35. package/dist/cli/commands/repair/checks/data.js +241 -0
  36. package/dist/cli/commands/repair/checks/index.d.ts +2 -0
  37. package/dist/cli/commands/repair/checks/index.js +20 -0
  38. package/dist/cli/commands/repair/checks/install.d.ts +5 -0
  39. package/dist/cli/commands/repair/checks/install.js +318 -0
  40. package/dist/cli/commands/repair/checks/machine.d.ts +2 -0
  41. package/dist/cli/commands/repair/checks/machine.js +232 -0
  42. package/dist/cli/commands/repair/checks/network.d.ts +6 -0
  43. package/dist/cli/commands/repair/checks/network.js +60 -0
  44. package/dist/cli/commands/repair/checks/pc.d.ts +4 -0
  45. package/dist/cli/commands/repair/checks/pc.js +55 -0
  46. package/dist/cli/commands/repair/checks/sandbox.d.ts +2 -0
  47. package/dist/cli/commands/repair/checks/sandbox.js +267 -0
  48. package/dist/cli/commands/repair/checks/settings.d.ts +2 -0
  49. package/dist/cli/commands/repair/checks/settings.js +169 -0
  50. package/dist/cli/commands/repair/checks/vpn.d.ts +2 -0
  51. package/dist/cli/commands/repair/checks/vpn.js +53 -0
  52. package/dist/cli/commands/repair/engine.d.ts +32 -0
  53. package/dist/cli/commands/repair/engine.js +127 -0
  54. package/dist/cli/commands/repair/env.d.ts +7 -0
  55. package/dist/cli/commands/repair/env.js +49 -0
  56. package/dist/cli/commands/repair/index.d.ts +1 -9
  57. package/dist/cli/commands/repair/index.js +2 -54
  58. package/dist/cli/commands/repair/log.d.ts +17 -0
  59. package/dist/cli/commands/repair/log.js +82 -0
  60. package/dist/cli/commands/repair/printer.d.ts +32 -0
  61. package/dist/cli/commands/repair/printer.js +132 -0
  62. package/dist/cli/commands/repair/run.d.ts +33 -0
  63. package/dist/cli/commands/repair/run.js +133 -0
  64. package/dist/cli/commands/repair/types.d.ts +97 -9
  65. package/dist/cli/commands/repair/types.js +11 -7
  66. package/dist/cli/commands/resume.js +2 -2
  67. package/dist/cli/commands/sessions/list.js +5 -2
  68. package/dist/cli/commands/sessions/status.d.ts +2 -0
  69. package/dist/cli/commands/sessions/status.js +27 -6
  70. package/dist/cli/commands/setup/index.d.ts +5 -0
  71. package/dist/cli/commands/setup/index.js +25 -1
  72. package/dist/cli/commands/storage/index.d.ts +2 -0
  73. package/dist/cli/commands/storage/index.js +2 -0
  74. package/dist/cli/commands/storage/offer.d.ts +3 -0
  75. package/dist/cli/commands/storage/offer.js +85 -0
  76. package/dist/cli/commands/storage/storage.d.ts +8 -0
  77. package/dist/cli/commands/storage/storage.js +171 -0
  78. package/dist/cli/commands/user-config.js +1 -1
  79. package/dist/cli/commands/vpn.d.ts +4 -0
  80. package/dist/cli/commands/vpn.js +75 -0
  81. package/dist/cli/commands/web.d.ts +4 -0
  82. package/dist/cli/commands/web.js +95 -0
  83. package/dist/cli/history.js +1 -1
  84. package/dist/cli/index.js +46 -3
  85. package/dist/cli/manifest.d.ts +303 -29
  86. package/dist/cli/manifest.js +173 -43
  87. package/dist/cli/menu/add-agent.d.ts +47 -0
  88. package/dist/cli/menu/add-agent.js +168 -0
  89. package/dist/cli/menu/apps.d.ts +31 -2
  90. package/dist/cli/menu/apps.js +44 -5
  91. package/dist/cli/menu/command-fields.d.ts +18 -0
  92. package/dist/cli/menu/command-fields.js +61 -0
  93. package/dist/cli/menu/command-form.d.ts +22 -0
  94. package/dist/cli/menu/command-form.js +169 -46
  95. package/dist/cli/menu/data-folder.d.ts +18 -0
  96. package/dist/cli/menu/data-folder.js +99 -0
  97. package/dist/cli/menu/menu.d.ts +1 -1
  98. package/dist/cli/menu/menu.js +11 -8
  99. package/dist/cli/menu/pages/accounts-page.js +55 -24
  100. package/dist/cli/menu/pages/agents-page.d.ts +66 -0
  101. package/dist/cli/menu/pages/agents-page.js +338 -0
  102. package/dist/cli/menu/pages/apps-page.js +1 -2
  103. package/dist/cli/menu/pages/list-page.d.ts +16 -3
  104. package/dist/cli/menu/pages/list-page.js +40 -8
  105. package/dist/cli/menu/pages/maintenance-page.d.ts +9 -0
  106. package/dist/cli/menu/pages/maintenance-page.js +161 -72
  107. package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
  108. package/dist/cli/menu/pages/sandbox-page.js +132 -43
  109. package/dist/cli/menu/pages/sessions-page.d.ts +4 -0
  110. package/dist/cli/menu/pages/sessions-page.js +41 -25
  111. package/dist/cli/menu/pages/settings-page.d.ts +8 -4
  112. package/dist/cli/menu/pages/settings-page.js +36 -13
  113. package/dist/cli/menu/settings/categories.d.ts +2 -0
  114. package/dist/cli/menu/settings/categories.js +19 -44
  115. package/dist/cli/menu/settings/collections/collection-editor.d.ts +7 -1
  116. package/dist/cli/menu/settings/collections/collection-editor.js +12 -8
  117. package/dist/cli/menu/settings/collections/map-editor.js +5 -1
  118. package/dist/cli/menu/settings/collections/object-list-editor.js +2 -2
  119. package/dist/cli/menu/settings/collections/object-map-editor.js +2 -2
  120. package/dist/cli/menu/settings/collections/string-list-editor.d.ts +11 -1
  121. package/dist/cli/menu/settings/collections/string-list-editor.js +23 -0
  122. package/dist/cli/menu/settings/index.d.ts +1 -1
  123. package/dist/cli/menu/settings/index.js +1 -1
  124. package/dist/cli/menu/settings/object-editor.d.ts +5 -1
  125. package/dist/cli/menu/settings/object-editor.js +15 -4
  126. package/dist/cli/menu/settings/path-kind.d.ts +20 -0
  127. package/dist/cli/menu/settings/path-kind.js +44 -0
  128. package/dist/cli/menu/settings/schema-node.d.ts +9 -0
  129. package/dist/cli/menu/settings/schema-node.js +26 -0
  130. package/dist/cli/menu/settings/value-editor.d.ts +5 -0
  131. package/dist/cli/menu/settings/value-editor.js +51 -18
  132. package/dist/cli/menu/several.js +7 -5
  133. package/dist/cli/menu/shell.d.ts +37 -10
  134. package/dist/cli/menu/shell.js +90 -10
  135. package/dist/cli/menu/vpn-login.d.ts +16 -0
  136. package/dist/cli/menu/vpn-login.js +66 -0
  137. package/dist/cli/output/print.d.ts +2 -0
  138. package/dist/cli/output/print.js +8 -1
  139. package/dist/cli/services/accounts-service.d.ts +2 -0
  140. package/dist/cli/services/accounts-service.js +2 -2
  141. package/dist/cli/services/health-service.d.ts +0 -6
  142. package/dist/cli/services/health-service.js +5 -42
  143. package/dist/cli/services/index.d.ts +3 -1
  144. package/dist/cli/services/index.js +3 -1
  145. package/dist/cli/services/migrate-service.js +1 -1
  146. package/dist/cli/services/update-service.d.ts +2 -0
  147. package/dist/cli/services/update-service.js +13 -6
  148. package/dist/cli/services/vpn-service.d.ts +28 -0
  149. package/dist/cli/services/vpn-service.js +46 -0
  150. package/dist/cli/services/web-service.d.ts +22 -0
  151. package/dist/cli/services/web-service.js +111 -0
  152. package/dist/cli/tui/app.d.ts +6 -3
  153. package/dist/cli/tui/app.js +12 -5
  154. package/dist/cli/tui/dialogs/path-listing.d.ts +43 -0
  155. package/dist/cli/tui/dialogs/path-listing.js +127 -0
  156. package/dist/cli/tui/dialogs/path-picker.d.ts +67 -0
  157. package/dist/cli/tui/dialogs/path-picker.js +423 -0
  158. package/dist/cli/tui/dialogs/text-screen.d.ts +6 -0
  159. package/dist/cli/tui/dialogs/text-screen.js +10 -3
  160. package/dist/cli/tui/frame.d.ts +7 -10
  161. package/dist/cli/tui/frame.js +19 -10
  162. package/dist/cli/tui/index.d.ts +4 -3
  163. package/dist/cli/tui/index.js +2 -1
  164. package/dist/cli/tui/list/list-screen.d.ts +14 -2
  165. package/dist/cli/tui/list/list-screen.js +68 -32
  166. package/dist/cli/tui/list/list-types.d.ts +8 -4
  167. package/dist/cli/tui/nav.d.ts +29 -0
  168. package/dist/cli/tui/nav.js +67 -0
  169. package/dist/cli/validate.d.ts +1 -1
  170. package/dist/cli/validate.js +3 -3
  171. package/dist/cli/version.d.ts +2 -0
  172. package/dist/cli/version.js +11 -0
  173. package/dist/core/config/defaults.js +5 -1
  174. package/dist/core/config/loader.js +12 -2
  175. package/dist/core/local-config/overrides.js +1 -0
  176. package/dist/core/prompt.d.ts +19 -0
  177. package/dist/core/prompt.js +43 -3
  178. package/dist/core/schema/common.d.ts +6 -0
  179. package/dist/core/schema/common.js +8 -0
  180. package/dist/core/schema/config.d.ts +23 -0
  181. package/dist/core/schema/config.js +10 -1
  182. package/dist/core/schema/harness.d.ts +1 -0
  183. package/dist/core/schema/harness.js +8 -2
  184. package/dist/core/schema/index.d.ts +3 -1
  185. package/dist/core/schema/index.js +3 -1
  186. package/dist/core/schema/profile.d.ts +1 -0
  187. package/dist/core/schema/profile.js +3 -0
  188. package/dist/core/schema/redact.d.ts +13 -0
  189. package/dist/core/schema/redact.js +75 -0
  190. package/dist/core/schema/sandbox.js +1 -0
  191. package/dist/core/schema/vpn.d.ts +17 -0
  192. package/dist/core/schema/vpn.js +22 -0
  193. package/dist/core/schema/web.d.ts +6 -0
  194. package/dist/core/schema/web.js +14 -0
  195. package/dist/core/types.d.ts +26 -0
  196. package/dist/guest/agents/agent-files.d.ts +49 -0
  197. package/dist/guest/agents/agent-files.js +63 -0
  198. package/dist/guest/agents/agent-installer.js +2 -9
  199. package/dist/guest/agents/local-state.d.ts +12 -0
  200. package/dist/guest/agents/local-state.js +198 -0
  201. package/dist/guest/agents/sbx-agent.d.ts +2 -0
  202. package/dist/guest/agents/sbx-agent.js +35 -0
  203. package/dist/guest/boot/boot.d.ts +4 -0
  204. package/dist/guest/boot/boot.js +50 -11
  205. package/dist/guest/daemon/daemon.js +4 -1
  206. package/dist/guest/daemon/exec.js +2 -1
  207. package/dist/guest/daemon/idle-shutdown.js +2 -0
  208. package/dist/guest/daemon/sessions.js +4 -1
  209. package/dist/guest/entries/agent.d.ts +1 -0
  210. package/dist/guest/entries/agent.js +10 -0
  211. package/dist/guest/entries/daemon.js +2 -0
  212. package/dist/guest/runtime/csc.d.ts +1 -0
  213. package/dist/guest/runtime/csc.js +1 -0
  214. package/dist/guest/runtime/secrets.d.ts +12 -0
  215. package/dist/guest/runtime/secrets.js +63 -0
  216. package/dist/guest/setup/claude-profile.d.ts +2 -1
  217. package/dist/guest/setup/claude-profile.js +12 -1
  218. package/dist/guest/setup/environment.d.ts +6 -0
  219. package/dist/guest/setup/environment.js +56 -6
  220. package/dist/guest/setup/path-plan.d.ts +32 -0
  221. package/dist/guest/setup/path-plan.js +67 -0
  222. package/dist/guest/setup/shell-path.d.ts +1 -0
  223. package/dist/guest/setup/shell-path.js +22 -0
  224. package/dist/guest/toolchains/languages/python.js +1 -1
  225. package/dist/guest/toolchains/runtimes.js +2 -2
  226. package/dist/guest/vpn/index.d.ts +1 -0
  227. package/dist/guest/vpn/index.js +1 -0
  228. package/dist/guest/vpn/openvpn.d.ts +9 -0
  229. package/dist/guest/vpn/openvpn.js +47 -0
  230. package/dist/guest/vpn/tunnel.d.ts +17 -0
  231. package/dist/guest/vpn/tunnel.js +78 -0
  232. package/dist/guest/vpn/vpn.d.ts +6 -0
  233. package/dist/guest/vpn/vpn.js +126 -0
  234. package/dist/host/channel/attach.d.ts +3 -1
  235. package/dist/host/channel/attach.js +1 -1
  236. package/dist/host/channel/channel.d.ts +7 -0
  237. package/dist/host/channel/channel.js +26 -2
  238. package/dist/host/health/acl-read.d.ts +17 -0
  239. package/dist/host/health/acl-read.js +45 -0
  240. package/dist/host/health/config-backup.d.ts +11 -0
  241. package/dist/host/health/config-backup.js +73 -0
  242. package/dist/host/health/index.d.ts +6 -0
  243. package/dist/host/health/index.js +7 -0
  244. package/dist/host/health/install-state.d.ts +33 -0
  245. package/dist/host/health/install-state.js +110 -0
  246. package/dist/host/health/path-copies.d.ts +32 -0
  247. package/dist/host/health/path-copies.js +82 -0
  248. package/dist/host/health/stale.d.ts +45 -0
  249. package/dist/host/health/stale.js +120 -0
  250. package/dist/host/health/system.d.ts +37 -0
  251. package/dist/host/health/system.js +87 -0
  252. package/dist/host/install/copies.d.ts +13 -0
  253. package/dist/host/install/copies.js +63 -0
  254. package/dist/host/install/index.d.ts +1 -0
  255. package/dist/host/install/index.js +1 -0
  256. package/dist/host/launch/generate.js +25 -1
  257. package/dist/host/launch/session-file.js +4 -1
  258. package/dist/host/launch/stage.js +1 -1
  259. package/dist/host/migrate/backup.js +3 -0
  260. package/dist/host/migrate/migrations/config-file.d.ts +2 -0
  261. package/dist/host/migrate/migrations/config-file.js +7 -0
  262. package/dist/host/migrate/migrations/follow-machine-storage.js +9 -1
  263. package/dist/host/migrate/migrations/pin-storage-root.d.ts +4 -0
  264. package/dist/host/migrate/migrations/pin-storage-root.js +46 -0
  265. package/dist/host/migrate/migrations/unpin-config-defaults.js +4 -1
  266. package/dist/host/migrate/registry.js +2 -1
  267. package/dist/host/profile/accounts.d.ts +25 -1
  268. package/dist/host/profile/accounts.js +96 -13
  269. package/dist/host/profile/agent-accounts.d.ts +5 -1
  270. package/dist/host/profile/agent-accounts.js +35 -5
  271. package/dist/host/profile/index.d.ts +2 -1
  272. package/dist/host/profile/index.js +1 -0
  273. package/dist/host/profile/plugin-share.d.ts +26 -0
  274. package/dist/host/profile/plugin-share.js +146 -0
  275. package/dist/host/projects/join.d.ts +7 -1
  276. package/dist/host/projects/join.js +21 -4
  277. package/dist/host/projects/registry.d.ts +16 -10
  278. package/dist/host/projects/registry.js +74 -6
  279. package/dist/host/sandbox/platform.d.ts +2 -0
  280. package/dist/host/sandbox/platform.js +21 -2
  281. package/dist/host/secrets/index.d.ts +2 -0
  282. package/dist/host/secrets/index.js +2 -0
  283. package/dist/host/secrets/launch-secrets.d.ts +17 -0
  284. package/dist/host/secrets/launch-secrets.js +93 -0
  285. package/dist/host/secrets/secret-store.d.ts +25 -0
  286. package/dist/host/secrets/secret-store.js +89 -0
  287. package/dist/host/sharing/acl.d.ts +16 -2
  288. package/dist/host/sharing/acl.js +35 -10
  289. package/dist/host/sharing/current.js +11 -2
  290. package/dist/host/sharing/doctor.d.ts +2 -0
  291. package/dist/host/sharing/doctor.js +44 -0
  292. package/dist/host/sharing/group-access.d.ts +6 -0
  293. package/dist/host/sharing/group-access.js +24 -0
  294. package/dist/host/sharing/index.d.ts +2 -1
  295. package/dist/host/sharing/index.js +2 -1
  296. package/dist/host/sharing/lifecycle.d.ts +8 -2
  297. package/dist/host/sharing/lifecycle.js +16 -7
  298. package/dist/host/storage/cleanup.d.ts +2 -0
  299. package/dist/host/storage/cleanup.js +2 -1
  300. package/dist/host/storage/history.d.ts +21 -0
  301. package/dist/host/storage/history.js +49 -0
  302. package/dist/host/storage/index.d.ts +4 -0
  303. package/dist/host/storage/index.js +4 -0
  304. package/dist/host/storage/move.d.ts +95 -0
  305. package/dist/host/storage/move.js +384 -0
  306. package/dist/host/storage/offer.d.ts +11 -0
  307. package/dist/host/storage/offer.js +36 -0
  308. package/dist/host/storage/root.d.ts +18 -0
  309. package/dist/host/storage/root.js +77 -0
  310. package/dist/host/tools/windows-terminal.d.ts +2 -0
  311. package/dist/host/tools/windows-terminal.js +23 -5
  312. package/dist/host/update/index.d.ts +1 -1
  313. package/dist/host/update/index.js +1 -1
  314. package/dist/host/update/notice.d.ts +2 -0
  315. package/dist/host/update/notice.js +13 -1
  316. package/dist/host/update/update-state.d.ts +6 -0
  317. package/dist/host/update/update-state.js +2 -0
  318. package/dist/host/update/updater.d.ts +19 -0
  319. package/dist/host/update/updater.js +88 -12
  320. package/dist/host/vpn/index.d.ts +4 -0
  321. package/dist/host/vpn/index.js +4 -0
  322. package/dist/host/vpn/login.d.ts +24 -0
  323. package/dist/host/vpn/login.js +79 -0
  324. package/dist/host/vpn/nordvpn.d.ts +21 -0
  325. package/dist/host/vpn/nordvpn.js +77 -0
  326. package/dist/host/vpn/prepare.d.ts +17 -0
  327. package/dist/host/vpn/prepare.js +77 -0
  328. package/dist/host/vpn/status.d.ts +12 -0
  329. package/dist/host/vpn/status.js +53 -0
  330. package/dist/host/web/auth.d.ts +38 -0
  331. package/dist/host/web/auth.js +96 -0
  332. package/dist/host/web/backend.d.ts +14 -0
  333. package/dist/host/web/backend.js +1 -0
  334. package/dist/host/web/bridge.d.ts +27 -0
  335. package/dist/host/web/bridge.js +144 -0
  336. package/dist/host/web/browser.d.ts +1 -0
  337. package/dist/host/web/browser.js +10 -0
  338. package/dist/host/web/control.d.ts +17 -0
  339. package/dist/host/web/control.js +67 -0
  340. package/dist/host/web/index.d.ts +9 -0
  341. package/dist/host/web/index.js +8 -0
  342. package/dist/host/web/runner.d.ts +14 -0
  343. package/dist/host/web/runner.js +45 -0
  344. package/dist/host/web/server.d.ts +40 -0
  345. package/dist/host/web/server.js +218 -0
  346. package/dist/host/web/state.d.ts +20 -0
  347. package/dist/host/web/state.js +41 -0
  348. package/dist/host/web/static-files.d.ts +13 -0
  349. package/dist/host/web/static-files.js +57 -0
  350. package/dist/runtime/agent.js +27614 -0
  351. package/dist/runtime/daemon.js +1012 -618
  352. package/dist/runtime/db.js +236 -106
  353. package/dist/runtime/main.js +1093 -250
  354. package/dist/runtime/netproxy.js +102 -3
  355. package/dist/runtime/view.js +102 -3
  356. package/dist/selftest/accounts.test.js +40 -0
  357. package/dist/selftest/agent-paths.test.d.ts +2 -0
  358. package/dist/selftest/agent-paths.test.js +115 -0
  359. package/dist/selftest/channel.test.js +5 -0
  360. package/dist/selftest/cli.test.js +34 -4
  361. package/dist/selftest/credentials.test.d.ts +2 -0
  362. package/dist/selftest/credentials.test.js +198 -0
  363. package/dist/selftest/generate.test.js +1 -1
  364. package/dist/selftest/health.test.d.ts +2 -0
  365. package/dist/selftest/health.test.js +470 -0
  366. package/dist/selftest/host-sim.test.js +2 -0
  367. package/dist/selftest/index.js +19 -0
  368. package/dist/selftest/layout.test.js +2 -0
  369. package/dist/selftest/menu-screens.d.ts +7 -0
  370. package/dist/selftest/menu-screens.js +65 -16
  371. package/dist/selftest/menu.test.js +574 -21
  372. package/dist/selftest/migrate.test.js +1 -1
  373. package/dist/selftest/personalization.test.js +3 -1
  374. package/dist/selftest/projects.test.js +39 -1
  375. package/dist/selftest/shared-use.test.js +64 -2
  376. package/dist/selftest/sharing.test.js +50 -1
  377. package/dist/selftest/storage.test.d.ts +2 -0
  378. package/dist/selftest/storage.test.js +363 -0
  379. package/dist/selftest/support/fixtures.d.ts +14 -0
  380. package/dist/selftest/support/releases.js +1 -1
  381. package/dist/selftest/toolchains.test.js +21 -0
  382. package/dist/selftest/update-service.test.js +87 -3
  383. package/dist/selftest/vpn.test.d.ts +2 -0
  384. package/dist/selftest/vpn.test.js +279 -0
  385. package/dist/selftest/web.test.d.ts +2 -0
  386. package/dist/selftest/web.test.js +304 -0
  387. package/dist/shared/apps/catalogue.js +31 -0
  388. package/dist/shared/channel/index.d.ts +1 -0
  389. package/dist/shared/channel/index.js +1 -0
  390. package/dist/shared/channel/run-dir.d.ts +6 -1
  391. package/dist/shared/channel/run-dir.js +12 -1
  392. package/dist/shared/channel/schema.d.ts +19 -0
  393. package/dist/shared/channel/schema.js +11 -0
  394. package/dist/shared/channel/secrets.d.ts +28 -0
  395. package/dist/shared/channel/secrets.js +44 -0
  396. package/dist/shared/vpn/download.d.ts +13 -0
  397. package/dist/shared/vpn/download.js +14 -0
  398. package/dist/shared/vpn/kill-switch.d.ts +18 -0
  399. package/dist/shared/vpn/kill-switch.js +81 -0
  400. package/dist/shared/vpn/ovpn.d.ts +23 -0
  401. package/dist/shared/vpn/ovpn.js +244 -0
  402. package/dist/shared/web/index.d.ts +1 -0
  403. package/dist/shared/web/index.js +1 -0
  404. package/dist/shared/web/protocol.d.ts +61 -0
  405. package/dist/shared/web/protocol.js +35 -0
  406. package/dist/sync/sync-cycle.js +5 -1
  407. package/dist/sync/sync-state.d.ts +1 -0
  408. package/dist/sync/sync-state.js +1 -0
  409. package/dist/sync/watcher.js +10 -0
  410. package/dist/web/index.html +13 -0
  411. package/dist/web/main-CXESL347.js +45 -0
  412. package/dist/web/main-VLR7DVQ4.css +1 -0
  413. package/native/csharp/FolderSecurity.cs +29 -0
  414. package/native/scripts/protect-data.ps1 +29 -0
  415. package/native/scripts/read-folder-sddl.ps1 +10 -0
  416. package/native/scripts/set-folder-access.ps1 +49 -7
  417. package/node_modules/ws/LICENSE +20 -0
  418. package/node_modules/ws/README.md +548 -0
  419. package/node_modules/ws/browser.js +8 -0
  420. package/node_modules/ws/index.js +22 -0
  421. package/node_modules/ws/lib/buffer-util.js +131 -0
  422. package/node_modules/ws/lib/constants.js +19 -0
  423. package/node_modules/ws/lib/event-target.js +292 -0
  424. package/node_modules/ws/lib/extension.js +203 -0
  425. package/node_modules/ws/lib/limiter.js +55 -0
  426. package/node_modules/ws/lib/permessage-deflate.js +530 -0
  427. package/node_modules/ws/lib/receiver.js +743 -0
  428. package/node_modules/ws/lib/sender.js +607 -0
  429. package/node_modules/ws/lib/stream.js +161 -0
  430. package/node_modules/ws/lib/subprotocol.js +62 -0
  431. package/node_modules/ws/lib/validation.js +152 -0
  432. package/node_modules/ws/lib/websocket-server.js +562 -0
  433. package/node_modules/ws/lib/websocket.js +1421 -0
  434. package/node_modules/ws/package.json +74 -0
  435. package/node_modules/ws/wrapper.mjs +21 -0
  436. package/package.json +15 -5
  437. package/sandbox/csharp/EnvBroadcast.cs +33 -0
  438. package/sandbox/csharp/PathSync.cs +90 -0
  439. package/sandbox/scripts/install-shell-path.ps1 +73 -0
  440. package/sandbox/scripts/set-machine-environment.ps1 +37 -4
  441. package/schemas/config.schema.json +79 -4
  442. package/schemas/sandbox-config.schema.json +14 -1
  443. package/dist/cli/commands/repair/checks.d.ts +0 -2
  444. package/dist/cli/commands/repair/checks.js +0 -246
  445. package/dist/cli/menu/pages/start-page.d.ts +0 -43
  446. package/dist/cli/menu/pages/start-page.js +0 -192
  447. package/dist/cli/tui/tab-bar.d.ts +0 -9
  448. package/dist/cli/tui/tab-bar.js +0 -33
@@ -15,26 +15,28 @@ export const AbsolutePathSchema = z
15
15
  .string()
16
16
  .max(1024)
17
17
  .refine((p) => path.isAbsolute(p), 'must be an absolute path');
18
- const RegistryEntrySchema = z.strictObject({
18
+ // Not strict: another copy or version of this tool may have written the file with more fields, and a registry that
19
+ // reads as nothing would make a running sandbox look like one that cannot take another folder.
20
+ const RegistryEntrySchema = z.object({
19
21
  name: IdSchema,
20
22
  host: AbsolutePathSchema,
21
23
  guest: z.string().max(1024),
22
24
  primary: z.boolean(),
23
- mechanism: z.enum(['launch', 'root', 'boot', 'wsb']),
25
+ mechanism: z.enum(['launch', 'root', 'boot', 'wsb']).catch('root'),
24
26
  registeredAt: z.string().max(64),
25
27
  });
26
- const RegistrySchema = z.strictObject({
28
+ const RegistrySchema = z.object({
27
29
  launchId: z.string().max(64),
28
30
  /** The running sandbox's protections (core/project-policy.ts), fixed at boot. */
29
31
  protections: z.record(z.string(), z.union([z.boolean(), z.string()])),
30
- roots: z.array(z.strictObject({ host: AbsolutePathSchema, guest: z.string().max(1024) })).max(64),
32
+ roots: z.array(z.object({ host: AbsolutePathSchema, guest: z.string().max(1024) })).max(64),
31
33
  projects: z.array(RegistryEntrySchema).max(1024),
32
34
  /** What the sandbox booted with that a joining project may also want (missing in registries of 1.4.0 and older). */
33
35
  atBoot: z
34
- .strictObject({
36
+ .object({
35
37
  memoryMb: z.number().int().positive().nullable(),
36
38
  mappings: z
37
- .array(z.strictObject({ host: z.string().max(1024), guest: z.string().max(1024), readOnly: z.boolean() }))
39
+ .array(z.object({ host: z.string().max(1024), guest: z.string().max(1024), readOnly: z.boolean() }))
38
40
  .max(1024),
39
41
  /** Boot settings turned on for a project mapped at boot (setting -> project): other projects share them. */
40
42
  widened: z.record(z.string().max(100), z.string().max(100)),
@@ -116,6 +118,72 @@ export class ProjectRegistry {
116
118
  rootHere(hostDir) {
117
119
  return projectForHostDir(this.read()?.roots ?? [], hostDir);
118
120
  }
121
+ /**
122
+ * The record rebuilt from the running sandbox's launch folder (host-only storage), for when run\projects.json is
123
+ * missing, cannot be read or belongs to an earlier sandbox: the launch folder, and every folder mapped for other
124
+ * projects at boot as a root. Saved and returned; null when the launch folder does not have what it takes.
125
+ */
126
+ rebuild(launchPath, launchId, protections) {
127
+ const session = readBounded(path.join(launchPath, 'session.json'), MAX_SESSION_BYTES);
128
+ const wsb = readBounded(path.join(launchPath, 'agent.wsb'), MAX_WSB_BYTES);
129
+ if (session === null || wsb === null)
130
+ return null;
131
+ let parsed;
132
+ try {
133
+ const result = LaunchSessionSchema.safeParse(JSON.parse(session));
134
+ if (!result.success || result.data.launchId !== launchId)
135
+ return null;
136
+ parsed = result.data;
137
+ }
138
+ catch {
139
+ return null;
140
+ }
141
+ const atBoot = parseWsbBoot(wsb);
142
+ const forProjects = new Set(parsed.roots.map((guest) => guest.toLowerCase()));
143
+ const roots = atBoot.mappings
144
+ .filter((m) => forProjects.has(m.guest.toLowerCase()) && path.isAbsolute(m.host))
145
+ .slice(0, 64)
146
+ .map((m) => ({ host: m.host, guest: m.guest }));
147
+ const registry = {
148
+ launchId,
149
+ protections,
150
+ roots,
151
+ projects: [
152
+ {
153
+ name: hubName(parsed.workspaceHost, []),
154
+ host: parsed.workspaceHost,
155
+ guest: parsed.workspace,
156
+ primary: true,
157
+ mechanism: 'launch',
158
+ registeredAt: new Date().toISOString(),
159
+ },
160
+ ],
161
+ atBoot,
162
+ };
163
+ this.save(registry);
164
+ return registry;
165
+ }
166
+ }
167
+ const MAX_SESSION_BYTES = 4 * 1024 * 1024;
168
+ const MAX_WSB_BYTES = 1024 * 1024;
169
+ /** What rebuild() needs from a launch folder's session.json (written on this PC by the launch). */
170
+ const LaunchSessionSchema = z.object({
171
+ launchId: z.string().max(64),
172
+ workspace: z.string().min(1).max(1024),
173
+ workspaceHost: AbsolutePathSchema,
174
+ roots: z.array(z.string().max(1024)).max(1024).default([]),
175
+ });
176
+ /** A file's text when it exists, is not a link and is at most `max` bytes; null otherwise. */
177
+ function readBounded(file, max) {
178
+ try {
179
+ const stat = fs.lstatSync(file);
180
+ if (!stat.isFile() || stat.size > max)
181
+ return null;
182
+ return fs.readFileSync(file, 'utf8');
183
+ }
184
+ catch {
185
+ return null;
186
+ }
119
187
  }
120
188
  /**
121
189
  * The projects of a sandbox another user started and shares: what that user's record lists, in the shape of a registry.
@@ -12,5 +12,7 @@ export declare function assertCanLaunch(owner?: SandboxOwner): void;
12
12
  /** cmd.exe expands %VAR% and ^ even inside quotes, and a quote ends them: such a path could run something else. */
13
13
  export declare function assertCmdSafePath(file: string): void;
14
14
  export declare function startSandbox(wsbPath: string): void;
15
+ /** After startSandbox: waits up to 20 seconds for the Windows Sandbox program to show up, or fails naming the .wsb. */
16
+ export declare function confirmSandboxStarted(wsbPath: string, waitMs?: number): Promise<void>;
15
17
  /** Runs a detached copy of this CLI (used for the background history/claude-mem sync watcher). */
16
18
  export declare function spawnDetached(entry: string, args: string[]): void;
@@ -3,8 +3,11 @@ import path from 'node:path';
3
3
  import { spawn, spawnSync } from 'node:child_process';
4
4
  import { SYSTEM } from '../../shared/system.js';
5
5
  import { refuseInSelfTest } from '../test-guard.js';
6
- import { fail, fileExists, isWin } from '../../core/util/index.js';
6
+ import { fail, fileExists, isWin, sleep } from '../../core/util/index.js';
7
7
  import { launchBlocked } from '../sharing/rules.js';
8
+ /** The longest a Windows helper program (tasklist, cmd start) may take before it counts as stuck. */
9
+ const HELPER_TIMEOUT_MS = 15000;
10
+ const START_WAIT_MS = 20000;
8
11
  const SANDBOX_PROCESSES = ['WindowsSandboxClient.exe', 'WindowsSandboxRemoteSession.exe', 'WindowsSandbox.exe'];
9
12
  /**
10
13
  * Running INSIDE a Windows Sandbox this tool started (its launch folder and data folder are mapped in): then the CLI
@@ -22,7 +25,11 @@ export function isSandboxRunning() {
22
25
  return false;
23
26
  if (insideSandbox())
24
27
  return true;
25
- const result = spawnSync(SYSTEM.tasklist(), ['/NH', '/FO', 'CSV'], { encoding: 'utf8', windowsHide: true });
28
+ const result = spawnSync(SYSTEM.tasklist(), ['/NH', '/FO', 'CSV'], {
29
+ encoding: 'utf8',
30
+ windowsHide: true,
31
+ timeout: HELPER_TIMEOUT_MS,
32
+ });
26
33
  const out = (result.stdout ?? '').toLowerCase();
27
34
  return SANDBOX_PROCESSES.some((image) => out.includes('"' + image.toLowerCase() + '"'));
28
35
  }
@@ -55,10 +62,22 @@ export function startSandbox(wsbPath) {
55
62
  const result = spawnSync(SYSTEM.cmd(), ['/d', '/c', 'start', '""', '"' + wsbPath + '"'], {
56
63
  stdio: 'inherit',
57
64
  windowsVerbatimArguments: true,
65
+ timeout: HELPER_TIMEOUT_MS,
58
66
  });
59
67
  if (result.error)
60
68
  throw result.error;
61
69
  }
70
+ /** After startSandbox: waits up to 20 seconds for the Windows Sandbox program to show up, or fails naming the .wsb. */
71
+ export async function confirmSandboxStarted(wsbPath, waitMs = START_WAIT_MS) {
72
+ const deadline = Date.now() + waitMs;
73
+ while (!isSandboxRunning()) {
74
+ if (Date.now() >= deadline)
75
+ fail('Windows Sandbox did not start within ' + Math.round(waitMs / 1000) + ' seconds.', 'Open ' +
76
+ wsbPath +
77
+ ' yourself to see the message Windows gives, or run sandbox doctor to check the Windows Sandbox feature.');
78
+ await sleep(1000);
79
+ }
80
+ }
62
81
  /** Runs a detached copy of this CLI (used for the background history/claude-mem sync watcher). */
63
82
  export function spawnDetached(entry, args) {
64
83
  refuseInSelfTest('start background programs');
@@ -0,0 +1,2 @@
1
+ export { SecretStore, type SecretBox } from './secret-store.js';
2
+ export { clearSecrets, withoutSecrets, writeSecrets } from './launch-secrets.js';
@@ -0,0 +1,2 @@
1
+ export { SecretStore } from './secret-store.js';
2
+ export { clearSecrets, withoutSecrets, writeSecrets } from './launch-secrets.js';
@@ -0,0 +1,17 @@
1
+ import { type SecretsFile } from '../../shared/channel/index.js';
2
+ import type { GuestRequest } from '../../core/types.js';
3
+ /** Writes <data>\secrets\<id>.json (nothing at all when there is nothing secret to hand over). */
4
+ export declare function writeSecrets(dataPath: string, id: string, secrets: SecretsFile): string | null;
5
+ /**
6
+ * Removes whatever one-shot files are left (the sandbox did not start, or did not get to read them) and, from older
7
+ * versions, a login copy in a launch folder. Called before a launch writes new ones and when the sandbox stops.
8
+ */
9
+ export declare function clearSecrets(storageRoot: string, dataPath: string): number;
10
+ /**
11
+ * A request with every agent's environment taken out (those values may be keys) and the values to hand over in the
12
+ * secrets file of that request. Requests that carry none come back untouched.
13
+ */
14
+ export declare function withoutSecrets(request: GuestRequest): {
15
+ request: GuestRequest;
16
+ secrets: SecretsFile | null;
17
+ };
@@ -0,0 +1,93 @@
1
+ // The one-shot secrets file: written before the sandbox starts (or with a request), read and deleted by the sandbox at
2
+ // once. See shared/channel/secrets.ts for what it holds and why it is not in session.json or the launch folder.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { RunDir } from '../../shared/channel/index.js';
6
+ import { writeJsonAtomic } from '../../shared/io/index.js';
7
+ import { assertNoLinks } from '../../core/util/index.js';
8
+ /** Writes <data>\secrets\<id>.json (nothing at all when there is nothing secret to hand over). */
9
+ export function writeSecrets(dataPath, id, secrets) {
10
+ const empty = !Object.keys(secrets.harnessEnvironment ?? {}).length &&
11
+ !Object.keys(secrets.harnessSpecEnvironment ?? {}).length &&
12
+ !Object.keys(secrets.projectHarnessEnvironment ?? {}).length &&
13
+ !secrets.claudeCredentials &&
14
+ !secrets.vpn;
15
+ if (empty)
16
+ return null;
17
+ const run = new RunDir(dataPath);
18
+ assertNoLinks(dataPath, run.secretsDir);
19
+ const file = run.secretFile(id);
20
+ writeJsonAtomic(file, secrets);
21
+ return file;
22
+ }
23
+ /**
24
+ * Removes whatever one-shot files are left (the sandbox did not start, or did not get to read them) and, from older
25
+ * versions, a login copy in a launch folder. Called before a launch writes new ones and when the sandbox stops.
26
+ */
27
+ export function clearSecrets(storageRoot, dataPath) {
28
+ let removed = 0;
29
+ const secrets = new RunDir(dataPath).secretsDir;
30
+ try {
31
+ assertNoLinks(dataPath, secrets);
32
+ for (const name of fs.readdirSync(secrets)) {
33
+ fs.rmSync(path.join(secrets, name), { recursive: true, force: true });
34
+ removed++;
35
+ }
36
+ }
37
+ catch {
38
+ // No such folder, or a link planted there: nothing to remove.
39
+ }
40
+ const launches = path.join(storageRoot, 'launches');
41
+ try {
42
+ for (const launch of fs.readdirSync(launches)) {
43
+ const old = path.join(launches, launch, 'claude-profile', '.credentials.json');
44
+ if (fs.existsSync(old)) {
45
+ fs.rmSync(old, { force: true });
46
+ removed++;
47
+ }
48
+ }
49
+ }
50
+ catch {
51
+ // No launches yet.
52
+ }
53
+ return removed;
54
+ }
55
+ const blank = (h) => ({ ...h, environment: {} });
56
+ const hasValues = (e) => Object.keys(e).length > 0;
57
+ /**
58
+ * A request with every agent's environment taken out (those values may be keys) and the values to hand over in the
59
+ * secrets file of that request. Requests that carry none come back untouched.
60
+ */
61
+ export function withoutSecrets(request) {
62
+ if (request.kind !== 'session' && request.kind !== 'project')
63
+ return { request, secrets: null };
64
+ const project = request.project;
65
+ const spec = request.kind === 'session' ? request.harnessSpec : undefined;
66
+ const projectEnv = Object.fromEntries(Object.entries(project?.harnesses ?? {}).filter(([, h]) => hasValues(h.environment)));
67
+ const specEnv = spec && hasValues(spec.environment) ? spec.environment : undefined;
68
+ if (!specEnv && !Object.keys(projectEnv).length)
69
+ return { request, secrets: null };
70
+ const cleaned = {
71
+ ...request,
72
+ ...(project
73
+ ? {
74
+ project: {
75
+ ...project,
76
+ harnesses: Object.fromEntries(Object.entries(project.harnesses).map(([n, h]) => [n, blank(h)])),
77
+ },
78
+ }
79
+ : {}),
80
+ ...(spec ? { harnessSpec: blank(spec) } : {}),
81
+ };
82
+ return {
83
+ request: cleaned,
84
+ secrets: {
85
+ ...(specEnv ? { harnessSpecEnvironment: specEnv } : {}),
86
+ ...(Object.keys(projectEnv).length
87
+ ? {
88
+ projectHarnessEnvironment: Object.fromEntries(Object.entries(projectEnv).map(([n, h]) => [n, h.environment])),
89
+ }
90
+ : {}),
91
+ },
92
+ };
93
+ }
@@ -0,0 +1,25 @@
1
+ /** Something that turns bytes into a sealed string and back. */
2
+ export interface SecretBox {
3
+ readonly kind: string;
4
+ seal(plain: Buffer): string;
5
+ open(sealed: string): Buffer;
6
+ }
7
+ /** Seals and opens text and JSON with one box. The sealed form says which box made it. */
8
+ export declare class SecretStore {
9
+ private readonly box;
10
+ constructor(box: SecretBox);
11
+ /** Windows DPAPI, current user. */
12
+ static dpapi(): SecretStore;
13
+ /** The test stand-in (not secure). */
14
+ static memory(): SecretStore;
15
+ /** What the program uses: DPAPI, except during the self-test (it never spawns Windows for this). */
16
+ static standard(): SecretStore;
17
+ /** True when `value` looks like something seal() made (any box). */
18
+ static isSealed(value: unknown): value is string;
19
+ seal(text: string): string;
20
+ open(sealed: string): string;
21
+ sealBytes(plain: Buffer): string;
22
+ openBytes(sealed: string): Buffer;
23
+ sealJson(value: unknown): string;
24
+ openJson(sealed: string): unknown;
25
+ }
@@ -0,0 +1,89 @@
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>".
4
+ import { spawnSync } from 'node:child_process';
5
+ import { fail } from '../../core/util/index.js';
6
+ import { SYSTEM } from '../../shared/system.js';
7
+ import { selfTestRunning } from '../../shared/test-guard.js';
8
+ import { nativeScript } from '../native.js';
9
+ const TIMEOUT_MS = 60000;
10
+ /** DPAPI for the current Windows user, through powershell.exe 5.1. */
11
+ class DpapiBox {
12
+ kind = 'dpapi1';
13
+ seal(plain) {
14
+ return this.run('protect', plain.toString('base64'));
15
+ }
16
+ open(sealed) {
17
+ return Buffer.from(this.run('unprotect', sealed), 'base64');
18
+ }
19
+ run(mode, input) {
20
+ const result = spawnSync(SYSTEM.powershell(), nativeScript('protect-data.ps1', ['-Mode', mode]), {
21
+ input,
22
+ encoding: 'utf8',
23
+ windowsHide: true,
24
+ timeout: TIMEOUT_MS,
25
+ });
26
+ if (result.status !== 0 || result.error)
27
+ fail('Windows could not ' +
28
+ (mode === 'protect' ? 'encrypt' : 'decrypt') +
29
+ ' a saved secret: ' +
30
+ (result.stderr || result.error?.message || 'no answer').trim().slice(0, 300), mode === 'protect'
31
+ ? 'Check that powershell.exe runs for your user.'
32
+ : 'It can only be opened by the Windows user that saved it, on the PC that saved it. Save it again.');
33
+ return result.stdout.trim();
34
+ }
35
+ }
36
+ /** A reversible stand-in for tests: it only proves the callers seal and open, not that anything is secret. */
37
+ class MemoryBox {
38
+ kind = 'mem1';
39
+ seal(plain) {
40
+ return Buffer.from(plain.toString('base64')).toString('base64');
41
+ }
42
+ open(sealed) {
43
+ return Buffer.from(Buffer.from(sealed, 'base64').toString('utf8'), 'base64');
44
+ }
45
+ }
46
+ /** Seals and opens text and JSON with one box. The sealed form says which box made it. */
47
+ export class SecretStore {
48
+ box;
49
+ constructor(box) {
50
+ this.box = box;
51
+ }
52
+ /** Windows DPAPI, current user. */
53
+ static dpapi() {
54
+ return new SecretStore(new DpapiBox());
55
+ }
56
+ /** The test stand-in (not secure). */
57
+ static memory() {
58
+ return new SecretStore(new MemoryBox());
59
+ }
60
+ /** 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();
63
+ }
64
+ /** True when `value` looks like something seal() made (any box). */
65
+ static isSealed(value) {
66
+ return typeof value === 'string' && /^(dpapi1|mem1):[A-Za-z0-9+/=]+$/.test(value);
67
+ }
68
+ seal(text) {
69
+ return this.box.kind + ':' + this.box.seal(Buffer.from(text, 'utf8'));
70
+ }
71
+ open(sealed) {
72
+ return this.openBytes(sealed).toString('utf8');
73
+ }
74
+ sealBytes(plain) {
75
+ return this.box.kind + ':' + this.box.seal(plain);
76
+ }
77
+ openBytes(sealed) {
78
+ const at = sealed.indexOf(':');
79
+ if (at < 0 || sealed.slice(0, at) !== this.box.kind)
80
+ fail('A saved secret was made on another system or with another store.', 'Save it again.');
81
+ return this.box.open(sealed.slice(at + 1));
82
+ }
83
+ sealJson(value) {
84
+ return this.seal(JSON.stringify(value));
85
+ }
86
+ openJson(sealed) {
87
+ return JSON.parse(this.open(sealed));
88
+ }
89
+ }
@@ -21,10 +21,24 @@ export declare function sharingAclPlan(o: {
21
21
  on: boolean;
22
22
  }): AclChange[];
23
23
  /**
24
- * Applies the changes with native\scripts\set-folder-access.ps1 (Get-Acl / Set-Acl, refusing links). Throws with the
25
- * script's reason when a change fails. The self-test guard is in applyAcl; the self-test calls this only on its temp
24
+ * Applies the changes with native\scripts\set-folder-access.ps1 (refusing links; writing nothing when an entry would
25
+ * change nothing, and a folder's own entry without touching what is inside it). Throws with the script's reason when a
26
+ * change fails or takes over 30 seconds. The self-test guard is in applyAcl; the self-test calls this only on its temp
26
27
  * folders.
27
28
  */
28
29
  export declare function runFolderAccess(changes: readonly AclChange[]): void;
29
30
  /** Applies sharing changes on this PC (refused inside the self-test). */
30
31
  export declare function applyAcl(changes: readonly AclChange[]): void;
32
+ /** Makes an existing folder private (see protectPrivateFolder) without the self-test guard: for temp folders only. */
33
+ export declare function runPrivateFolder(dir: string, options?: PrivateOptions): void;
34
+ export interface PrivateOptions {
35
+ /** The folder holds files and every one of them has to be walked: wait up to 30 minutes instead of 30 seconds. */
36
+ readonly walkAll?: boolean;
37
+ }
38
+ /**
39
+ * Makes an existing folder (not a link) private: inheritance off, full control for you, SYSTEM and Administrators
40
+ * only, inherited by what is created in it. Nothing is written when it already is; an empty folder is changed without
41
+ * walking inside it. A folder with files is walked (minutes for a large one), so it stops after 30 seconds unless the
42
+ * caller asks for `walkAll` (only an explicit repair does, after saying so). Launch paths never do.
43
+ */
44
+ export declare function protectPrivateFolder(dir: string, options?: PrivateOptions): void;
@@ -32,23 +32,23 @@ export function sharingAclPlan(o) {
32
32
  entry(path.join(o.dataPath, 'run'), 'Modify', 'inherit'),
33
33
  ];
34
34
  }
35
- /**
36
- * Applies the changes with native\scripts\set-folder-access.ps1 (Get-Acl / Set-Acl, refusing links). Throws with the
37
- * script's reason when a change fails. The self-test guard is in applyAcl; the self-test calls this only on its temp
38
- * folders.
39
- */
40
- export function runFolderAccess(changes) {
41
- if (changes.length === 0)
42
- return;
35
+ const ACL_TIMEOUT_MS = 30000;
36
+ /** Making a folder that holds files private walks all of it: a few hundred thousand files take minutes. */
37
+ const WALK_TIMEOUT_MS = 30 * 60000;
38
+ /** One run of native\scripts\set-folder-access.ps1 over `entries` (the JSON's "changes"); throws a UserError. */
39
+ function runScript(entries, timeoutMs = ACL_TIMEOUT_MS) {
43
40
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-sandbox-acl-'));
44
41
  const file = path.join(dir, 'changes.json');
45
42
  try {
46
- fs.writeFileSync(file, JSON.stringify({ changes }, null, 2), 'utf8');
43
+ fs.writeFileSync(file, JSON.stringify({ changes: entries }, null, 2), 'utf8');
47
44
  const run = spawnSync(SYSTEM.powershell(), nativeScript('set-folder-access.ps1', ['-Json', file]), {
48
45
  encoding: 'utf8',
49
46
  windowsHide: true,
50
- timeout: 120000,
47
+ timeout: timeoutMs,
51
48
  });
49
+ const timedOut = run.error?.code === 'ETIMEDOUT';
50
+ if (timedOut)
51
+ fail('Changing the folder permissions took longer than ' + timeoutMs / 1000 + ' seconds and was stopped.', 'Close programs that scan or lock the folders (virus scanners, file explorers) and try again.');
52
52
  if (run.status !== 0 || run.error) {
53
53
  const reason = (run.stderr || run.stdout || run.error?.message || 'no output').trim();
54
54
  fail('Could not change the folder permissions: ' + reason, 'Check that the folders are not links and that you can change their permissions, then try again.');
@@ -58,8 +58,33 @@ export function runFolderAccess(changes) {
58
58
  fs.rmSync(dir, { recursive: true, force: true });
59
59
  }
60
60
  }
61
+ /**
62
+ * Applies the changes with native\scripts\set-folder-access.ps1 (refusing links; writing nothing when an entry would
63
+ * change nothing, and a folder's own entry without touching what is inside it). Throws with the script's reason when a
64
+ * change fails or takes over 30 seconds. The self-test guard is in applyAcl; the self-test calls this only on its temp
65
+ * folders.
66
+ */
67
+ export function runFolderAccess(changes) {
68
+ if (changes.length === 0)
69
+ return;
70
+ runScript(changes);
71
+ }
61
72
  /** Applies sharing changes on this PC (refused inside the self-test). */
62
73
  export function applyAcl(changes) {
63
74
  refuseInSelfTest('change folder permissions');
64
75
  runFolderAccess(changes);
65
76
  }
77
+ /** Makes an existing folder private (see protectPrivateFolder) without the self-test guard: for temp folders only. */
78
+ export function runPrivateFolder(dir, options = {}) {
79
+ runScript([{ op: 'private', path: dir }], options.walkAll ? WALK_TIMEOUT_MS : ACL_TIMEOUT_MS);
80
+ }
81
+ /**
82
+ * Makes an existing folder (not a link) private: inheritance off, full control for you, SYSTEM and Administrators
83
+ * only, inherited by what is created in it. Nothing is written when it already is; an empty folder is changed without
84
+ * walking inside it. A folder with files is walked (minutes for a large one), so it stops after 30 seconds unless the
85
+ * caller asks for `walkAll` (only an explicit repair does, after saying so). Launch paths never do.
86
+ */
87
+ export function protectPrivateFolder(dir, options = {}) {
88
+ refuseInSelfTest('change folder permissions');
89
+ runPrivateFolder(dir, options);
90
+ }
@@ -6,9 +6,14 @@ import { insideSandbox } from '../sandbox/platform.js';
6
6
  import { activeDir } from './machine-paths.js';
7
7
  import { ActiveRecords } from './active-record.js';
8
8
  import { parseTasklist, whoOwnsSandbox } from './owner.js';
9
+ const HELPER_TIMEOUT_MS = 15000;
9
10
  /** The rows of `tasklist /NH /FO CSV`: every process of every session (standard users may see these). */
10
11
  export function listProcesses() {
11
- const run = spawnSync(SYSTEM.tasklist(), ['/NH', '/FO', 'CSV'], { encoding: 'utf8', windowsHide: true });
12
+ const run = spawnSync(SYSTEM.tasklist(), ['/NH', '/FO', 'CSV'], {
13
+ encoding: 'utf8',
14
+ windowsHide: true,
15
+ timeout: HELPER_TIMEOUT_MS,
16
+ });
12
17
  return parseTasklist(run.stdout ?? '');
13
18
  }
14
19
  /** The Windows session this process runs in (0 = services, 1 = the first signed-in user ...), or null when unknown. */
@@ -17,7 +22,11 @@ export function sessionOf(rows, pid = process.pid) {
17
22
  }
18
23
  /** This user's SID, from `whoami /user`: `"DOMAIN\name","S-1-5-21-..."`. */
19
24
  export function currentUserSid() {
20
- const run = spawnSync(SYSTEM.whoami(), ['/user', '/fo', 'csv', '/nh'], { encoding: 'utf8', windowsHide: true });
25
+ const run = spawnSync(SYSTEM.whoami(), ['/user', '/fo', 'csv', '/nh'], {
26
+ encoding: 'utf8',
27
+ windowsHide: true,
28
+ timeout: HELPER_TIMEOUT_MS,
29
+ });
21
30
  const sid = /"(S-1-[0-9-]+)"\s*$/.exec((run.stdout ?? '').trim())?.[1];
22
31
  if (!sid)
23
32
  throw new Error('Could not find out who you are (whoami /user gave: ' + (run.stdout || run.stderr) + ').');
@@ -0,0 +1,2 @@
1
+ /** The extra doctor rows of an all-users install; none for any other install. */
2
+ export declare function machineDoctorRows(packageRoot: string, dataPath: string): [string, string][];
@@ -0,0 +1,44 @@
1
+ // Doctor rows for an all-users install: can this user read the settings for the PC and write the data folder.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { machineConfigPath } from '../../core/config/index.js';
5
+ import { errMsg } from '../../core/util/index.js';
6
+ import { sharingDeps } from './lifecycle.js';
7
+ /** "readable", "not there" or the reason it cannot be read. */
8
+ function readableText(file) {
9
+ try {
10
+ fs.accessSync(file, fs.constants.R_OK);
11
+ return 'readable';
12
+ }
13
+ catch (e) {
14
+ const code = e.code;
15
+ if (code === 'ENOENT' || code === 'ENOTDIR')
16
+ return 'none (built-in defaults apply)';
17
+ return 'NOT readable (' + errMsg(e) + '). Sign out of Windows and in again after being added to the group';
18
+ }
19
+ }
20
+ /** "writable" or why not: a small file is created and removed in the folder (made first when it is missing). */
21
+ function writableText(dir) {
22
+ const probe = path.join(dir, '.write-test-' + process.pid);
23
+ try {
24
+ fs.mkdirSync(dir, { recursive: true });
25
+ fs.writeFileSync(probe, '');
26
+ return 'writable';
27
+ }
28
+ catch (e) {
29
+ return 'NOT writable (' + errMsg(e) + ')';
30
+ }
31
+ finally {
32
+ fs.rmSync(probe, { force: true });
33
+ }
34
+ }
35
+ /** The extra doctor rows of an all-users install; none for any other install. */
36
+ export function machineDoctorRows(packageRoot, dataPath) {
37
+ if (!sharingDeps(packageRoot))
38
+ return [];
39
+ const file = machineConfigPath();
40
+ return [
41
+ ['Settings for this PC', file + ': ' + readableText(file)],
42
+ ['Data folder access', dataPath + ': ' + writableText(dataPath)],
43
+ ];
44
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * true when the group has an entry on any of the existing folders, or when that cannot be read (fail closed: the
3
+ * caller then removes the entries and refuses the launch if that fails). false only when every folder was read and
4
+ * none has a group entry.
5
+ */
6
+ export declare function groupHasAccess(dirs: readonly string[]): boolean;
@@ -0,0 +1,24 @@
1
+ // Does the sharing group still have an entry on one of the folders a sharing plan touches? Read-only (icacls), so a
2
+ // launch that shares nothing can skip the permission script when the answer is no.
3
+ import fs from 'node:fs';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { SYSTEM } from '../../shared/system.js';
6
+ // acl-read.ts is pure; the health barrel would make a cycle (health reads the process list from sharing).
7
+ import { SHARING_GROUP, groupEntries, parseIcacls } from '../health/acl-read.js';
8
+ /**
9
+ * true when the group has an entry on any of the existing folders, or when that cannot be read (fail closed: the
10
+ * caller then removes the entries and refuses the launch if that fails). false only when every folder was read and
11
+ * none has a group entry.
12
+ */
13
+ export function groupHasAccess(dirs) {
14
+ for (const dir of dirs) {
15
+ if (!fs.existsSync(dir))
16
+ continue;
17
+ const run = spawnSync(SYSTEM.icacls(), [dir], { encoding: 'utf8', windowsHide: true, timeout: 15000 });
18
+ if (run.status !== 0 || run.error)
19
+ return true;
20
+ if (groupEntries(parseIcacls(run.stdout ?? '', dir), SHARING_GROUP).length > 0)
21
+ return true;
22
+ }
23
+ return false;
24
+ }
@@ -5,4 +5,5 @@ export { currentUserName, currentUserSid, listProcesses, runningOwner, sessionOf
5
5
  export { ownerLine, ownerName, ownerRule, launchBlocked, type OwnerAction, type OwnerRule } from './rules.js';
6
6
  export { sharedDataPath, type Borrowed } from './borrow.js';
7
7
  export { beginSharing, endSharing, sharingDeps, takeSharingNotice, type SharingDeps, type SharingStart, } from './lifecycle.js';
8
- export { sharingAclPlan, applyAcl, runFolderAccess, type AclChange } from './acl.js';
8
+ export { sharingAclPlan, applyAcl, protectPrivateFolder, runFolderAccess, runPrivateFolder, type AclChange, } from './acl.js';
9
+ export { machineDoctorRows } from './doctor.js';
@@ -7,4 +7,5 @@ export { currentUserName, currentUserSid, listProcesses, runningOwner, sessionOf
7
7
  export { ownerLine, ownerName, ownerRule, launchBlocked } from './rules.js';
8
8
  export { sharedDataPath } from './borrow.js';
9
9
  export { beginSharing, endSharing, sharingDeps, takeSharingNotice, } from './lifecycle.js';
10
- export { sharingAclPlan, applyAcl, runFolderAccess } from './acl.js';
10
+ export { sharingAclPlan, applyAcl, protectPrivateFolder, runFolderAccess, runPrivateFolder, } from './acl.js';
11
+ export { machineDoctorRows } from './doctor.js';
@@ -8,14 +8,20 @@ export interface SharingDeps {
8
8
  readonly groupSid: string | null;
9
9
  /** The shared tree (machine\users): permissions change only for data folders inside it. */
10
10
  readonly usersDir: string;
11
+ /**
12
+ * Does the group have an entry on any of these folders (true also when that cannot be read)? When it says no, a
13
+ * launch that shares nothing has no entries to remove and skips the permission script. Left out: always apply.
14
+ */
15
+ readonly groupHasAccess?: (dirs: readonly string[]) => boolean;
11
16
  }
12
17
  /** What the launch knows about the sandbox it is about to start. */
13
18
  export type SharingStart = Omit<ActiveSandbox, 'startedAt'> & {
14
19
  readonly startedAt?: string;
15
20
  };
16
21
  /**
17
- * The machinery of an all-users install, or null for every other install (nothing is shared there: one user, one
18
- * storage folder).
22
+ * The machinery of the all-users install on this PC, or null when there is none (nothing is shared then: one user, one
23
+ * storage folder). It is found from any copy of the program: an npm copy or a checkout that starts a sandbox shares it
24
+ * the same way the installed one does, with the group and the settings the install recorded.
19
25
  */
20
26
  export declare function sharingDeps(packageRoot: string): SharingDeps | null;
21
27
  /** Writes the record, and opens (shared) or closes (not shared) the owner's folders for the group. */