windows-development-sandbox 1.14.0 → 1.16.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 (100) hide show
  1. package/config.example.json +2 -1
  2. package/dist/app/features/accounts/accounts.def.js +4 -3
  3. package/dist/app/features/defs.d.ts +4 -2
  4. package/dist/app/features/defs.js +26 -12
  5. package/dist/app/features/projects/projects.def.d.ts +1 -1
  6. package/dist/app/features/projects/projects.run.d.ts +1 -1
  7. package/dist/app/features/projects/projects.run.js +10 -2
  8. package/dist/app/features/sandbox/sandbox.def.js +12 -6
  9. package/dist/app/features/sessions/sessions.def.js +1 -1
  10. package/dist/app/features/settings/categories.def.js +1 -0
  11. package/dist/bin/agent-sandbox.js +2 -2
  12. package/dist/bin/{chunk-F7IMME5G.js → chunk-7EXDUNE7.js} +98 -79
  13. package/dist/bin/{chunk-BQ23GBLC.js → chunk-HIC4CUMY.js} +1925 -1430
  14. package/dist/bin/{chunk-LQHEUN2A.js → chunk-PXTLKIV4.js} +1 -1
  15. package/dist/bin/claude-host.js +2 -2
  16. package/dist/bin/claude-sbx.js +2 -2
  17. package/dist/bin/codex-sbx.js +2 -2
  18. package/dist/bin/gemini-sbx.js +2 -2
  19. package/dist/bin/sandbox.js +2 -2
  20. package/dist/bin/sbx-hide.js +2 -2
  21. package/dist/bin/sbx-show.js +2 -2
  22. package/dist/cli/commands/projects/here.js +2 -0
  23. package/dist/cli/index.js +4 -1
  24. package/dist/cli/report.d.ts +7 -0
  25. package/dist/cli/report.js +31 -0
  26. package/dist/cli/services/health-service.js +15 -3
  27. package/dist/cli/services/projects-service.d.ts +8 -3
  28. package/dist/cli/services/projects-service.js +21 -4
  29. package/dist/core/config/defaults.js +1 -0
  30. package/dist/core/schema/config.d.ts +4 -0
  31. package/dist/core/schema/projects.d.ts +4 -0
  32. package/dist/core/schema/projects.js +3 -0
  33. package/dist/guest/boot/boot.js +8 -2
  34. package/dist/guest/daemon/daemon.js +3 -0
  35. package/dist/guest/daemon/live-folders.d.ts +48 -0
  36. package/dist/guest/daemon/live-folders.js +245 -0
  37. package/dist/guest/daemon/live.d.ts +4 -0
  38. package/dist/guest/daemon/live.js +57 -0
  39. package/dist/guest/entries/daemon.js +4 -2
  40. package/dist/guest/runtime/csc.d.ts +1 -0
  41. package/dist/guest/runtime/csc.js +1 -0
  42. package/dist/guest/runtime/secrets.d.ts +5 -2
  43. package/dist/guest/runtime/secrets.js +12 -5
  44. package/dist/host/engines/hcs/live-folders.d.ts +117 -0
  45. package/dist/host/engines/hcs/live-folders.js +347 -0
  46. package/dist/host/engines/index.d.ts +1 -0
  47. package/dist/host/engines/index.js +1 -0
  48. package/dist/host/launch/generate.js +8 -1
  49. package/dist/host/projects/join.d.ts +4 -0
  50. package/dist/host/projects/join.js +11 -3
  51. package/dist/host/projects/mapper.d.ts +34 -5
  52. package/dist/host/projects/mapper.js +88 -20
  53. package/dist/host/projects/registry.d.ts +2 -0
  54. package/dist/host/projects/registry.js +1 -1
  55. package/dist/host/secrets/launch-secrets.js +2 -1
  56. package/dist/runtime/agent.js +14 -0
  57. package/dist/runtime/daemon.js +466 -132
  58. package/dist/runtime/db.js +14 -0
  59. package/dist/runtime/main.js +26 -3
  60. package/dist/runtime/netproxy.js +14 -0
  61. package/dist/runtime/view.js +14 -0
  62. package/dist/selftest/accounts.test.js +22 -0
  63. package/dist/selftest/app.test.js +2 -2
  64. package/dist/selftest/index.js +2 -0
  65. package/dist/selftest/layout.test.js +1 -0
  66. package/dist/selftest/live-folders.test.d.ts +2 -0
  67. package/dist/selftest/live-folders.test.js +588 -0
  68. package/dist/selftest/projects.test.js +7 -7
  69. package/dist/selftest/support/engine-golden.js +1 -0
  70. package/dist/selftest/support/fixtures.d.ts +1 -0
  71. package/dist/selftest/support/live.js +2 -0
  72. package/dist/shared/channel/index.d.ts +1 -0
  73. package/dist/shared/channel/index.js +1 -0
  74. package/dist/shared/channel/live.d.ts +53 -0
  75. package/dist/shared/channel/live.js +92 -0
  76. package/dist/shared/channel/run-dir.d.ts +5 -0
  77. package/dist/shared/channel/run-dir.js +11 -0
  78. package/dist/shared/channel/secrets.d.ts +1 -0
  79. package/dist/shared/channel/secrets.js +5 -0
  80. package/dist/shared/paths/projects.d.ts +6 -1
  81. package/dist/shared/paths/projects.js +21 -1
  82. package/dist/sync/watcher.js +9 -0
  83. package/dist/web/index.html +2 -2
  84. package/dist/web/main-JTH3FT4O.js +221 -0
  85. package/dist/web/{main-UAQPI5XD.css → main-XT7T6ODM.css} +1 -1
  86. package/native/csharp/HcsHost/AssemblyInfo.cs +4 -4
  87. package/native/csharp/HcsHost/FolderAccess.cs +361 -0
  88. package/native/csharp/HcsHost/Hcs.cs +31 -3
  89. package/native/csharp/HcsHost/Program.cs +54 -6
  90. package/native/csharp/HcsHost/Share.cs +64 -11
  91. package/native/csharp/HcsHost/WsbGuard.cs +130 -0
  92. package/native/csharp/HcsHost/WsbShare.cs +295 -0
  93. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  94. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  95. package/native/scripts/contained-common.ps1 +4 -0
  96. package/native/scripts/hcs-live-share.ps1 +191 -0
  97. package/package.json +1 -1
  98. package/sandbox/csharp/BindMount.cs +120 -0
  99. package/schemas/config.schema.json +8 -0
  100. package/dist/web/main-6XNBTJLT.js +0 -221
