@neuvybe/jarvis 0.71.0 → 0.73.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.
@@ -0,0 +1,339 @@
1
+ # Per-command containment holder for the bash tool (packages/core/src/tools/contained-command.ts).
2
+ # One command per process. The holder joins a fresh kill-on-close job with no breakaway BEFORE it
3
+ # starts anything, then starts the Git launcher inside it, so everything the command starts is in
4
+ # the job and ends when the holder's job handle closes: when the holder exits after the command,
5
+ # when the core kills it, or when the core dies (its standard input, the lifeline, reaches end of
6
+ # file). It reads one binary spec from standard input, writes a per-command nonce first on each
7
+ # output stream (the ready markers), and after the command a nonce-delimited trailer with the count
8
+ # of processes the command left (all of them ended). It writes nothing else. Fixed exit codes:
9
+ # 0x6A560001 restricted language mode, 0x6A560002 the compile failed, 0x6A560003 the job could not
10
+ # be set up, 0x6A560004 the spec was rejected, 0x6A560005 the launcher did not start (after the
11
+ # markers), 0x6A560006 an internal failure, 0x6A560007 the launcher or the working directory does
12
+ # not exist (after the markers); otherwise the launcher's own exit code, exactly.
13
+ $ErrorActionPreference = 'Stop'
14
+ $ProgressPreference = 'SilentlyContinue'
15
+ $PSModuleAutoLoadingPreference = 'None'
16
+ # Restricted language mode blocks the compile below, so it exits with no output at all.
17
+ if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { exit 0x6A560001 }
18
+ $code = @'
19
+ using System;
20
+ using System.IO;
21
+ using System.Runtime.InteropServices;
22
+ using System.Text;
23
+ using System.Threading;
24
+
25
+ public static class JarvisHolder {
26
+ const uint Job = 0x6A560003;
27
+ const uint Spec = 0x6A560004;
28
+ const uint Launch = 0x6A560005;
29
+ const uint Internal = 0x6A560006;
30
+ const uint NotFound = 0x6A560007;
31
+ const int MaxSpec = 1024 * 1024;
32
+ const int NonceBytes = 16;
33
+
34
+ const uint KillOnJobClose = 0x2000;
35
+ const uint DieOnUnhandledException = 0x400;
36
+ const int ExtendedLimitInformation = 9;
37
+ const int BasicAccountingInformation = 1;
38
+ const uint ExtendedStartupInfoPresent = 0x00080000;
39
+ const uint UnicodeEnvironment = 0x00000400;
40
+ const int UseStdHandles = 0x100;
41
+ const int UseShowWindow = 0x1;
42
+ const uint HandleFlagInherit = 1;
43
+ static readonly IntPtr HandleListAttribute = (IntPtr)0x00020002;
44
+
45
+ [StructLayout(LayoutKind.Sequential)]
46
+ struct BasicLimit {
47
+ public long PerProcessUserTimeLimit;
48
+ public long PerJobUserTimeLimit;
49
+ public uint LimitFlags;
50
+ public UIntPtr MinimumWorkingSetSize;
51
+ public UIntPtr MaximumWorkingSetSize;
52
+ public uint ActiveProcessLimit;
53
+ public UIntPtr Affinity;
54
+ public uint PriorityClass;
55
+ public uint SchedulingClass;
56
+ }
57
+
58
+ [StructLayout(LayoutKind.Sequential)]
59
+ struct IoCounters {
60
+ public ulong ReadOperationCount, WriteOperationCount, OtherOperationCount;
61
+ public ulong ReadTransferCount, WriteTransferCount, OtherTransferCount;
62
+ }
63
+
64
+ [StructLayout(LayoutKind.Sequential)]
65
+ struct ExtendedLimit {
66
+ public BasicLimit Basic;
67
+ public IoCounters Io;
68
+ public UIntPtr ProcessMemoryLimit;
69
+ public UIntPtr JobMemoryLimit;
70
+ public UIntPtr PeakProcessMemoryUsed;
71
+ public UIntPtr PeakJobMemoryUsed;
72
+ }
73
+
74
+ [StructLayout(LayoutKind.Sequential)]
75
+ struct Accounting {
76
+ public long TotalUserTime, TotalKernelTime, ThisPeriodTotalUserTime, ThisPeriodTotalKernelTime;
77
+ public uint TotalPageFaultCount, TotalProcesses, ActiveProcesses, TotalTerminatedProcesses;
78
+ }
79
+
80
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
81
+ struct StartupInfo {
82
+ public int cb;
83
+ public string lpReserved, lpDesktop, lpTitle;
84
+ public int dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
85
+ public short wShowWindow, cbReserved2;
86
+ public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError;
87
+ }
88
+
89
+ [StructLayout(LayoutKind.Sequential)]
90
+ struct StartupInfoEx {
91
+ public StartupInfo StartupInfo;
92
+ public IntPtr lpAttributeList;
93
+ }
94
+
95
+ [StructLayout(LayoutKind.Sequential)]
96
+ struct ProcessInformation {
97
+ public IntPtr hProcess, hThread;
98
+ public int dwProcessId, dwThreadId;
99
+ }
100
+
101
+ delegate bool ConsoleHandler(uint kind);
102
+
103
+ [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr CreateJobObjectW(IntPtr attributes, IntPtr name);
104
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetInformationJobObject(IntPtr job, int kind, ref ExtendedLimit info, int size);
105
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool QueryInformationJobObject(IntPtr job, int kind, out Accounting info, int size, IntPtr returned);
106
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
107
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool TerminateJobObject(IntPtr job, uint code);
108
+ [DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess();
109
+ [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int kind);
110
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetHandleInformation(IntPtr handle, uint mask, uint flags);
111
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern IntPtr CreateFileW(string name, uint access, uint share, IntPtr security, uint disposition, uint flags, IntPtr template);
112
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool WriteFile(IntPtr file, byte[] buffer, int count, out int written, IntPtr overlapped);
113
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool PeekNamedPipe(IntPtr pipe, IntPtr buffer, int size, IntPtr read, out int available, IntPtr left);
114
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool InitializeProcThreadAttributeList(IntPtr list, int count, int flags, ref IntPtr size);
115
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool UpdateProcThreadAttribute(IntPtr list, uint flags, IntPtr attribute, IntPtr value, IntPtr size, IntPtr previous, IntPtr returnSize);
116
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern bool CreateProcessW(string application, StringBuilder commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandles, uint flags, IntPtr environment, string directory, ref StartupInfoEx startup, out ProcessInformation info);
117
+ [DllImport("kernel32.dll", SetLastError = true)] static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
118
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool GetExitCodeProcess(IntPtr process, out uint code);
119
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool CloseHandle(IntPtr handle);
120
+ [DllImport("kernel32.dll")] static extern bool SetConsoleCtrlHandler(ConsoleHandler handler, bool add);
121
+ [DllImport("kernel32.dll")] static extern void ExitProcess(uint code);
122
+
123
+ static IntPtr job = IntPtr.Zero;
124
+ // Kept alive for the process's life: the console holds only a pointer to it.
125
+ static ConsoleHandler handler = Handled;
126
+
127
+ static bool Handled(uint kind) { return true; }
128
+
129
+ public static void Run() {
130
+ AppDomain.CurrentDomain.UnhandledException += (sender, e) => ExitProcess(Internal);
131
+ try {
132
+ ExitProcess(Hold());
133
+ } catch {
134
+ ExitProcess(Internal);
135
+ }
136
+ }
137
+
138
+ static uint Hold() {
139
+ // A terminal's Ctrl+C reaches the command as before; the core decides the rest. A handler,
140
+ // not the inherited ignore flag, so the command keeps its own default.
141
+ SetConsoleCtrlHandler(handler, true);
142
+ job = CreateJobObjectW(IntPtr.Zero, IntPtr.Zero);
143
+ if (job == IntPtr.Zero) return Job;
144
+ ExtendedLimit limit = new ExtendedLimit();
145
+ // Never a breakaway bit: MSYS asks to break away whenever the immediate job allows any.
146
+ limit.Basic.LimitFlags = KillOnJobClose | DieOnUnhandledException;
147
+ if (!SetInformationJobObject(job, ExtendedLimitInformation, ref limit, Marshal.SizeOf(typeof(ExtendedLimit)))) return Job;
148
+ // Joined before anything starts, so a kill during start misses nothing.
149
+ if (!AssignProcessToJobObject(job, GetCurrentProcess())) return Job;
150
+
151
+ Stream input = Console.OpenStandardInput();
152
+ byte[] spec = ReadSpec(input);
153
+ if (spec == null) return Spec;
154
+ int offset = 0;
155
+ byte[] nonce = new byte[NonceBytes];
156
+ Array.Copy(spec, offset, nonce, 0, NonceBytes);
157
+ offset += NonceBytes;
158
+ bool hide = spec[offset++] == 1;
159
+ string launcher = ReadField(spec, ref offset);
160
+ string commandLine = ReadField(spec, ref offset);
161
+ string directory = ReadField(spec, ref offset);
162
+ string environment = ReadField(spec, ref offset);
163
+ if (launcher == null || commandLine == null || directory == null || environment == null || offset != spec.Length) return Spec;
164
+ if (!IsDrivePath(launcher) || !launcher.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) return Spec;
165
+ // The working directory as Node's own spawn took it: a drive path, or a network share.
166
+ if (!IsDrivePath(directory) && !IsUncPath(directory)) return Spec;
167
+ foreach (char c in commandLine) if (c == '\0') return Spec;
168
+ IntPtr block = EnvironmentBlock(environment);
169
+ if (block == IntPtr.Zero) return Spec;
170
+
171
+ IntPtr stdout = GetStdHandle(-11);
172
+ IntPtr stderr = GetStdHandle(-12);
173
+ IntPtr nul = CreateFileW("NUL", 0x80000000, 3, IntPtr.Zero, 3, 0, IntPtr.Zero);
174
+ if (nul == (IntPtr)(-1)) return Internal;
175
+ foreach (IntPtr h in new IntPtr[] { nul, stdout, stderr }) SetHandleInformation(h, HandleFlagInherit, HandleFlagInherit);
176
+
177
+ // Exactly three handles reach the command: NUL as its input, and its two output pipes.
178
+ IntPtr size = IntPtr.Zero;
179
+ InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref size);
180
+ IntPtr list = Marshal.AllocHGlobal(size);
181
+ if (!InitializeProcThreadAttributeList(list, 1, 0, ref size)) return Internal;
182
+ IntPtr handles = Marshal.AllocHGlobal(IntPtr.Size * 3);
183
+ Marshal.WriteIntPtr(handles, 0, nul);
184
+ Marshal.WriteIntPtr(handles, IntPtr.Size, stdout);
185
+ Marshal.WriteIntPtr(handles, IntPtr.Size * 2, stderr);
186
+ if (!UpdateProcThreadAttribute(list, 0, HandleListAttribute, handles, (IntPtr)(IntPtr.Size * 3), IntPtr.Zero, IntPtr.Zero)) return Internal;
187
+
188
+ StartupInfoEx startup = new StartupInfoEx();
189
+ startup.StartupInfo.cb = Marshal.SizeOf(typeof(StartupInfoEx));
190
+ startup.StartupInfo.dwFlags = UseStdHandles | (hide ? UseShowWindow : 0);
191
+ startup.StartupInfo.wShowWindow = 0;
192
+ startup.StartupInfo.hStdInput = nul;
193
+ startup.StartupInfo.hStdOutput = stdout;
194
+ startup.StartupInfo.hStdError = stderr;
195
+ startup.lpAttributeList = list;
196
+
197
+ // The ready markers, stdout then stderr: the stderr marker is the last write before the
198
+ // launcher exists, so the core knows a command never ran when it never arrives.
199
+ if (!WriteAll(stdout, nonce) || !WriteAll(stderr, nonce)) return Internal;
200
+
201
+ // A cancel that landed while the holder was starting: the lifeline has already reached end of
202
+ // file, or carries a byte, so the command never starts (the lifeline thread ends any later one).
203
+ int waiting;
204
+ if (!PeekNamedPipe(GetStdHandle(-10), IntPtr.Zero, 0, IntPtr.Zero, out waiting, IntPtr.Zero) || waiting > 0) return Internal;
205
+
206
+ ProcessInformation info;
207
+ // No console flags: the launcher shares the holder's console, windowless when hidden, so no
208
+ // console host joins the job and a terminal's Ctrl+C still reaches the command.
209
+ if (!CreateProcessW(launcher, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, true, ExtendedStartupInfoPresent | UnicodeEnvironment, block, directory, ref startup, out info)) {
210
+ // A launcher or working directory that does not exist reads as the shell's ENOENT, as
211
+ // Node's own spawn reported it; any other refusal as blocked.
212
+ int error = Marshal.GetLastWin32Error();
213
+ return error == 2 || error == 3 || error == 267 ? NotFound : Launch;
214
+ }
215
+ CloseHandle(info.hThread);
216
+
217
+ // The lifeline: any byte after the spec, or end of file, ends the job (the core wrote the
218
+ // spec and nothing else; it closes this pipe, or dies, to end the command).
219
+ Thread lifeline = new Thread(() => {
220
+ try {
221
+ byte[] one = new byte[1];
222
+ input.Read(one, 0, 1);
223
+ } catch {
224
+ }
225
+ // The holder is in the job too, so this ends it with H6 as well.
226
+ TerminateJobObject(job, Internal);
227
+ ExitProcess(Internal);
228
+ });
229
+ lifeline.IsBackground = true;
230
+ lifeline.Start();
231
+
232
+ WaitForSingleObject(info.hProcess, 0xFFFFFFFF);
233
+ uint code;
234
+ if (!GetExitCodeProcess(info.hProcess, out code)) return Internal;
235
+ // Closed before the count, so the launcher's place in the job never waits on this handle.
236
+ CloseHandle(info.hProcess);
237
+ Accounting accounting;
238
+ uint leftovers = 0;
239
+ if (QueryInformationJobObject(job, BasicAccountingInformation, out accounting, Marshal.SizeOf(typeof(Accounting)), IntPtr.Zero)) {
240
+ // The holder itself is in the job; everything else still in it, the command left.
241
+ leftovers = accounting.ActiveProcesses > 0 ? accounting.ActiveProcesses - 1 : 0;
242
+ }
243
+ byte[] trailer = new byte[NonceBytes * 2 + 8];
244
+ Array.Copy(nonce, 0, trailer, 0, NonceBytes);
245
+ Encoding.ASCII.GetBytes("END1").CopyTo(trailer, NonceBytes);
246
+ BitConverter.GetBytes(leftovers).CopyTo(trailer, NonceBytes + 4);
247
+ Array.Copy(nonce, 0, trailer, NonceBytes + 8, NonceBytes);
248
+ WriteAll(stdout, trailer);
249
+ // End the job outright rather than by its last handle closing, so a stray handle to it held
250
+ // elsewhere (a diagnostic tool, endpoint protection) cannot keep what the command left alive.
251
+ // The holder is in the job, so it ends here too, with the launcher's exact code.
252
+ TerminateJobObject(job, code);
253
+ return code;
254
+ }
255
+
256
+ static bool WriteAll(IntPtr handle, byte[] bytes) {
257
+ int written;
258
+ return WriteFile(handle, bytes, bytes.Length, out written, IntPtr.Zero) && written == bytes.Length;
259
+ }
260
+
261
+ // `JVH1`, the body's length, then the body; exactly that many bytes, so the rest of the input
262
+ // stays the lifeline.
263
+ static byte[] ReadSpec(Stream input) {
264
+ byte[] header = ReadExactly(input, 8);
265
+ if (header == null || header[0] != (byte)'J' || header[1] != (byte)'V' || header[2] != (byte)'H' || header[3] != (byte)'1') return null;
266
+ int length = BitConverter.ToInt32(header, 4);
267
+ if (length < NonceBytes + 1 || length > MaxSpec) return null;
268
+ return ReadExactly(input, length);
269
+ }
270
+
271
+ static byte[] ReadExactly(Stream input, int count) {
272
+ byte[] buffer = new byte[count];
273
+ int done = 0;
274
+ while (done < count) {
275
+ int read = input.Read(buffer, done, count - done);
276
+ if (read <= 0) return null;
277
+ done += read;
278
+ }
279
+ return buffer;
280
+ }
281
+
282
+ static string ReadField(byte[] spec, ref int offset) {
283
+ if (offset + 4 > spec.Length) return null;
284
+ int length = BitConverter.ToInt32(spec, offset);
285
+ offset += 4;
286
+ if (length < 0 || length % 2 != 0 || length > spec.Length - offset) return null;
287
+ string text = Encoding.Unicode.GetString(spec, offset, length);
288
+ offset += length;
289
+ return text;
290
+ }
291
+
292
+ static bool IsDrivePath(string path) {
293
+ if (path.Length < 3 || !char.IsLetter(path[0]) || path[1] != ':' || path[2] != '\\') return false;
294
+ return path.IndexOf('\0') < 0 && path.IndexOf('"') < 0;
295
+ }
296
+
297
+ // A network share (\\server\share\...), never a device or namespace path (\\.\ or \\?\).
298
+ static bool IsUncPath(string path) {
299
+ if (path.Length < 5 || path[0] != '\\' || path[1] != '\\') return false;
300
+ if (path[2] == '?' || path[2] == '.' || path[2] == '\\') return false;
301
+ int share = path.IndexOf('\\', 2) + 1;
302
+ if (share < 4 || share >= path.Length || path[share] == '\\') return false;
303
+ return path.IndexOf('\0') < 0 && path.IndexOf('"') < 0;
304
+ }
305
+
306
+ static string NameOf(string entry) {
307
+ return entry.Substring(0, entry.IndexOf('=', 1));
308
+ }
309
+
310
+ // The core's environment, exactly: `K=V` entries sorted by name ignoring case (libuv's order),
311
+ // each NUL-terminated, then a final NUL. The holder's own minimal environment never reaches
312
+ // the command.
313
+ static IntPtr EnvironmentBlock(string environment) {
314
+ string[] entries = environment.Length == 0 ? new string[0] : environment.Split('\0');
315
+ foreach (string entry in entries) if (entry.Length < 2 || entry.IndexOf('=', 1) < 1) return IntPtr.Zero;
316
+ Array.Sort(entries, (a, b) => {
317
+ int byName = string.Compare(NameOf(a), NameOf(b), StringComparison.OrdinalIgnoreCase);
318
+ return byName != 0 ? byName : string.CompareOrdinal(a, b);
319
+ });
320
+ StringBuilder block = new StringBuilder();
321
+ foreach (string entry in entries) {
322
+ block.Append(entry);
323
+ block.Append('\0');
324
+ }
325
+ block.Append('\0');
326
+ return Marshal.StringToHGlobalUni(block.ToString());
327
+ }
328
+ }
329
+ '@
330
+ try {
331
+ # Add-Type lives in this one module. Importing it by path, with auto-loading off (as the host's
332
+ # privacy helper does), spares a new profile the analysis of every installed module.
333
+ Import-Module "$PSHOME\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1"
334
+ Add-Type -TypeDefinition $code -Language CSharp
335
+ } catch {
336
+ exit 0x6A560002
337
+ }
338
+ [JarvisHolder]::Run()
339
+ exit 0x6A560006