@@ -4,13 +4,13 @@ using System.Reflection;
4
4
  using System.Runtime.InteropServices;
5
5
 
6
6
  [assembly: AssemblyTitle("Windows Development Sandbox HCS helper")]
7
- [assembly: AssemblyDescription("Helper of windows-development-sandbox (open source, MIT), used only by the beta HCS engine: asks the Windows Host Compute Service to list, create, start and stop its sandbox virtual machine, gives that machine access to its own disk file and creates a blank virtual disk file. It never mounts a disk on this PC. Source: https://github.com/JoshDiDuca/windows-development-sandbox")]
7
+ [assembly: AssemblyDescription("Helper of windows-development-sandbox (open source, MIT), used only by the beta HCS engine: asks the Windows Host Compute Service to list, create, start and stop its sandbox virtual machine, gives that machine access to its own disk file, creates a blank virtual disk file and, for the opt-in live folders alpha, adds or removes a folder share on the running Windows Sandbox after an administrator prompt. It never mounts a disk on this PC. Source: https://github.com/JoshDiDuca/windows-development-sandbox")]
8
8
  [assembly: AssemblyCompany("windows-development-sandbox (open source)")]
9
9
  [assembly: AssemblyProduct("Windows Development Sandbox")]
10
10
  [assembly: AssemblyCopyright("MIT License, windows-development-sandbox contributors")]
11
- [assembly: AssemblyVersion("1.14.0.0")]
12
- [assembly: AssemblyFileVersion("1.14.0.0")]
13
- [assembly: AssemblyInformationalVersion("1.14.0")]
11
+ [assembly: AssemblyVersion("1.16.0.0")]
12
+ [assembly: AssemblyFileVersion("1.16.0.0")]
13
+ [assembly: AssemblyInformationalVersion("1.16.0")]
14
14
  [assembly: ComVisible(false)]
15
15
  // computecore.dll and computestorage.dll are not KnownDLLs: load them from System32 only, never from the exe's folder.
16
16
  [assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
@@ -0,0 +1,361 @@
1
+ // What wsb-share may expose, checked here in the elevated helper without relying on the unelevated caller: the plain
2
+ // long path of an existing folder on a fixed local drive (ShareRequest.FolderProblem), not on a subst drive, opened
3
+ // without following links and shared in the form Windows itself reports for that handle; never Windows, Program Files,
4
+ // ProgramData, the profile root or another user's profile, nor this tool's storage or data folder (or a folder that
5
+ // holds them); and only a folder the unelevated user can read (and write, for a writable share) themselves.
6
+ using System;
7
+ using System.ComponentModel;
8
+ using System.IO;
9
+ using System.Runtime.InteropServices;
10
+ using System.Text;
11
+
12
+ namespace AgentSandbox.HcsHost
13
+ {
14
+ public static class FolderAccess
15
+ {
16
+ private const uint FileReadAttributes = 0x80;
17
+ private const uint FileShareAll = 0x7;
18
+ /// <summary>Read and write, never delete: while the handle is held the folder cannot be renamed or replaced.</summary>
19
+ private const uint FileShareReadWrite = 0x3;
20
+ private const uint OpenExisting = 3;
21
+ private const uint FlagBackupSemantics = 0x02000000;
22
+ private const uint FlagOpenReparsePoint = 0x00200000;
23
+ private const uint AttributeDirectory = 0x10;
24
+ private const uint AttributeReparsePoint = 0x400;
25
+ private const uint TokenQuery = 0x0008;
26
+ private const uint TokenDuplicate = 0x0002;
27
+ private const int TokenLinkedTokenClass = 19;
28
+ private const int SecurityIdentification = 1;
29
+ private const uint OwnerGroupDacl = 0x1 | 0x2 | 0x4;
30
+ private const uint FileGenericRead = 0x120089;
31
+ private const uint FileGenericWrite = 0x120116;
32
+ private const uint FileGenericExecute = 0x1200A0;
33
+ private const uint FileAllAccess = 0x1F01FF;
34
+ private const uint FileTraverse = 0x20;
35
+ private static readonly IntPtr InvalidHandle = new IntPtr(-1);
36
+
37
+ [StructLayout(LayoutKind.Sequential)]
38
+ private struct GenericMapping
39
+ {
40
+ public uint GenericRead;
41
+ public uint GenericWrite;
42
+ public uint GenericExecute;
43
+ public uint GenericAll;
44
+ }
45
+
46
+ [StructLayout(LayoutKind.Sequential)]
47
+ private struct ByHandleFileInformation
48
+ {
49
+ public uint FileAttributes;
50
+ public long CreationTime;
51
+ public long LastAccessTime;
52
+ public long LastWriteTime;
53
+ public uint VolumeSerialNumber;
54
+ public uint FileSizeHigh;
55
+ public uint FileSizeLow;
56
+ public uint NumberOfLinks;
57
+ public uint FileIndexHigh;
58
+ public uint FileIndexLow;
59
+ }
60
+
61
+ private static class NativeMethods
62
+ {
63
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
64
+ public static extern IntPtr CreateFileW(string name, uint access, uint share, IntPtr security, uint disposition, uint flags, IntPtr template);
65
+
66
+ [DllImport("kernel32.dll", SetLastError = true)]
67
+ public static extern bool GetFileInformationByHandle(IntPtr file, out ByHandleFileInformation info);
68
+
69
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
70
+ public static extern uint GetFinalPathNameByHandleW(IntPtr file, StringBuilder path, uint length, uint flags);
71
+
72
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
73
+ public static extern uint QueryDosDeviceW(string device, StringBuilder target, uint length);
74
+
75
+ [DllImport("kernel32.dll", SetLastError = true)]
76
+ public static extern bool CloseHandle(IntPtr handle);
77
+
78
+ [DllImport("kernel32.dll")]
79
+ public static extern IntPtr GetCurrentProcess();
80
+
81
+ [DllImport("advapi32.dll", SetLastError = true)]
82
+ public static extern bool OpenProcessToken(IntPtr process, uint access, out IntPtr token);
83
+
84
+ [DllImport("advapi32.dll", SetLastError = true)]
85
+ public static extern bool GetTokenInformation(IntPtr token, int infoClass, out IntPtr linked, int length, out int returned);
86
+
87
+ [DllImport("advapi32.dll", SetLastError = true)]
88
+ public static extern bool DuplicateToken(IntPtr token, int level, out IntPtr duplicate);
89
+
90
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
91
+ public static extern bool GetFileSecurityW(string name, uint info, byte[] descriptor, uint length, out uint needed);
92
+
93
+ [DllImport("advapi32.dll", SetLastError = true)]
94
+ public static extern bool AccessCheck(byte[] descriptor, IntPtr token, uint desired, ref GenericMapping mapping, byte[] privileges, ref uint privilegesLength, out uint granted, out bool status);
95
+ }
96
+
97
+ /// <summary>
98
+ /// Null (with the path to share in `finalPath` and the folder still open in `held`, without delete sharing, so it
99
+ /// cannot be renamed or swapped for a link until the caller closes it after the share) when the folder may be
100
+ /// shared with Windows Sandbox; otherwise why not.
101
+ /// </summary>
102
+ public static string Problem(string path, bool readOnly, string storageRoot, string dataPath, out string finalPath, out IntPtr held)
103
+ {
104
+ finalPath = null;
105
+ held = IntPtr.Zero;
106
+ if (string.IsNullOrEmpty(storageRoot) || string.IsNullOrEmpty(dataPath))
107
+ {
108
+ return "the request must name this tool's storage and data folders (they are never shared)";
109
+ }
110
+ string problem = ShareRequest.FolderProblem(path);
111
+ if (problem != null)
112
+ {
113
+ return problem;
114
+ }
115
+ string folder = path.TrimEnd('\\');
116
+ if (IsSubst(folder.Substring(0, 2)))
117
+ {
118
+ return "the folder is on a subst drive: give its real path";
119
+ }
120
+ string real;
121
+ problem = Open(folder, out real, out held);
122
+ if (problem != null)
123
+ {
124
+ return problem;
125
+ }
126
+ problem = CheckOpened(folder, real, readOnly, storageRoot, dataPath);
127
+ if (problem != null)
128
+ {
129
+ Close(held);
130
+ held = IntPtr.Zero;
131
+ return problem;
132
+ }
133
+ finalPath = real;
134
+ return null;
135
+ }
136
+
137
+ private static string CheckOpened(string folder, string real, bool readOnly, string storageRoot, string dataPath)
138
+ {
139
+ string problem;
140
+ if (!string.Equals(real, folder, StringComparison.OrdinalIgnoreCase))
141
+ {
142
+ return "the folder's real path is " + real + ": give that one";
143
+ }
144
+ problem = DeniedProblem(real, storageRoot, dataPath);
145
+ if (problem != null)
146
+ {
147
+ return problem;
148
+ }
149
+ return UserAccessProblem(real, readOnly);
150
+ }
151
+
152
+ public static void Close(IntPtr held)
153
+ {
154
+ if (held != IntPtr.Zero && held != InvalidHandle)
155
+ {
156
+ NativeMethods.CloseHandle(held);
157
+ }
158
+ }
159
+
160
+ /// <summary>After the share: the held folder is still at `path` (null), or why not.</summary>
161
+ public static string MovedProblem(IntPtr held, string path)
162
+ {
163
+ string now;
164
+ string problem = FinalPath(held, out now);
165
+ if (problem != null)
166
+ {
167
+ return problem;
168
+ }
169
+ return string.Equals(now, path, StringComparison.OrdinalIgnoreCase) ? null : "the folder moved while it was being shared";
170
+ }
171
+
172
+ private static bool IsSubst(string drive)
173
+ {
174
+ StringBuilder target = new StringBuilder(1024);
175
+ if (NativeMethods.QueryDosDeviceW(drive, target, (uint)target.Capacity) == 0)
176
+ {
177
+ return true; // unknown: treated like a subst (fail closed)
178
+ }
179
+ return target.ToString().StartsWith("\\??\\", StringComparison.Ordinal);
180
+ }
181
+
182
+ /// <summary>
183
+ /// Opens the folder itself without following a link and without delete sharing (kept open in `handle`), and
184
+ /// says the path Windows reports for it.
185
+ /// </summary>
186
+ private static string Open(string folder, out string real, out IntPtr handle)
187
+ {
188
+ real = null;
189
+ handle = NativeMethods.CreateFileW(folder, FileReadAttributes, FileShareReadWrite, IntPtr.Zero, OpenExisting, FlagBackupSemantics | FlagOpenReparsePoint, IntPtr.Zero);
190
+ if (handle == InvalidHandle)
191
+ {
192
+ handle = IntPtr.Zero;
193
+ return "the folder cannot be opened (or something is renaming it): " + new Win32Exception(Marshal.GetLastWin32Error()).Message;
194
+ }
195
+ bool keep = false;
196
+ try
197
+ {
198
+ ByHandleFileInformation info;
199
+ if (!NativeMethods.GetFileInformationByHandle(handle, out info))
200
+ {
201
+ return "the folder cannot be read: " + new Win32Exception(Marshal.GetLastWin32Error()).Message;
202
+ }
203
+ if ((info.FileAttributes & AttributeDirectory) == 0 || (info.FileAttributes & AttributeReparsePoint) != 0)
204
+ {
205
+ return "only a plain folder (not a file or a link) can be shared";
206
+ }
207
+ string problem = FinalPath(handle, out real);
208
+ keep = problem == null;
209
+ return problem;
210
+ }
211
+ finally
212
+ {
213
+ if (!keep)
214
+ {
215
+ NativeMethods.CloseHandle(handle);
216
+ handle = IntPtr.Zero;
217
+ }
218
+ }
219
+ }
220
+
221
+ private static string FinalPath(IntPtr handle, out string real)
222
+ {
223
+ real = null;
224
+ StringBuilder sb = new StringBuilder(1024);
225
+ uint length = NativeMethods.GetFinalPathNameByHandleW(handle, sb, (uint)sb.Capacity, 0);
226
+ if (length == 0 || length >= sb.Capacity)
227
+ {
228
+ return "the folder's real path cannot be read";
229
+ }
230
+ string text = sb.ToString();
231
+ {
232
+ if (!text.StartsWith("\\\\?\\", StringComparison.Ordinal) || text.StartsWith("\\\\?\\UNC\\", StringComparison.OrdinalIgnoreCase))
233
+ {
234
+ return "only folders on a local drive can be shared";
235
+ }
236
+ real = text.Substring(4).TrimEnd('\\');
237
+ return null;
238
+ }
239
+ }
240
+
241
+ private static bool Within(string parent, string child)
242
+ {
243
+ if (string.IsNullOrEmpty(parent))
244
+ {
245
+ return false;
246
+ }
247
+ string p = parent.TrimEnd('\\');
248
+ return string.Equals(p, child, StringComparison.OrdinalIgnoreCase) ||
249
+ child.StartsWith(p + "\\", StringComparison.OrdinalIgnoreCase);
250
+ }
251
+
252
+ /// <summary>Pure apart from the special folders: the folders that are never shared.</summary>
253
+ public static string DeniedProblem(string real, string storageRoot, string dataPath)
254
+ {
255
+ string[] system = new string[]
256
+ {
257
+ Environment.GetFolderPath(Environment.SpecialFolder.Windows),
258
+ Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
259
+ Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
260
+ Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
261
+ };
262
+ foreach (string s in system)
263
+ {
264
+ if (Within(s, real) || Within(real, s))
265
+ {
266
+ return "system folders cannot be shared: " + real;
267
+ }
268
+ }
269
+ string profile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile).TrimEnd('\\');
270
+ string users = Path.GetDirectoryName(profile);
271
+ if (Within(users, real) && !(Within(profile, real) && !string.Equals(profile, real, StringComparison.OrdinalIgnoreCase)))
272
+ {
273
+ return "only a folder inside your own profile can be shared from the users folder (never the profile itself or another user's)";
274
+ }
275
+ foreach (string own in new string[] { storageRoot, dataPath })
276
+ {
277
+ if (!string.IsNullOrEmpty(own) && (Within(own, real) || Within(real, own)))
278
+ {
279
+ return "this tool's own storage or data folder cannot be shared: " + real;
280
+ }
281
+ }
282
+ return null;
283
+ }
284
+
285
+ /// <summary>The token of the user who asked: the unelevated half of an elevated token, else this one.</summary>
286
+ private static IntPtr UserToken()
287
+ {
288
+ IntPtr process;
289
+ if (!NativeMethods.OpenProcessToken(NativeMethods.GetCurrentProcess(), TokenQuery | TokenDuplicate, out process))
290
+ {
291
+ throw new Win32Exception(Marshal.GetLastWin32Error());
292
+ }
293
+ try
294
+ {
295
+ IntPtr linked;
296
+ int returned;
297
+ if (NativeMethods.GetTokenInformation(process, TokenLinkedTokenClass, out linked, IntPtr.Size, out returned) && linked != IntPtr.Zero)
298
+ {
299
+ return linked;
300
+ }
301
+ IntPtr duplicate;
302
+ if (!NativeMethods.DuplicateToken(process, SecurityIdentification, out duplicate))
303
+ {
304
+ throw new Win32Exception(Marshal.GetLastWin32Error());
305
+ }
306
+ return duplicate;
307
+ }
308
+ finally
309
+ {
310
+ NativeMethods.CloseHandle(process);
311
+ }
312
+ }
313
+
314
+ /// <summary>Null when the unelevated user may list (and, for a writable share, change) the folder themselves.</summary>
315
+ public static string UserAccessProblem(string folder, bool readOnly)
316
+ {
317
+ uint needed;
318
+ NativeMethods.GetFileSecurityW(folder, OwnerGroupDacl, null, 0, out needed);
319
+ if (needed == 0 || needed > 1024 * 1024)
320
+ {
321
+ return "the folder's permissions cannot be read";
322
+ }
323
+ byte[] descriptor = new byte[needed];
324
+ if (!NativeMethods.GetFileSecurityW(folder, OwnerGroupDacl, descriptor, needed, out needed))
325
+ {
326
+ return "the folder's permissions cannot be read: " + new Win32Exception(Marshal.GetLastWin32Error()).Message;
327
+ }
328
+ IntPtr token = IntPtr.Zero;
329
+ try
330
+ {
331
+ token = UserToken();
332
+ GenericMapping mapping = new GenericMapping();
333
+ mapping.GenericRead = FileGenericRead;
334
+ mapping.GenericWrite = FileGenericWrite;
335
+ mapping.GenericExecute = FileGenericExecute;
336
+ mapping.GenericAll = FileAllAccess;
337
+ uint desired = FileGenericRead | FileTraverse | (readOnly ? 0u : FileGenericWrite);
338
+ byte[] privileges = new byte[256];
339
+ uint privilegesLength = (uint)privileges.Length;
340
+ uint granted;
341
+ bool allowed;
342
+ if (!NativeMethods.AccessCheck(descriptor, token, desired, ref mapping, privileges, ref privilegesLength, out granted, out allowed))
343
+ {
344
+ return "the folder's permissions cannot be checked: " + new Win32Exception(Marshal.GetLastWin32Error()).Message;
345
+ }
346
+ if (!allowed)
347
+ {
348
+ return readOnly ? "you cannot read this folder yourself, so it cannot be shared" : "you cannot change this folder yourself, so it cannot be shared writable";
349
+ }
350
+ return null;
351
+ }
352
+ finally
353
+ {
354
+ if (token != IntPtr.Zero)
355
+ {
356
+ NativeMethods.CloseHandle(token);
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
@@ -63,7 +63,9 @@ namespace AgentSandbox.HcsHost
63
63
 
64
64
  public static class Hcs
65
65
  {
66
+ /// <summary>HcsOpenComputeSystem documents no narrower right than GENERIC_ALL for modify.</summary>
66
67
  private const uint GenericAll = 0x10000000;
68
+ private const uint LoadLibrarySearchSystem32 = 0x00000800;
67
69
  private const uint QueryMs = 15000;
68
70
  private const uint ChangeMs = 180000;
69
71
 
@@ -116,7 +118,7 @@ namespace AgentSandbox.HcsHost
116
118
  public static extern IntPtr LocalFree(IntPtr mem);
117
119
 
118
120
  [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
119
- public static extern IntPtr LoadLibrary(string fileName);
121
+ public static extern IntPtr LoadLibraryEx(string fileName, IntPtr file, uint flags);
120
122
 
121
123
  [DllImport("kernel32.dll", SetLastError = true)]
122
124
  public static extern bool FreeLibrary(IntPtr module);
@@ -139,10 +141,10 @@ namespace AgentSandbox.HcsHost
139
141
  }
140
142
  }
141
143
 
142
- /// <summary>Whether a DLL (computecore.dll) can be loaded at all on this PC.</summary>
144
+ /// <summary>Whether a DLL (computecore.dll) can be loaded at all on this PC (from System32 only).</summary>
143
145
  public static bool CanLoad(string dll)
144
146
  {
145
- IntPtr module = NativeMethods.LoadLibrary(dll);
147
+ IntPtr module = NativeMethods.LoadLibraryEx(dll, IntPtr.Zero, LoadLibrarySearchSystem32);
146
148
  if (module == IntPtr.Zero)
147
149
  {
148
150
  return false;
@@ -254,6 +256,32 @@ namespace AgentSandbox.HcsHost
254
256
  return WithSystem(id, ChangeMs, delegate(IntPtr system, IntPtr op) { return NativeMethods.HcsModifyComputeSystem(system, op, modifySettingRequest, IntPtr.Zero); });
255
257
  }
256
258
 
259
+ /// <summary>Opens a compute system and keeps the handle (Close it): checks and changes then use the same system.</summary>
260
+ public static int Open(string id, out IntPtr system)
261
+ {
262
+ return NativeMethods.HcsOpenComputeSystem(id, GenericAll, out system);
263
+ }
264
+
265
+ public static void Close(IntPtr system)
266
+ {
267
+ if (system != IntPtr.Zero)
268
+ {
269
+ NativeMethods.HcsCloseComputeSystem(system);
270
+ }
271
+ }
272
+
273
+ /// <summary>The properties of an opened system (Id, SystemType, Owner, State, ...).</summary>
274
+ public static HcsReply PropertiesOf(IntPtr system)
275
+ {
276
+ return Run(delegate(IntPtr op) { return NativeMethods.HcsGetComputeSystemProperties(system, op, "{\"PropertyTypes\":[]}"); }, QueryMs);
277
+ }
278
+
279
+ /// <summary>One ModifySettingRequest on an opened system, waiting at most `waitMs`.</summary>
280
+ public static HcsReply ModifyOn(IntPtr system, string modifySettingRequest, uint waitMs)
281
+ {
282
+ return Run(delegate(IntPtr op) { return NativeMethods.HcsModifyComputeSystem(system, op, modifySettingRequest, IntPtr.Zero); }, waitMs);
283
+ }
284
+
257
285
  /// <summary>Lets the virtual machine `vmId` open the file (its disk). The file must exist.</summary>
258
286
  public static HcsReply GrantVmAccess(string vmId, string filePath)
259
287
  {
@@ -1,6 +1,8 @@
1
1
  // HcsHost.exe <verb> [arguments] --out <response.json>
2
2
  // Verbs: check, enumerate, properties <id>, create <request.json>, start <id>, terminate <id>,
3
- // grant-vm-access <vmId> <file>, create-vhdx <path> <maxGb>, share <request.json> (prototype).
3
+ // grant-vm-access <vmId> <file>, create-vhdx <path> <maxGb>, share <request.json> (our own systems),
4
+ // wsb-share <request.json> --sha256 <hex> (live folders on the running Windows Sandbox, WsbShare.cs),
5
+ // wsb-rules <enumerate.json> [id] (pure: applies WsbGuard's rules to a recorded list; asks HCS nothing).
4
6
  // The answer is one JSON file (see Json.Response); exit code 0 = ok, 1 = failed, 2 = wrong use. A create request is
5
7
  // {"id": "...", "document": "<the HCS document as a JSON string>"}. Never attaches a disk to this PC.
6
8
  using System;
@@ -23,6 +25,7 @@ namespace AgentSandbox.HcsHost
23
25
  public static int Main(string[] args)
24
26
  {
25
27
  string outFile = null;
28
+ string sha256 = null;
26
29
  List<string> rest = new List<string>();
27
30
  for (int i = 0; i < args.Length; i++)
28
31
  {
@@ -31,6 +34,11 @@ namespace AgentSandbox.HcsHost
31
34
  outFile = args[i + 1];
32
35
  i++;
33
36
  }
37
+ else if (args[i] == "--sha256" && i + 1 < args.Length)
38
+ {
39
+ sha256 = args[i + 1];
40
+ i++;
41
+ }
34
42
  else
35
43
  {
36
44
  rest.Add(args[i]);
@@ -40,7 +48,7 @@ namespace AgentSandbox.HcsHost
40
48
  int code;
41
49
  try
42
50
  {
43
- code = Dispatch(rest, out response);
51
+ code = Dispatch(rest, sha256, out response);
44
52
  }
45
53
  catch (Exception ex)
46
54
  {
@@ -73,11 +81,11 @@ namespace AgentSandbox.HcsHost
73
81
  return reply.Ok ? ExitOk : ExitFailed;
74
82
  }
75
83
 
76
- private static int Dispatch(List<string> args, out string response)
84
+ private static int Dispatch(List<string> args, string sha256, out string response)
77
85
  {
78
86
  if (args.Count == 0)
79
87
  {
80
- return Usage(out response, "HcsHost <check|enumerate|properties|create|start|terminate|grant-vm-access|create-vhdx> ... --out file");
88
+ return Usage(out response, "HcsHost <check|enumerate|properties|create|start|terminate|grant-vm-access|create-vhdx|share|wsb-share|wsb-rules> ... --out file");
81
89
  }
82
90
  string verb = args[0];
83
91
  if (verb == "check")
@@ -113,6 +121,22 @@ namespace AgentSandbox.HcsHost
113
121
  {
114
122
  return Share(args[1], out response);
115
123
  }
124
+ if (verb == "wsb-share" && args.Count == 2)
125
+ {
126
+ if (sha256 == null || !System.Text.RegularExpressions.Regex.IsMatch(sha256, "^[0-9A-Fa-f]{64}$"))
127
+ {
128
+ return Usage(out response, "wsb-share <request.json> --sha256 <its SHA-256>");
129
+ }
130
+ Dictionary<string, string> values = new Dictionary<string, string>();
131
+ string message;
132
+ bool ok = WsbShare.Run(args[1], sha256, values, out message);
133
+ response = Json.Response(ok, 0, message, "", values);
134
+ return ok ? ExitOk : (message.StartsWith("Refused: ") ? ExitUsage : ExitFailed);
135
+ }
136
+ if (verb == "wsb-rules" && (args.Count == 2 || args.Count == 3))
137
+ {
138
+ return WsbRules(args[1], args.Count == 3 ? args[2] : null, out response);
139
+ }
116
140
  if (verb == "grant-vm-access" && args.Count == 3)
117
141
  {
118
142
  if (Refused(Guard.IdProblem(args[1]), out response) || Refused(Guard.DiskProblem(args[2]), out response))
@@ -175,8 +199,8 @@ namespace AgentSandbox.HcsHost
175
199
  }
176
200
 
177
201
  /// <summary>
178
- /// PROTOTYPE (docs/design/hcs-live-folders.md): adds or removes a vSMB share on a running system of OURS (id
179
- /// prefix and Owner, as for start/terminate). Windows Sandbox's own system is refused like any other.
202
+ /// Adds or removes a vSMB share on a running system of OURS (id prefix and Owner, as for start/terminate).
203
+ /// Windows Sandbox's own system is refused like any other: that is wsb-share's, with its own rules.
180
204
  /// </summary>
181
205
  private static int Share(string requestFile, out string response)
182
206
  {
@@ -195,6 +219,30 @@ namespace AgentSandbox.HcsHost
195
219
  return FromReply(Hcs.Modify(request.Id, values["request"]), values, out response);
196
220
  }
197
221
 
222
+ /// <summary>The test hook for WsbGuard: its rules on a recorded enumerate answer. Never calls HCS.</summary>
223
+ private static int WsbRules(string file, string id, out string response)
224
+ {
225
+ FileInfo info = new FileInfo(file);
226
+ if (!info.Exists || info.Length > MaxRequestBytes)
227
+ {
228
+ return Usage(out response, "wsb-rules <enumerate.json> [id]");
229
+ }
230
+ string found;
231
+ string problem = WsbGuard.Candidate(File.ReadAllText(file, Encoding.UTF8), out found);
232
+ if (problem == null && id != null)
233
+ {
234
+ problem = WsbGuard.IdProblem(id) ?? WsbGuard.MatchProblem(found, id);
235
+ }
236
+ Dictionary<string, string> values = new Dictionary<string, string>();
237
+ values["id"] = found ?? "";
238
+ if (Refused(problem, out response))
239
+ {
240
+ return ExitUsage;
241
+ }
242
+ response = Json.Response(true, 0, "ok", "", values);
243
+ return ExitOk;
244
+ }
245
+
198
246
  /// <summary>True (with the refusal as the response) when `problem` says why the request is not allowed.</summary>
199
247
  private static bool Refused(string problem, out string response)
200
248
  {