@zixt/host 0.0.73 → 0.0.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1153 -1098
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,27 +11,27 @@ var __export = (target, all) => {
|
|
|
11
11
|
import { fstatSync as fstatSync2 } from "node:fs";
|
|
12
12
|
|
|
13
13
|
// src/supervisor.ts
|
|
14
|
-
import { spawn as
|
|
14
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
15
15
|
import { fstatSync } from "node:fs";
|
|
16
16
|
import {
|
|
17
17
|
access as access2,
|
|
18
|
-
lstat as
|
|
19
|
-
mkdir as
|
|
20
|
-
open as
|
|
21
|
-
readFile as
|
|
18
|
+
lstat as lstat5,
|
|
19
|
+
mkdir as mkdir4,
|
|
20
|
+
open as open4,
|
|
21
|
+
readFile as readFile6,
|
|
22
22
|
readlink,
|
|
23
|
-
readdir as
|
|
24
|
-
rename as
|
|
25
|
-
rm as
|
|
23
|
+
readdir as readdir3,
|
|
24
|
+
rename as rename3,
|
|
25
|
+
rm as rm4,
|
|
26
26
|
symlink
|
|
27
27
|
} from "node:fs/promises";
|
|
28
|
-
import { basename as basename3, dirname as
|
|
29
|
-
import { homedir } from "node:os";
|
|
28
|
+
import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute8, join as join7, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
|
|
29
|
+
import { homedir as homedir2 } from "node:os";
|
|
30
30
|
|
|
31
31
|
// package.json
|
|
32
32
|
var package_default = {
|
|
33
33
|
name: "@zixt/host",
|
|
34
|
-
version: "0.0.
|
|
34
|
+
version: "0.0.74",
|
|
35
35
|
type: "module",
|
|
36
36
|
exports: {
|
|
37
37
|
".": "./src/client.ts",
|
|
@@ -14665,6 +14665,12 @@ var ID_PREFIXES = {
|
|
|
14665
14665
|
managerUsage: "mgu",
|
|
14666
14666
|
/** One ordered Manager reply awaiting Slack delivery. */
|
|
14667
14667
|
managerSlackOutbox: "mso",
|
|
14668
|
+
/** One organization-owned virtual Manager email inbox. */
|
|
14669
|
+
managerEmailInbox: "mei",
|
|
14670
|
+
/** One admitted or quarantined inbound Manager email. */
|
|
14671
|
+
managerEmailEvent: "mee",
|
|
14672
|
+
/** One idempotent outbound Manager email. */
|
|
14673
|
+
managerEmailDelivery: "med",
|
|
14668
14674
|
/** One organization phone line that reaches its Manager. */
|
|
14669
14675
|
voiceLine: "vln",
|
|
14670
14676
|
/** One member-owned phone number admitted to Manager calls. */
|
|
@@ -14706,6 +14712,18 @@ var ManagerIntegrationActionId = idSchema(
|
|
|
14706
14712
|
ID_PREFIXES.managerIntegrationAction,
|
|
14707
14713
|
"Manager integration action id"
|
|
14708
14714
|
);
|
|
14715
|
+
var ManagerEmailInboxId = idSchema(
|
|
14716
|
+
ID_PREFIXES.managerEmailInbox,
|
|
14717
|
+
"Manager email inbox id"
|
|
14718
|
+
);
|
|
14719
|
+
var ManagerEmailEventId = idSchema(
|
|
14720
|
+
ID_PREFIXES.managerEmailEvent,
|
|
14721
|
+
"Manager email event id"
|
|
14722
|
+
);
|
|
14723
|
+
var ManagerEmailDeliveryId = idSchema(
|
|
14724
|
+
ID_PREFIXES.managerEmailDelivery,
|
|
14725
|
+
"Manager email delivery id"
|
|
14726
|
+
);
|
|
14709
14727
|
var VoiceLineId = idSchema(ID_PREFIXES.voiceLine, "voice line id");
|
|
14710
14728
|
var VoiceCallerId = idSchema(ID_PREFIXES.voiceCaller, "voice caller id");
|
|
14711
14729
|
var VoiceCallId = idSchema(ID_PREFIXES.voiceCall, "voice call id");
|
|
@@ -18232,7 +18250,33 @@ var ListManagerMemoriesResponse = external_exports.object({
|
|
|
18232
18250
|
memories: external_exports.array(ManagerMemoryProjection).max(MANAGER_MEMORY_MAX_ITEMS),
|
|
18233
18251
|
limit: external_exports.number().int().min(1)
|
|
18234
18252
|
}).strict();
|
|
18235
|
-
var ConversationChannel = external_exports.enum(["web", "slack", "whatsapp", "phone"]);
|
|
18253
|
+
var ConversationChannel = external_exports.enum(["web", "slack", "whatsapp", "phone", "email"]);
|
|
18254
|
+
var MANAGER_EMAIL_ALLOWLIST_MAX = 200;
|
|
18255
|
+
var MANAGER_EMAIL_QUARANTINE_LIMIT = 25;
|
|
18256
|
+
var ManagerEmailAddress = external_exports.string().email().max(320);
|
|
18257
|
+
var ManagerEmailDomain = external_exports.string().min(3).max(253).regex(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i);
|
|
18258
|
+
var ManagerEmailQuarantineProjection = external_exports.object({
|
|
18259
|
+
id: external_exports.string().min(1),
|
|
18260
|
+
sender: ManagerEmailAddress,
|
|
18261
|
+
subject: external_exports.string().max(300),
|
|
18262
|
+
reason: external_exports.enum(["authentication_failed", "not_approved", "automatic_message"]),
|
|
18263
|
+
receivedAt: external_exports.string()
|
|
18264
|
+
}).strict();
|
|
18265
|
+
var ManagerEmailSettingsResponse = external_exports.object({
|
|
18266
|
+
address: ManagerEmailAddress,
|
|
18267
|
+
status: external_exports.enum(["ready", "not_configured"]),
|
|
18268
|
+
memberAddressesApprovedAutomatically: external_exports.literal(true),
|
|
18269
|
+
allowedAddresses: external_exports.array(ManagerEmailAddress).max(MANAGER_EMAIL_ALLOWLIST_MAX),
|
|
18270
|
+
allowedDomains: external_exports.array(ManagerEmailDomain).max(MANAGER_EMAIL_ALLOWLIST_MAX),
|
|
18271
|
+
quarantine: external_exports.array(ManagerEmailQuarantineProjection).max(MANAGER_EMAIL_QUARANTINE_LIMIT),
|
|
18272
|
+
revision: external_exports.number().int().min(0)
|
|
18273
|
+
}).strict();
|
|
18274
|
+
var UpdateManagerEmailSettingsRequest = external_exports.object({
|
|
18275
|
+
allowedAddresses: external_exports.array(ManagerEmailAddress).max(MANAGER_EMAIL_ALLOWLIST_MAX),
|
|
18276
|
+
allowedDomains: external_exports.array(ManagerEmailDomain).max(MANAGER_EMAIL_ALLOWLIST_MAX),
|
|
18277
|
+
expectedRevision: external_exports.number().int().min(0)
|
|
18278
|
+
}).strict();
|
|
18279
|
+
var DeleteManagerEmailQuarantineResponse = external_exports.object({ discarded: external_exports.literal(true) }).strict();
|
|
18236
18280
|
var ConversationStatus = external_exports.enum(["idle", "thinking"]);
|
|
18237
18281
|
var ConversationProjection = external_exports.object({
|
|
18238
18282
|
id: ConversationId,
|
|
@@ -23994,6 +24038,23 @@ function createReleaseStateStore(root) {
|
|
|
23994
24038
|
};
|
|
23995
24039
|
}
|
|
23996
24040
|
|
|
24041
|
+
// src/runners/run-artifacts.ts
|
|
24042
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
24043
|
+
import {
|
|
24044
|
+
chmod as chmod2,
|
|
24045
|
+
lstat as lstat4,
|
|
24046
|
+
mkdir as mkdir3,
|
|
24047
|
+
open as open3,
|
|
24048
|
+
readdir as readdir2,
|
|
24049
|
+
readFile as readFile5,
|
|
24050
|
+
realpath as realpath3,
|
|
24051
|
+
rename as rename2,
|
|
24052
|
+
rm as rm3,
|
|
24053
|
+
writeFile
|
|
24054
|
+
} from "node:fs/promises";
|
|
24055
|
+
import { homedir } from "node:os";
|
|
24056
|
+
import { dirname as dirname3, isAbsolute as isAbsolute7, join as join6, relative as relative3, resolve as resolve4, sep as sep3, win32 as win322 } from "node:path";
|
|
24057
|
+
|
|
23997
24058
|
// src/windows-job.ts
|
|
23998
24059
|
import { spawn as spawn3 } from "node:child_process";
|
|
23999
24060
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -24514,257 +24575,908 @@ async function awaitWindowsContainmentGate(env = process.env, input = process.st
|
|
|
24514
24575
|
});
|
|
24515
24576
|
}
|
|
24516
24577
|
|
|
24517
|
-
// src/
|
|
24518
|
-
var
|
|
24519
|
-
var
|
|
24520
|
-
var
|
|
24521
|
-
var WORKER_PROXY_ROLE = "worker_proxy";
|
|
24522
|
-
var SUPERVISOR_ROLE = "supervisor";
|
|
24523
|
-
var WORKER_PROXY_ENTRY_ENV = "ZIXT_HOST_WORKER_PROXY_ENTRY";
|
|
24524
|
-
var SUPERVISOR_PROTOCOL_ENV = "ZIXT_HOST_SUPERVISOR_PROTOCOL";
|
|
24525
|
-
var SUPERVISOR_VERSION_ENV = "ZIXT_HOST_SUPERVISOR_VERSION";
|
|
24526
|
-
var SUPERVISOR_PROTOCOL = "2";
|
|
24527
|
-
var SUPERVISOR_RELOAD_EXIT_CODE = 76;
|
|
24528
|
-
var DO_NOT_RESTART_EXIT_CODE = 64;
|
|
24529
|
-
var RESTART_BACKOFF_MS = 2e3;
|
|
24530
|
-
var MAX_RESTART_BACKOFF_MS = 6e4;
|
|
24531
|
-
var CRASH_STREAK_RESET_MS = 6e4;
|
|
24532
|
-
var RELEASE_PROBATION_MS = 5 * 6e4;
|
|
24533
|
-
var FAST_UPDATE_EXIT_MS = 1e4;
|
|
24534
|
-
var STOP_GRACE_MS = 3e4;
|
|
24535
|
-
var INSTALL_TIMEOUT_MS = 5 * 6e4;
|
|
24536
|
-
var WORKER_WATCHDOG_CHECK_MS = 1e3;
|
|
24537
|
-
var WORKER_WATCHDOG_TIMEOUT_MS = 15e3;
|
|
24538
|
-
var WORKER_WATCHDOG_STARTUP_MS = 6e4;
|
|
24539
|
-
var WORKER_READINESS_TIMEOUT_MS = 9e4;
|
|
24578
|
+
// src/runners/run-artifacts.ts
|
|
24579
|
+
var SAFE_SEGMENT = /^[A-Za-z0-9_-]{1,200}$/;
|
|
24580
|
+
var DIRECTORY_MODE = 448;
|
|
24581
|
+
var FILE_MODE = 384;
|
|
24540
24582
|
var CLEANUP_ATTEMPTS = 3;
|
|
24541
|
-
var
|
|
24542
|
-
var
|
|
24543
|
-
var
|
|
24544
|
-
|
|
24545
|
-
|
|
24546
|
-
|
|
24547
|
-
|
|
24583
|
+
var CLEANUP_RETRY_DELAY_MS = 50;
|
|
24584
|
+
var WINDOWS_ACL_TIMEOUT_MS = 5e3;
|
|
24585
|
+
var RUNNER_GUARDIAN = String.raw`
|
|
24586
|
+
const { spawn } = require('node:child_process');
|
|
24587
|
+
|
|
24588
|
+
const quote = (value) => '"' + String(value).replace(/\\/g, '/').replaceAll('"', '""') + '"';
|
|
24589
|
+
const launchMarker = '__ZIXT_RELEASE_RUNNER__';
|
|
24590
|
+
const exitMarker = process.argv[2];
|
|
24591
|
+
const guardianNonce = process.argv[3];
|
|
24592
|
+
if (!/^[A-Za-z0-9_-]{16,200}$/.test(guardianNonce || '')) process.exit(1);
|
|
24593
|
+
const containmentNonce = process.env[${JSON.stringify(WINDOWS_CONTAINMENT_GATE_ENV)}];
|
|
24594
|
+
const containmentMarker = ${JSON.stringify(WINDOWS_CONTAINMENT_GATE_PREFIX)};
|
|
24595
|
+
let containmentReady = !containmentNonce;
|
|
24596
|
+
const maxReleaseBytes = 32 * 1024 * 1024;
|
|
24597
|
+
let target = null;
|
|
24598
|
+
let pending = Buffer.alloc(0);
|
|
24599
|
+
let stdinEnded = false;
|
|
24600
|
+
let expectedBytes = null;
|
|
24601
|
+
let config = null;
|
|
24602
|
+
let launchPending = false;
|
|
24603
|
+
let stderrTail = '';
|
|
24604
|
+
let postReleaseInput = Buffer.alloc(0);
|
|
24605
|
+
let reported = false;
|
|
24606
|
+
const report = (result) => {
|
|
24607
|
+
if (reported) return;
|
|
24608
|
+
reported = true;
|
|
24609
|
+
process.stdout.write(
|
|
24610
|
+
'\n' + exitMarker + JSON.stringify({ ...result, stderrTail }) + '\n'
|
|
24611
|
+
);
|
|
24612
|
+
setInterval(() => {}, 2_147_483_647);
|
|
24613
|
+
};
|
|
24614
|
+
|
|
24615
|
+
// Job helper setup + assignment each have a bounded Windows phase. This gate
|
|
24616
|
+
// must outlive both, including slow first-run Add-Type under endpoint protection.
|
|
24617
|
+
const prelaunchTimeout = setTimeout(() => process.exit(1), 130_000);
|
|
24618
|
+
prelaunchTimeout.unref();
|
|
24619
|
+
|
|
24620
|
+
const launchTarget = (release) => {
|
|
24548
24621
|
try {
|
|
24549
|
-
|
|
24550
|
-
|
|
24622
|
+
const command = config.comspec || config.command;
|
|
24623
|
+
const args = config.comspec
|
|
24624
|
+
? ['/d', '/s', '/c', '"' + [config.command, ...config.args].map(quote).join(' ') + '"']
|
|
24625
|
+
: config.args;
|
|
24626
|
+
target = spawn(command, args, {
|
|
24627
|
+
cwd: config.cwd,
|
|
24628
|
+
env: config.env,
|
|
24629
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
24630
|
+
windowsHide: true,
|
|
24631
|
+
windowsVerbatimArguments: Boolean(config.comspec),
|
|
24632
|
+
});
|
|
24633
|
+
launchPending = false;
|
|
24551
24634
|
} catch {
|
|
24635
|
+
process.exit(1);
|
|
24636
|
+
return;
|
|
24552
24637
|
}
|
|
24553
|
-
|
|
24554
|
-
|
|
24555
|
-
|
|
24556
|
-
|
|
24557
|
-
|
|
24558
|
-
|
|
24559
|
-
}
|
|
24560
|
-
|
|
24561
|
-
|
|
24562
|
-
|
|
24563
|
-
|
|
24564
|
-
|
|
24565
|
-
|
|
24566
|
-
|
|
24567
|
-
|
|
24568
|
-
}
|
|
24569
|
-
|
|
24570
|
-
|
|
24571
|
-
|
|
24572
|
-
|
|
24638
|
+
|
|
24639
|
+
target.stdin.on('error', () => {});
|
|
24640
|
+
target.stdout.pipe(process.stdout, { end: false });
|
|
24641
|
+
target.stderr.on('data', (chunk) => {
|
|
24642
|
+
process.stderr.write(chunk);
|
|
24643
|
+
stderrTail = (stderrTail + String(chunk)).slice(-32_768);
|
|
24644
|
+
});
|
|
24645
|
+
let targetResult;
|
|
24646
|
+
let stdoutEnded = false;
|
|
24647
|
+
const maybeReport = () => {
|
|
24648
|
+
if (targetResult && stdoutEnded) report(targetResult);
|
|
24649
|
+
};
|
|
24650
|
+
target.stdout.once('end', () => {
|
|
24651
|
+
stdoutEnded = true;
|
|
24652
|
+
maybeReport();
|
|
24653
|
+
});
|
|
24654
|
+
target.stdout.once('close', () => {
|
|
24655
|
+
stdoutEnded = true;
|
|
24656
|
+
maybeReport();
|
|
24657
|
+
});
|
|
24658
|
+
target.once('error', () => {
|
|
24659
|
+
targetResult = { kind: 'spawn_error' };
|
|
24660
|
+
stdoutEnded = true;
|
|
24661
|
+
maybeReport();
|
|
24662
|
+
});
|
|
24663
|
+
target.once('close', (code, signal) => {
|
|
24664
|
+
targetResult = { kind: 'close', code, signal };
|
|
24665
|
+
maybeReport();
|
|
24666
|
+
});
|
|
24667
|
+
if (config.prompt) target.stdin.write(config.prompt);
|
|
24668
|
+
if (postReleaseInput.length) {
|
|
24669
|
+
target.stdin.write(postReleaseInput);
|
|
24670
|
+
postReleaseInput = Buffer.alloc(0);
|
|
24671
|
+
}
|
|
24672
|
+
if (stdinEnded) target.stdin.end();
|
|
24673
|
+
};
|
|
24674
|
+
|
|
24675
|
+
const launch = (release) => {
|
|
24676
|
+
clearTimeout(prelaunchTimeout);
|
|
24677
|
+
config = release;
|
|
24678
|
+
launchPending = true;
|
|
24679
|
+
if (process.platform === 'win32') {
|
|
24680
|
+
launchTarget(release);
|
|
24681
|
+
return;
|
|
24682
|
+
}
|
|
24683
|
+
|
|
24684
|
+
// A detached POSIX group's numeric id outlives its leader, but without a
|
|
24685
|
+
// live identity witness a stale registry entry cannot distinguish that
|
|
24686
|
+
// inherited group from the same PGID being recycled later. This
|
|
24687
|
+
// credential-free sentinel inherits the guardian's exact group and carries
|
|
24688
|
+
// only its high-entropy nonce in argv. It deliberately survives SIGTERM so
|
|
24689
|
+
// checked recovery can keep the PGID reserved through the graceful pass and
|
|
24690
|
+
// validate the nonce again immediately before a group-wide SIGKILL.
|
|
24691
|
+
const identitySentinel = spawn(
|
|
24692
|
+
process.execPath,
|
|
24693
|
+
[
|
|
24694
|
+
'-e',
|
|
24695
|
+
"process.on('SIGTERM', () => {}); setInterval(() => {}, 2147483647);",
|
|
24696
|
+
guardianNonce,
|
|
24697
|
+
],
|
|
24698
|
+
{ env: process.env, stdio: 'ignore' },
|
|
24699
|
+
);
|
|
24700
|
+
identitySentinel.once('error', () => process.exit(1));
|
|
24701
|
+
identitySentinel.once('spawn', () => launchTarget(release));
|
|
24702
|
+
identitySentinel.once('exit', () => {
|
|
24703
|
+
if (!reported) process.exit(1);
|
|
24704
|
+
});
|
|
24705
|
+
};
|
|
24706
|
+
|
|
24707
|
+
process.stdin.on('data', (chunk) => {
|
|
24708
|
+
if (target) {
|
|
24709
|
+
target.stdin.write(chunk);
|
|
24710
|
+
return;
|
|
24711
|
+
}
|
|
24712
|
+
if (config) {
|
|
24713
|
+
postReleaseInput = Buffer.concat([postReleaseInput, chunk]);
|
|
24714
|
+
if (postReleaseInput.length > maxReleaseBytes) process.exit(1);
|
|
24715
|
+
return;
|
|
24716
|
+
}
|
|
24717
|
+
pending = Buffer.concat([pending, chunk]);
|
|
24718
|
+
if (pending.length > maxReleaseBytes + 256) process.exit(1);
|
|
24719
|
+
if (!containmentReady) {
|
|
24720
|
+
const gateNewline = pending.indexOf(10);
|
|
24721
|
+
if (gateNewline < 0) return;
|
|
24722
|
+
const gate = pending.subarray(0, gateNewline).toString().replace(/\r$/, '');
|
|
24723
|
+
if (gate !== containmentMarker + ' ' + containmentNonce) process.exit(1);
|
|
24724
|
+
containmentReady = true;
|
|
24725
|
+
pending = pending.subarray(gateNewline + 1);
|
|
24726
|
+
}
|
|
24727
|
+
if (expectedBytes === null) {
|
|
24728
|
+
const newline = pending.indexOf(10);
|
|
24729
|
+
if (newline < 0) return;
|
|
24730
|
+
const header = pending.subarray(0, newline).toString();
|
|
24731
|
+
const match = new RegExp('^' + launchMarker + ' ([1-9][0-9]{0,8})$').exec(header);
|
|
24732
|
+
if (!match) process.exit(1);
|
|
24733
|
+
expectedBytes = Number(match[1]);
|
|
24734
|
+
if (expectedBytes > maxReleaseBytes) process.exit(1);
|
|
24735
|
+
pending = pending.subarray(newline + 1);
|
|
24736
|
+
}
|
|
24737
|
+
if (pending.length < expectedBytes) return;
|
|
24573
24738
|
try {
|
|
24574
|
-
const
|
|
24575
|
-
|
|
24576
|
-
|
|
24577
|
-
|
|
24578
|
-
if (!entry.isFile()) return false;
|
|
24579
|
-
const manifest = JSON.parse(manifestText);
|
|
24580
|
-
return manifest.name === PACKAGE_NAME && manifest.version === version2;
|
|
24739
|
+
const release = JSON.parse(pending.subarray(0, expectedBytes).toString('utf8'));
|
|
24740
|
+
postReleaseInput = pending.subarray(expectedBytes);
|
|
24741
|
+
pending = Buffer.alloc(0);
|
|
24742
|
+
launch(release);
|
|
24581
24743
|
} catch {
|
|
24582
|
-
|
|
24744
|
+
process.exit(1);
|
|
24583
24745
|
}
|
|
24584
|
-
}
|
|
24585
|
-
|
|
24586
|
-
|
|
24587
|
-
|
|
24588
|
-
|
|
24589
|
-
|
|
24590
|
-
|
|
24591
|
-
|
|
24746
|
+
});
|
|
24747
|
+
process.stdin.on('end', () => {
|
|
24748
|
+
stdinEnded = true;
|
|
24749
|
+
if (!containmentReady) process.exit(1);
|
|
24750
|
+
if (target) target.stdin.end();
|
|
24751
|
+
else if (!launchPending) process.exit(1);
|
|
24752
|
+
});
|
|
24753
|
+
process.stdin.on('error', () => process.exit(1));
|
|
24754
|
+
`;
|
|
24755
|
+
var WINDOWS_PRIVATE_DACL_SCRIPT = String.raw`
|
|
24756
|
+
$ErrorActionPreference = 'Stop'
|
|
24757
|
+
$paths = ConvertFrom-Json $env:ZIXT_RUN_ARTIFACT_PATHS
|
|
24758
|
+
$current = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
24759
|
+
$system = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-18')
|
|
24760
|
+
$admins = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
|
|
24761
|
+
$allowed = @($current.Value, $system.Value, $admins.Value)
|
|
24762
|
+
foreach ($path in $paths) {
|
|
24763
|
+
$acl = [System.Security.AccessControl.DirectorySecurity]::new()
|
|
24764
|
+
$acl.SetAccessRuleProtection($true, $false)
|
|
24765
|
+
foreach ($sid in @($current, $system, $admins)) {
|
|
24766
|
+
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
|
|
24767
|
+
$sid,
|
|
24768
|
+
[System.Security.AccessControl.FileSystemRights]::FullControl,
|
|
24769
|
+
[System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit',
|
|
24770
|
+
[System.Security.AccessControl.PropagationFlags]::None,
|
|
24771
|
+
[System.Security.AccessControl.AccessControlType]::Allow
|
|
24772
|
+
)
|
|
24773
|
+
[void]$acl.AddAccessRule($rule)
|
|
24774
|
+
}
|
|
24775
|
+
$entry = [System.IO.DirectoryInfo]::new($path)
|
|
24776
|
+
$entry.SetAccessControl($acl)
|
|
24777
|
+
$check = $entry.GetAccessControl()
|
|
24778
|
+
if (-not $check.AreAccessRulesProtected) { throw 'DACL inheritance remained enabled' }
|
|
24779
|
+
$seen = @{}
|
|
24780
|
+
foreach ($rule in $check.Access) {
|
|
24781
|
+
$sid = $rule.IdentityReference.Translate(
|
|
24782
|
+
[System.Security.Principal.SecurityIdentifier]
|
|
24783
|
+
).Value
|
|
24784
|
+
if ($rule.AccessControlType -ne 'Allow' -or $allowed -notcontains $sid) {
|
|
24785
|
+
throw 'Unexpected DACL entry'
|
|
24786
|
+
}
|
|
24787
|
+
$seen[$sid] = $true
|
|
24788
|
+
}
|
|
24789
|
+
foreach ($sid in $allowed) {
|
|
24790
|
+
if (-not $seen.ContainsKey($sid)) { throw 'Required DACL entry is missing' }
|
|
24592
24791
|
}
|
|
24593
24792
|
}
|
|
24594
|
-
|
|
24595
|
-
|
|
24596
|
-
|
|
24597
|
-
|
|
24598
|
-
|
|
24793
|
+
`;
|
|
24794
|
+
function defaultRunArtifactRoot() {
|
|
24795
|
+
return join6(homedir(), ".zixt", "run-artifacts");
|
|
24796
|
+
}
|
|
24797
|
+
function requireSafeSegment(value, field) {
|
|
24798
|
+
if (!SAFE_SEGMENT.test(value)) {
|
|
24799
|
+
throw new Error(`${field} must be a safe path segment`);
|
|
24599
24800
|
}
|
|
24600
|
-
const version2 = relativeEntry.split(sep3)[0];
|
|
24601
|
-
if (!version2 || !VERSION_DIR.test(version2)) return null;
|
|
24602
|
-
return resolve4(entry) === resolve4(installedReleaseEntry(version2, root)) ? version2 : null;
|
|
24603
24801
|
}
|
|
24604
|
-
function
|
|
24605
|
-
return
|
|
24802
|
+
function isMissing(error52) {
|
|
24803
|
+
return error52.code === "ENOENT";
|
|
24606
24804
|
}
|
|
24607
|
-
function
|
|
24608
|
-
|
|
24805
|
+
function assertBelow(parent, child) {
|
|
24806
|
+
const path = relative3(parent, child);
|
|
24807
|
+
const escapes = path === "" || path === ".." || path.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute7(path);
|
|
24808
|
+
if (escapes) throw new Error("run artifact path escapes its private root");
|
|
24609
24809
|
}
|
|
24610
|
-
|
|
24611
|
-
|
|
24612
|
-
|
|
24613
|
-
|
|
24614
|
-
|
|
24615
|
-
const pointer = JSON.parse(readFileSync(join(dirname(__filename), '${WINDOWS_RELEASE_POINTER}'), 'utf8'));
|
|
24616
|
-
if (pointer.schema !== 1 || typeof pointer.entry !== 'string' || !isAbsolute(pointer.entry)) process.exit(64);
|
|
24617
|
-
const child = spawn(process.execPath, [pointer.entry, ...process.argv.slice(2)], {
|
|
24618
|
-
// This wrapper owns the only write end of the installed launcher's stdin.
|
|
24619
|
-
// If Task Scheduler/the wrapper dies, EOF reaches launchHostSupervisor and
|
|
24620
|
-
// triggers checked supervisor/worker cleanup before a scheduled restart.
|
|
24621
|
-
stdio: ['pipe', 'inherit', 'inherit'],
|
|
24622
|
-
windowsHide: true,
|
|
24623
|
-
});
|
|
24624
|
-
child.stdin.on('error', () => {});
|
|
24625
|
-
let stopping = false;
|
|
24626
|
-
const closeChildInput = () => {
|
|
24627
|
-
if (stopping) return;
|
|
24628
|
-
stopping = true;
|
|
24629
|
-
try { child.stdin.end(); } catch {}
|
|
24630
|
-
};
|
|
24631
|
-
process.stdin.on('data', (chunk) => {
|
|
24632
|
-
if (!stopping && child.stdin.writable) child.stdin.write(chunk);
|
|
24633
|
-
});
|
|
24634
|
-
process.stdin.on('end', closeChildInput);
|
|
24635
|
-
process.stdin.on('error', closeChildInput);
|
|
24636
|
-
process.stdin.resume();
|
|
24637
|
-
process.on('SIGINT', closeChildInput);
|
|
24638
|
-
process.on('SIGTERM', closeChildInput);
|
|
24639
|
-
child.once('error', () => process.exit(1));
|
|
24640
|
-
child.once('exit', (code) => process.exit(code == null ? 1 : code));
|
|
24641
|
-
`;
|
|
24642
|
-
async function replaceDurableFile(path, contents, sync = syncDirectory3) {
|
|
24643
|
-
const parent = dirname3(path);
|
|
24644
|
-
await mkdir3(parent, { recursive: true, mode: 448 });
|
|
24645
|
-
const temporary = join6(parent, `.${CURRENT_RELEASE_ENTRY}.${process.pid}.${crypto.randomUUID()}`);
|
|
24646
|
-
const handle = await open3(temporary, "wx", 384);
|
|
24647
|
-
try {
|
|
24648
|
-
await handle.writeFile(contents, "utf8");
|
|
24649
|
-
await handle.sync();
|
|
24650
|
-
await handle.close();
|
|
24651
|
-
await rename2(temporary, path);
|
|
24652
|
-
await sync(parent);
|
|
24653
|
-
} catch (error52) {
|
|
24654
|
-
await handle.close().catch(() => void 0);
|
|
24655
|
-
await rm3(temporary, { force: true }).catch(() => void 0);
|
|
24656
|
-
throw error52;
|
|
24657
|
-
}
|
|
24810
|
+
async function requireRealDirectory(path, label) {
|
|
24811
|
+
const entry = await lstat4(path);
|
|
24812
|
+
if (entry.isSymbolicLink()) throw new Error(`${label} must not be a symbolic link`);
|
|
24813
|
+
if (!entry.isDirectory()) throw new Error(`${label} must be a directory`);
|
|
24814
|
+
return realpath3(path);
|
|
24658
24815
|
}
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24816
|
+
function assertWindowsProfileBoundary(profile, target) {
|
|
24817
|
+
const path = win322.relative(win322.resolve(profile), win322.resolve(target));
|
|
24818
|
+
if (path === "" || path === ".." || path.startsWith("..\\") || win322.isAbsolute(path)) {
|
|
24819
|
+
throw new Error("run artifact root must be inside the current Windows user profile");
|
|
24662
24820
|
}
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
|
|
24670
|
-
|
|
24671
|
-
|
|
24672
|
-
|
|
24673
|
-
throw new Error("the Zixt Host Windows launcher is not a regular file");
|
|
24674
|
-
}
|
|
24675
|
-
const contents = await readFile5(launcher, "utf8");
|
|
24676
|
-
if (!contents.startsWith(WINDOWS_LAUNCHER_OWNED_MARKER)) {
|
|
24677
|
-
throw new Error("the Zixt Host Windows launcher is not owned by Zixt");
|
|
24678
|
-
}
|
|
24679
|
-
if (contents !== WINDOWS_STABLE_LAUNCHER) {
|
|
24680
|
-
await replaceDurableFile(launcher, WINDOWS_STABLE_LAUNCHER, sync);
|
|
24821
|
+
}
|
|
24822
|
+
async function rejectWindowsSymlinkAncestors(profile, target) {
|
|
24823
|
+
const path = win322.relative(win322.resolve(profile), win322.resolve(target));
|
|
24824
|
+
let current = win322.resolve(profile);
|
|
24825
|
+
for (const segment of path.split("\\").filter(Boolean)) {
|
|
24826
|
+
current = win322.join(current, segment);
|
|
24827
|
+
try {
|
|
24828
|
+
const entry = await lstat4(current);
|
|
24829
|
+
if (entry.isSymbolicLink()) {
|
|
24830
|
+
throw new Error("run artifact path must not contain symbolic links or junctions");
|
|
24681
24831
|
}
|
|
24682
|
-
}
|
|
24683
|
-
|
|
24684
|
-
}
|
|
24685
|
-
const pointerPath = windowsReleasePointer(root);
|
|
24686
|
-
const existingPointer = await lstat4(pointerPath).catch((error52) => {
|
|
24687
|
-
if (error52.code === "ENOENT") return null;
|
|
24832
|
+
} catch (error52) {
|
|
24833
|
+
if (isMissing(error52)) return;
|
|
24688
24834
|
throw error52;
|
|
24689
|
-
});
|
|
24690
|
-
if (existingPointer) {
|
|
24691
|
-
if (!existingPointer.isFile() || existingPointer.size > 4 * 1024) {
|
|
24692
|
-
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
24693
|
-
}
|
|
24694
|
-
let prior;
|
|
24695
|
-
try {
|
|
24696
|
-
prior = JSON.parse(await readFile5(pointerPath, "utf8"));
|
|
24697
|
-
} catch {
|
|
24698
|
-
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
24699
|
-
}
|
|
24700
|
-
const candidate = prior;
|
|
24701
|
-
if (!prior || typeof prior !== "object" || candidate.schema !== 1 || typeof candidate.entry !== "string" || installedReleaseVersion(candidate.entry, root) === null) {
|
|
24702
|
-
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
24703
|
-
}
|
|
24704
24835
|
}
|
|
24705
|
-
await replaceDurableFile(
|
|
24706
|
-
pointerPath,
|
|
24707
|
-
`${JSON.stringify({ schema: 1, entry: resolve4(entry) })}
|
|
24708
|
-
`,
|
|
24709
|
-
sync
|
|
24710
|
-
);
|
|
24711
|
-
return launcher;
|
|
24712
24836
|
}
|
|
24713
|
-
|
|
24714
|
-
|
|
24715
|
-
const
|
|
24716
|
-
|
|
24717
|
-
|
|
24718
|
-
|
|
24719
|
-
|
|
24720
|
-
|
|
24721
|
-
|
|
24837
|
+
}
|
|
24838
|
+
async function prepareRoot(root) {
|
|
24839
|
+
const absolute = resolve4(root);
|
|
24840
|
+
let realProfile;
|
|
24841
|
+
if (process.platform === "win32") {
|
|
24842
|
+
const profile = resolve4(homedir());
|
|
24843
|
+
assertWindowsProfileBoundary(profile, absolute);
|
|
24844
|
+
await rejectWindowsSymlinkAncestors(profile, absolute);
|
|
24845
|
+
realProfile = await realpath3(profile);
|
|
24722
24846
|
}
|
|
24723
|
-
const temporary = join6(root, `.${CURRENT_RELEASE_ENTRY}.${process.pid}.${crypto.randomUUID()}`);
|
|
24724
24847
|
try {
|
|
24725
|
-
await
|
|
24726
|
-
await rename2(temporary, current);
|
|
24727
|
-
await sync(root);
|
|
24848
|
+
await lstat4(absolute);
|
|
24728
24849
|
} catch (error52) {
|
|
24729
|
-
|
|
24730
|
-
|
|
24850
|
+
if (!isMissing(error52)) throw error52;
|
|
24851
|
+
await mkdir3(absolute, { recursive: true, mode: DIRECTORY_MODE });
|
|
24731
24852
|
}
|
|
24732
|
-
|
|
24853
|
+
const real = await requireRealDirectory(absolute, "run artifact root");
|
|
24854
|
+
if (realProfile) assertWindowsProfileBoundary(realProfile, real);
|
|
24855
|
+
await chmod2(real, DIRECTORY_MODE);
|
|
24856
|
+
return real;
|
|
24733
24857
|
}
|
|
24734
|
-
async function
|
|
24735
|
-
|
|
24858
|
+
async function prepareAgentRoot(root, agentId) {
|
|
24859
|
+
const path = join6(root, agentId);
|
|
24860
|
+
assertBelow(root, path);
|
|
24861
|
+
try {
|
|
24862
|
+
await lstat4(path);
|
|
24863
|
+
} catch (error52) {
|
|
24864
|
+
if (!isMissing(error52)) throw error52;
|
|
24736
24865
|
try {
|
|
24737
|
-
|
|
24738
|
-
|
|
24739
|
-
if (
|
|
24740
|
-
const value = JSON.parse(await readFile5(pointerPath, "utf8"));
|
|
24741
|
-
return value.schema === 1 && typeof value.entry === "string" ? installedReleaseVersion(value.entry, root) : null;
|
|
24742
|
-
} catch {
|
|
24743
|
-
return null;
|
|
24866
|
+
await mkdir3(path, { mode: DIRECTORY_MODE });
|
|
24867
|
+
} catch (mkdirError) {
|
|
24868
|
+
if (mkdirError.code !== "EEXIST") throw mkdirError;
|
|
24744
24869
|
}
|
|
24745
24870
|
}
|
|
24746
|
-
|
|
24747
|
-
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24871
|
+
const real = await requireRealDirectory(path, "run artifact Agent directory");
|
|
24872
|
+
assertBelow(root, real);
|
|
24873
|
+
await chmod2(real, DIRECTORY_MODE);
|
|
24874
|
+
return real;
|
|
24875
|
+
}
|
|
24876
|
+
async function lockDownWindowsDirectories(paths) {
|
|
24877
|
+
if (process.platform !== "win32") return;
|
|
24878
|
+
const windowsRoot = process.env.SystemRoot ?? process.env.WINDIR;
|
|
24879
|
+
if (!windowsRoot || !win322.isAbsolute(windowsRoot)) {
|
|
24880
|
+
throw new Error("private Windows run-artifact ACL authority is unavailable");
|
|
24753
24881
|
}
|
|
24882
|
+
const powershell = join6(windowsRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
24883
|
+
const encoded = Buffer.from(WINDOWS_PRIVATE_DACL_SCRIPT, "utf16le").toString("base64");
|
|
24884
|
+
await new Promise((resolvePromise, reject3) => {
|
|
24885
|
+
const helper = spawn4(
|
|
24886
|
+
powershell,
|
|
24887
|
+
["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded],
|
|
24888
|
+
{
|
|
24889
|
+
env: {
|
|
24890
|
+
SystemRoot: windowsRoot,
|
|
24891
|
+
WINDIR: windowsRoot,
|
|
24892
|
+
...process.env.TEMP ? { TEMP: process.env.TEMP } : {},
|
|
24893
|
+
...process.env.TMP ? { TMP: process.env.TMP } : {},
|
|
24894
|
+
ZIXT_RUN_ARTIFACT_PATHS: JSON.stringify(paths)
|
|
24895
|
+
},
|
|
24896
|
+
stdio: "ignore",
|
|
24897
|
+
windowsHide: true
|
|
24898
|
+
}
|
|
24899
|
+
);
|
|
24900
|
+
let finished = false;
|
|
24901
|
+
const timeout = setTimeout(() => {
|
|
24902
|
+
helper.kill("SIGKILL");
|
|
24903
|
+
finish(new Error("private Windows run-artifact ACL update timed out"));
|
|
24904
|
+
}, WINDOWS_ACL_TIMEOUT_MS);
|
|
24905
|
+
timeout.unref?.();
|
|
24906
|
+
const finish = (error52) => {
|
|
24907
|
+
if (finished) return;
|
|
24908
|
+
finished = true;
|
|
24909
|
+
clearTimeout(timeout);
|
|
24910
|
+
if (error52) reject3(error52);
|
|
24911
|
+
else resolvePromise();
|
|
24912
|
+
};
|
|
24913
|
+
helper.once(
|
|
24914
|
+
"error",
|
|
24915
|
+
() => finish(new Error("private Windows run-artifact ACL update could not start"))
|
|
24916
|
+
);
|
|
24917
|
+
helper.once(
|
|
24918
|
+
"close",
|
|
24919
|
+
(code) => finish(code === 0 ? void 0 : new Error("private Windows run-artifact ACL update failed"))
|
|
24920
|
+
);
|
|
24921
|
+
});
|
|
24754
24922
|
}
|
|
24755
|
-
function
|
|
24756
|
-
const
|
|
24757
|
-
|
|
24758
|
-
|
|
24759
|
-
|
|
24760
|
-
|
|
24761
|
-
|
|
24762
|
-
|
|
24763
|
-
|
|
24764
|
-
|
|
24765
|
-
|
|
24766
|
-
|
|
24767
|
-
|
|
24923
|
+
async function createPrivateDirectory(parent, name) {
|
|
24924
|
+
const path = join6(parent, name);
|
|
24925
|
+
assertBelow(parent, path);
|
|
24926
|
+
await mkdir3(path, { mode: DIRECTORY_MODE });
|
|
24927
|
+
await chmod2(path, DIRECTORY_MODE);
|
|
24928
|
+
const real = await realpath3(path);
|
|
24929
|
+
assertBelow(parent, real);
|
|
24930
|
+
return real;
|
|
24931
|
+
}
|
|
24932
|
+
async function writePrivateFile(path, content) {
|
|
24933
|
+
await writeFile(path, content, { flag: "wx", mode: FILE_MODE });
|
|
24934
|
+
await chmod2(path, FILE_MODE);
|
|
24935
|
+
}
|
|
24936
|
+
function quotePosix(value) {
|
|
24937
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
24938
|
+
}
|
|
24939
|
+
function quoteWindows(value) {
|
|
24940
|
+
if (value.includes('"') || /[\r\n]/.test(value)) {
|
|
24941
|
+
throw new Error("Windows command paths must not contain quotes or newlines");
|
|
24942
|
+
}
|
|
24943
|
+
return `"${value}"`;
|
|
24944
|
+
}
|
|
24945
|
+
function buildDenySshCommand(executablePath, scriptPath, platform = process.platform) {
|
|
24946
|
+
const quote2 = platform === "win32" ? quoteWindows : quotePosix;
|
|
24947
|
+
return `${quote2(executablePath)} ${quote2(scriptPath)}`;
|
|
24948
|
+
}
|
|
24949
|
+
async function createRunArtifacts(input) {
|
|
24950
|
+
requireSafeSegment(input.agentId, "agentId");
|
|
24951
|
+
requireSafeSegment(input.runToken, "runToken");
|
|
24952
|
+
const root = await prepareRoot(input.root);
|
|
24953
|
+
const removeTree = input.removeTree ?? ((path) => rm3(path, { recursive: true, force: true }));
|
|
24954
|
+
const cleanupRetryDelayMs = input.cleanupRetryDelayMs ?? CLEANUP_RETRY_DELAY_MS;
|
|
24955
|
+
const agentRoot = await prepareAgentRoot(root, input.agentId);
|
|
24956
|
+
await lockDownWindowsDirectories([root, agentRoot]);
|
|
24957
|
+
const runRoot = join6(agentRoot, input.runToken);
|
|
24958
|
+
assertBelow(agentRoot, runRoot);
|
|
24959
|
+
try {
|
|
24960
|
+
await mkdir3(runRoot, { mode: DIRECTORY_MODE });
|
|
24961
|
+
await chmod2(runRoot, DIRECTORY_MODE);
|
|
24962
|
+
const realRunRoot = await realpath3(runRoot);
|
|
24963
|
+
assertBelow(agentRoot, realRunRoot);
|
|
24964
|
+
const emptyGithubConfigDirectory = await createPrivateDirectory(realRunRoot, "gh-config");
|
|
24965
|
+
const emptyGitHooksDirectory = await createPrivateDirectory(realRunRoot, "git-hooks");
|
|
24966
|
+
const gitBridgesDirectory = await createPrivateDirectory(realRunRoot, "git-bridges");
|
|
24967
|
+
const denySshScript = join6(realRunRoot, "deny-ssh.cjs");
|
|
24968
|
+
const runnerWrapperScript = join6(realRunRoot, "runner-wrapper.cjs");
|
|
24969
|
+
const systemPromptPath = join6(realRunRoot, "system-prompt.txt");
|
|
24970
|
+
const mcpConfigPath = join6(realRunRoot, "mcp.json");
|
|
24971
|
+
await writePrivateFile(denySshScript, "process.exitCode = 127;");
|
|
24972
|
+
await writePrivateFile(runnerWrapperScript, RUNNER_GUARDIAN);
|
|
24973
|
+
return {
|
|
24974
|
+
runRoot: realRunRoot,
|
|
24975
|
+
emptyGithubConfigDirectory,
|
|
24976
|
+
emptyGitHooksDirectory,
|
|
24977
|
+
gitBridgesDirectory,
|
|
24978
|
+
denySshScript,
|
|
24979
|
+
denySshCommand: buildDenySshCommand(process.execPath, denySshScript),
|
|
24980
|
+
runnerWrapperScript,
|
|
24981
|
+
nullConfigPath: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
24982
|
+
async writeSystemPrompt(content) {
|
|
24983
|
+
await writePrivateFile(systemPromptPath, content);
|
|
24984
|
+
return systemPromptPath;
|
|
24985
|
+
},
|
|
24986
|
+
async writeMcpConfig(content) {
|
|
24987
|
+
await writePrivateFile(mcpConfigPath, content);
|
|
24988
|
+
return mcpConfigPath;
|
|
24989
|
+
},
|
|
24990
|
+
async cleanup() {
|
|
24991
|
+
await removePrivateTreeWithRetries(realRunRoot, removeTree, cleanupRetryDelayMs);
|
|
24992
|
+
}
|
|
24993
|
+
};
|
|
24994
|
+
} catch (error52) {
|
|
24995
|
+
await removePrivateTreeWithRetries(runRoot, removeTree, cleanupRetryDelayMs).catch(() => {
|
|
24996
|
+
});
|
|
24997
|
+
throw error52;
|
|
24998
|
+
}
|
|
24999
|
+
}
|
|
25000
|
+
async function removePrivateTreeWithRetries(path, removeTree, retryDelayMs) {
|
|
25001
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS; attempt++) {
|
|
25002
|
+
try {
|
|
25003
|
+
await removeTree(path);
|
|
25004
|
+
return;
|
|
25005
|
+
} catch (error52) {
|
|
25006
|
+
if (attempt === CLEANUP_ATTEMPTS) throw error52;
|
|
25007
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, retryDelayMs));
|
|
25008
|
+
}
|
|
25009
|
+
}
|
|
25010
|
+
}
|
|
25011
|
+
async function sweepOrphanedRunArtifacts(root) {
|
|
25012
|
+
const absolute = resolve4(root);
|
|
25013
|
+
let realProfile;
|
|
25014
|
+
if (process.platform === "win32") {
|
|
25015
|
+
const profile = resolve4(homedir());
|
|
25016
|
+
assertWindowsProfileBoundary(profile, absolute);
|
|
25017
|
+
await rejectWindowsSymlinkAncestors(profile, absolute);
|
|
25018
|
+
realProfile = await realpath3(profile);
|
|
25019
|
+
}
|
|
25020
|
+
let realRoot;
|
|
25021
|
+
try {
|
|
25022
|
+
realRoot = await requireRealDirectory(absolute, "run artifact root");
|
|
25023
|
+
} catch (error52) {
|
|
25024
|
+
if (isMissing(error52)) return 0;
|
|
25025
|
+
throw error52;
|
|
25026
|
+
}
|
|
25027
|
+
if (realProfile) assertWindowsProfileBoundary(realProfile, realRoot);
|
|
25028
|
+
await chmod2(realRoot, DIRECTORY_MODE);
|
|
25029
|
+
await lockDownWindowsDirectories([realRoot]);
|
|
25030
|
+
const agents = await readdir2(realRoot, { withFileTypes: true });
|
|
25031
|
+
let removed = 0;
|
|
25032
|
+
for (const agent of agents) {
|
|
25033
|
+
if (!SAFE_SEGMENT.test(agent.name) || !agent.isDirectory() || agent.isSymbolicLink()) continue;
|
|
25034
|
+
const agentPath = join6(realRoot, agent.name);
|
|
25035
|
+
const runs = await readdir2(agentPath, { withFileTypes: true });
|
|
25036
|
+
for (const run3 of runs) {
|
|
25037
|
+
if (!SAFE_SEGMENT.test(run3.name) || !run3.isDirectory() || run3.isSymbolicLink()) continue;
|
|
25038
|
+
const runPath = join6(agentPath, run3.name);
|
|
25039
|
+
assertBelow(agentPath, runPath);
|
|
25040
|
+
await rm3(runPath, { recursive: true, force: true });
|
|
25041
|
+
removed++;
|
|
25042
|
+
}
|
|
25043
|
+
}
|
|
25044
|
+
return removed;
|
|
25045
|
+
}
|
|
25046
|
+
function defaultRunRegistryRoot() {
|
|
25047
|
+
return join6(homedir(), ".zixt", "run-registry");
|
|
25048
|
+
}
|
|
25049
|
+
async function terminateRecordedRunProcesses(registryRoot = defaultRunRegistryRoot(), terminate = terminateRecordedProcessTree) {
|
|
25050
|
+
const entries = await readRecordedRunAssignmentEntriesStrict(registryRoot);
|
|
25051
|
+
const terminated = /* @__PURE__ */ new Set();
|
|
25052
|
+
const orderedEntries = [...entries].sort(
|
|
25053
|
+
(left, right) => Number(Boolean(right.record.identity)) - Number(Boolean(left.record.identity))
|
|
25054
|
+
);
|
|
25055
|
+
for (const { record: record2 } of orderedEntries) {
|
|
25056
|
+
const identityKey = record2.identity ? `${record2.identity.kind}:${record2.identity.nonce}` : "legacy";
|
|
25057
|
+
const witnessKey = `${record2.pid}:${identityKey}`;
|
|
25058
|
+
if (terminated.has(witnessKey)) continue;
|
|
25059
|
+
await terminate(record2.pid, record2.identity);
|
|
25060
|
+
terminated.add(witnessKey);
|
|
25061
|
+
}
|
|
25062
|
+
return entries.map(({ record: record2 }) => record2);
|
|
25063
|
+
}
|
|
25064
|
+
async function syncRunRegistryDirectory(path) {
|
|
25065
|
+
const handle = await open3(path, "r");
|
|
25066
|
+
try {
|
|
25067
|
+
await handle.sync();
|
|
25068
|
+
} finally {
|
|
25069
|
+
await handle.close();
|
|
25070
|
+
}
|
|
25071
|
+
}
|
|
25072
|
+
async function ensureDurableRunRegistryRoot(registryRoot, syncDirectory7) {
|
|
25073
|
+
const firstCreated = await mkdir3(registryRoot, { recursive: true, mode: DIRECTORY_MODE });
|
|
25074
|
+
if (firstCreated && process.platform !== "win32") {
|
|
25075
|
+
const first = resolve4(firstCreated);
|
|
25076
|
+
const target = resolve4(registryRoot);
|
|
25077
|
+
await syncDirectory7(dirname3(first));
|
|
25078
|
+
let current = first;
|
|
25079
|
+
for (const part of relative3(first, target).split(sep3).filter(Boolean)) {
|
|
25080
|
+
await syncDirectory7(current);
|
|
25081
|
+
current = join6(current, part);
|
|
25082
|
+
}
|
|
25083
|
+
}
|
|
25084
|
+
await chmod2(registryRoot, DIRECTORY_MODE);
|
|
25085
|
+
}
|
|
25086
|
+
async function recordRunAssignment(runToken, record2, registryRoot = defaultRunRegistryRoot(), options = {}) {
|
|
25087
|
+
if (!SAFE_SEGMENT.test(runToken)) return false;
|
|
25088
|
+
const destination = join6(registryRoot, `${runToken}.json`);
|
|
25089
|
+
const temporary = join6(registryRoot, `.${runToken}.${process.pid}.${Date.now()}.tmp`);
|
|
25090
|
+
let handle;
|
|
25091
|
+
try {
|
|
25092
|
+
const syncDirectory7 = options.syncDirectory ?? syncRunRegistryDirectory;
|
|
25093
|
+
await ensureDurableRunRegistryRoot(registryRoot, syncDirectory7);
|
|
25094
|
+
handle = await open3(temporary, "wx", FILE_MODE);
|
|
25095
|
+
await handle.writeFile(JSON.stringify(record2), "utf8");
|
|
25096
|
+
await handle.sync();
|
|
25097
|
+
await handle.close();
|
|
25098
|
+
handle = void 0;
|
|
25099
|
+
await rename2(temporary, destination);
|
|
25100
|
+
if (process.platform !== "win32") {
|
|
25101
|
+
await syncDirectory7(registryRoot);
|
|
25102
|
+
}
|
|
25103
|
+
return true;
|
|
25104
|
+
} catch {
|
|
25105
|
+
return false;
|
|
25106
|
+
} finally {
|
|
25107
|
+
await handle?.close().catch(() => {
|
|
25108
|
+
});
|
|
25109
|
+
await rm3(temporary, { force: true }).catch(() => {
|
|
25110
|
+
});
|
|
25111
|
+
}
|
|
25112
|
+
}
|
|
25113
|
+
var retainingAssignments = false;
|
|
25114
|
+
function retainRunAssignments() {
|
|
25115
|
+
retainingAssignments = true;
|
|
25116
|
+
}
|
|
25117
|
+
async function forgetRunAssignment(runToken, registryRoot = defaultRunRegistryRoot()) {
|
|
25118
|
+
if (retainingAssignments) return;
|
|
25119
|
+
if (!SAFE_SEGMENT.test(runToken)) return;
|
|
25120
|
+
try {
|
|
25121
|
+
await rm3(join6(registryRoot, `${runToken}.json`), { force: true });
|
|
25122
|
+
} catch {
|
|
25123
|
+
}
|
|
25124
|
+
}
|
|
25125
|
+
function parseAssignment(text) {
|
|
25126
|
+
let parsed;
|
|
25127
|
+
try {
|
|
25128
|
+
parsed = JSON.parse(text);
|
|
25129
|
+
} catch {
|
|
25130
|
+
return null;
|
|
25131
|
+
}
|
|
25132
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
25133
|
+
const value = parsed;
|
|
25134
|
+
if (typeof value.taskId !== "string" || value.taskId === "") return null;
|
|
25135
|
+
if (typeof value.epoch !== "number" || !Number.isInteger(value.epoch) || value.epoch < 1) {
|
|
25136
|
+
return null;
|
|
25137
|
+
}
|
|
25138
|
+
const pid = value.pid;
|
|
25139
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 1) return null;
|
|
25140
|
+
let identity;
|
|
25141
|
+
if (value.identity !== void 0) {
|
|
25142
|
+
if (typeof value.identity !== "object" || value.identity === null || value.identity.kind !== "guardian_nonce" || typeof value.identity.nonce !== "string" || !/^[A-Za-z0-9_-]{16,200}$/.test(value.identity.nonce)) {
|
|
25143
|
+
return null;
|
|
25144
|
+
}
|
|
25145
|
+
identity = {
|
|
25146
|
+
kind: "guardian_nonce",
|
|
25147
|
+
nonce: value.identity.nonce
|
|
25148
|
+
};
|
|
25149
|
+
}
|
|
25150
|
+
return { taskId: value.taskId, epoch: value.epoch, pid, ...identity ? { identity } : {} };
|
|
25151
|
+
}
|
|
25152
|
+
async function readRecordedRunAssignmentEntries(registryRoot = defaultRunRegistryRoot()) {
|
|
25153
|
+
let entries;
|
|
25154
|
+
try {
|
|
25155
|
+
entries = await readdir2(registryRoot, { withFileTypes: true });
|
|
25156
|
+
} catch {
|
|
25157
|
+
return [];
|
|
25158
|
+
}
|
|
25159
|
+
const found = [];
|
|
25160
|
+
for (const entry of entries) {
|
|
25161
|
+
if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith(".json")) continue;
|
|
25162
|
+
const runToken = entry.name.slice(0, -".json".length);
|
|
25163
|
+
if (!SAFE_SEGMENT.test(runToken)) continue;
|
|
25164
|
+
let text;
|
|
25165
|
+
try {
|
|
25166
|
+
text = await readFile5(join6(registryRoot, entry.name), "utf8");
|
|
25167
|
+
} catch {
|
|
25168
|
+
continue;
|
|
25169
|
+
}
|
|
25170
|
+
const record2 = parseAssignment(text);
|
|
25171
|
+
if (record2) found.push({ runToken, record: record2 });
|
|
25172
|
+
}
|
|
25173
|
+
return found;
|
|
25174
|
+
}
|
|
25175
|
+
async function readRecordedRunAssignmentEntriesStrict(registryRoot = defaultRunRegistryRoot()) {
|
|
25176
|
+
let rootStat;
|
|
25177
|
+
try {
|
|
25178
|
+
rootStat = await lstat4(registryRoot);
|
|
25179
|
+
} catch (error52) {
|
|
25180
|
+
if (isMissing(error52)) return [];
|
|
25181
|
+
throw new Error("run registry state could not be observed");
|
|
25182
|
+
}
|
|
25183
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
25184
|
+
throw new Error("run registry root is not a trusted directory");
|
|
25185
|
+
}
|
|
25186
|
+
let entries;
|
|
25187
|
+
try {
|
|
25188
|
+
entries = await readdir2(registryRoot, { withFileTypes: true });
|
|
25189
|
+
} catch {
|
|
25190
|
+
throw new Error("run registry state could not be observed");
|
|
25191
|
+
}
|
|
25192
|
+
const found = [];
|
|
25193
|
+
for (const entry of entries) {
|
|
25194
|
+
if (!entry.name.endsWith(".json")) continue;
|
|
25195
|
+
const runToken = entry.name.slice(0, -".json".length);
|
|
25196
|
+
if (!SAFE_SEGMENT.test(runToken)) continue;
|
|
25197
|
+
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
25198
|
+
throw new Error("committed run registry witness is not a regular file");
|
|
25199
|
+
}
|
|
25200
|
+
let text;
|
|
25201
|
+
try {
|
|
25202
|
+
text = await readFile5(join6(registryRoot, entry.name), "utf8");
|
|
25203
|
+
} catch {
|
|
25204
|
+
throw new Error("committed run registry witness could not be read");
|
|
25205
|
+
}
|
|
25206
|
+
const record2 = parseAssignment(text);
|
|
25207
|
+
if (!record2) throw new Error("committed run registry witness is malformed");
|
|
25208
|
+
found.push({ runToken, record: record2 });
|
|
25209
|
+
}
|
|
25210
|
+
return found;
|
|
25211
|
+
}
|
|
25212
|
+
async function forgetAcknowledgedRunAssignments(assignments, registryRoot = defaultRunRegistryRoot()) {
|
|
25213
|
+
if (assignments.length === 0) return;
|
|
25214
|
+
const acknowledged = new Set(
|
|
25215
|
+
assignments.map((assignment) => `${assignment.taskId}:${assignment.epoch}`)
|
|
25216
|
+
);
|
|
25217
|
+
const entries = await readRecordedRunAssignmentEntries(registryRoot);
|
|
25218
|
+
await Promise.all(
|
|
25219
|
+
entries.filter(({ record: record2 }) => acknowledged.has(`${record2.taskId}:${record2.epoch}`)).map(({ runToken }) => rm3(join6(registryRoot, `${runToken}.json`), { force: true }))
|
|
25220
|
+
);
|
|
25221
|
+
}
|
|
25222
|
+
async function forgetSupersededRunAssignments(taskId, epoch, registryRoot = defaultRunRegistryRoot()) {
|
|
25223
|
+
const entries = await readRecordedRunAssignmentEntries(registryRoot);
|
|
25224
|
+
await Promise.all(
|
|
25225
|
+
entries.filter(({ record: record2 }) => record2.taskId === taskId && record2.epoch < epoch).map(({ runToken }) => rm3(join6(registryRoot, `${runToken}.json`), { force: true }))
|
|
25226
|
+
);
|
|
25227
|
+
}
|
|
25228
|
+
|
|
25229
|
+
// src/supervisor.ts
|
|
25230
|
+
var PACKAGE_NAME = "@zixt/host";
|
|
25231
|
+
var WORKER_ROLE_ENV = "ZIXT_HOST_ROLE";
|
|
25232
|
+
var WORKER_ROLE = "worker";
|
|
25233
|
+
var WORKER_PROXY_ROLE = "worker_proxy";
|
|
25234
|
+
var SUPERVISOR_ROLE = "supervisor";
|
|
25235
|
+
var WORKER_PROXY_ENTRY_ENV = "ZIXT_HOST_WORKER_PROXY_ENTRY";
|
|
25236
|
+
var SUPERVISOR_PROTOCOL_ENV = "ZIXT_HOST_SUPERVISOR_PROTOCOL";
|
|
25237
|
+
var SUPERVISOR_VERSION_ENV = "ZIXT_HOST_SUPERVISOR_VERSION";
|
|
25238
|
+
var SUPERVISOR_PROTOCOL = "2";
|
|
25239
|
+
var SUPERVISOR_RELOAD_EXIT_CODE = 76;
|
|
25240
|
+
var DO_NOT_RESTART_EXIT_CODE = 64;
|
|
25241
|
+
var RESTART_BACKOFF_MS = 2e3;
|
|
25242
|
+
var MAX_RESTART_BACKOFF_MS = 6e4;
|
|
25243
|
+
var CRASH_STREAK_RESET_MS = 6e4;
|
|
25244
|
+
var RELEASE_PROBATION_MS = 5 * 6e4;
|
|
25245
|
+
var FAST_UPDATE_EXIT_MS = 1e4;
|
|
25246
|
+
var STOP_GRACE_MS = 3e4;
|
|
25247
|
+
var INSTALL_TIMEOUT_MS = 5 * 6e4;
|
|
25248
|
+
var WORKER_WATCHDOG_CHECK_MS = 1e3;
|
|
25249
|
+
var WORKER_WATCHDOG_TIMEOUT_MS = 15e3;
|
|
25250
|
+
var WORKER_WATCHDOG_STARTUP_MS = 6e4;
|
|
25251
|
+
var WORKER_READINESS_TIMEOUT_MS = 9e4;
|
|
25252
|
+
var CLEANUP_ATTEMPTS2 = 3;
|
|
25253
|
+
var VERSION_DIR = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
25254
|
+
var CURRENT_RELEASE_ENTRY = "current.js";
|
|
25255
|
+
var WINDOWS_RELEASE_POINTER = "current-release.json";
|
|
25256
|
+
var WINDOWS_LAUNCHER_OWNED_MARKER = "// Zixt Host stable Windows launcher v";
|
|
25257
|
+
var WINDOWS_LAUNCHER_MARKER = `${WINDOWS_LAUNCHER_OWNED_MARKER}2`;
|
|
25258
|
+
function signalWorkerGroup(child, signal) {
|
|
25259
|
+
if (!child.pid) return;
|
|
25260
|
+
try {
|
|
25261
|
+
if (process.platform === "win32") child.kill(signal);
|
|
25262
|
+
else process.kill(-child.pid, signal);
|
|
25263
|
+
} catch {
|
|
25264
|
+
}
|
|
25265
|
+
}
|
|
25266
|
+
function versionsRoot() {
|
|
25267
|
+
return process.env.ZIXT_HOST_VERSIONS_DIR ?? join7(homedir2(), ".zixt", "host-versions");
|
|
25268
|
+
}
|
|
25269
|
+
function npmCommand(platform = process.platform) {
|
|
25270
|
+
return platform === "win32" ? "npm.cmd" : "npm";
|
|
25271
|
+
}
|
|
25272
|
+
function windowsInstallerCommandLine(command, args) {
|
|
25273
|
+
return [command, ...args].map(quoteForCmd).join(" ");
|
|
25274
|
+
}
|
|
25275
|
+
function installedReleaseEntry(version2, root = versionsRoot()) {
|
|
25276
|
+
return join7(root, version2, "node_modules", PACKAGE_NAME, "dist", "index.js");
|
|
25277
|
+
}
|
|
25278
|
+
function releaseEntryAtPrefix(prefix) {
|
|
25279
|
+
return join7(prefix, "node_modules", PACKAGE_NAME, "dist", "index.js");
|
|
25280
|
+
}
|
|
25281
|
+
function releaseManifestAtPrefix(prefix) {
|
|
25282
|
+
return join7(prefix, "node_modules", PACKAGE_NAME, "package.json");
|
|
25283
|
+
}
|
|
25284
|
+
async function validReleaseAtPrefix(prefix, version2) {
|
|
25285
|
+
try {
|
|
25286
|
+
const [entry, manifestText] = await Promise.all([
|
|
25287
|
+
lstat5(releaseEntryAtPrefix(prefix)),
|
|
25288
|
+
readFile6(releaseManifestAtPrefix(prefix), "utf8")
|
|
25289
|
+
]);
|
|
25290
|
+
if (!entry.isFile()) return false;
|
|
25291
|
+
const manifest = JSON.parse(manifestText);
|
|
25292
|
+
return manifest.name === PACKAGE_NAME && manifest.version === version2;
|
|
25293
|
+
} catch {
|
|
25294
|
+
return false;
|
|
25295
|
+
}
|
|
25296
|
+
}
|
|
25297
|
+
async function syncDirectory3(path) {
|
|
25298
|
+
if (process.platform === "win32") return;
|
|
25299
|
+
const directory = await open4(path, "r");
|
|
25300
|
+
try {
|
|
25301
|
+
await directory.sync();
|
|
25302
|
+
} finally {
|
|
25303
|
+
await directory.close();
|
|
25304
|
+
}
|
|
25305
|
+
}
|
|
25306
|
+
function installedReleaseVersion(entry, root = versionsRoot()) {
|
|
25307
|
+
if (!isAbsolute8(entry)) return null;
|
|
25308
|
+
const relativeEntry = relative4(resolve5(root), resolve5(entry));
|
|
25309
|
+
if (!relativeEntry || relativeEntry.startsWith(`..${sep4}`) || isAbsolute8(relativeEntry)) {
|
|
25310
|
+
return null;
|
|
25311
|
+
}
|
|
25312
|
+
const version2 = relativeEntry.split(sep4)[0];
|
|
25313
|
+
if (!version2 || !VERSION_DIR.test(version2)) return null;
|
|
25314
|
+
return resolve5(entry) === resolve5(installedReleaseEntry(version2, root)) ? version2 : null;
|
|
25315
|
+
}
|
|
25316
|
+
function currentReleaseEntry(root = versionsRoot(), platform = process.platform) {
|
|
25317
|
+
return join7(root, platform === "win32" ? "current-launcher.cjs" : CURRENT_RELEASE_ENTRY);
|
|
25318
|
+
}
|
|
25319
|
+
function windowsReleasePointer(root = versionsRoot()) {
|
|
25320
|
+
return join7(root, WINDOWS_RELEASE_POINTER);
|
|
25321
|
+
}
|
|
25322
|
+
var WINDOWS_STABLE_LAUNCHER = `${WINDOWS_LAUNCHER_MARKER}
|
|
25323
|
+
'use strict';
|
|
25324
|
+
const { spawn } = require('node:child_process');
|
|
25325
|
+
const { readFileSync } = require('node:fs');
|
|
25326
|
+
const { dirname, isAbsolute, join } = require('node:path');
|
|
25327
|
+
const pointer = JSON.parse(readFileSync(join(dirname(__filename), '${WINDOWS_RELEASE_POINTER}'), 'utf8'));
|
|
25328
|
+
if (pointer.schema !== 1 || typeof pointer.entry !== 'string' || !isAbsolute(pointer.entry)) process.exit(64);
|
|
25329
|
+
const child = spawn(process.execPath, [pointer.entry, ...process.argv.slice(2)], {
|
|
25330
|
+
// This wrapper owns the only write end of the installed launcher's stdin.
|
|
25331
|
+
// If Task Scheduler/the wrapper dies, EOF reaches launchHostSupervisor and
|
|
25332
|
+
// triggers checked supervisor/worker cleanup before a scheduled restart.
|
|
25333
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
25334
|
+
windowsHide: true,
|
|
25335
|
+
});
|
|
25336
|
+
child.stdin.on('error', () => {});
|
|
25337
|
+
let stopping = false;
|
|
25338
|
+
const closeChildInput = () => {
|
|
25339
|
+
if (stopping) return;
|
|
25340
|
+
stopping = true;
|
|
25341
|
+
try { child.stdin.end(); } catch {}
|
|
25342
|
+
};
|
|
25343
|
+
process.stdin.on('data', (chunk) => {
|
|
25344
|
+
if (!stopping && child.stdin.writable) child.stdin.write(chunk);
|
|
25345
|
+
});
|
|
25346
|
+
process.stdin.on('end', closeChildInput);
|
|
25347
|
+
process.stdin.on('error', closeChildInput);
|
|
25348
|
+
process.stdin.resume();
|
|
25349
|
+
process.on('SIGINT', closeChildInput);
|
|
25350
|
+
process.on('SIGTERM', closeChildInput);
|
|
25351
|
+
child.once('error', () => process.exit(1));
|
|
25352
|
+
child.once('exit', (code) => process.exit(code == null ? 1 : code));
|
|
25353
|
+
`;
|
|
25354
|
+
async function replaceDurableFile(path, contents, sync = syncDirectory3) {
|
|
25355
|
+
const parent = dirname4(path);
|
|
25356
|
+
await mkdir4(parent, { recursive: true, mode: 448 });
|
|
25357
|
+
const temporary = join7(parent, `.${CURRENT_RELEASE_ENTRY}.${process.pid}.${crypto.randomUUID()}`);
|
|
25358
|
+
const handle = await open4(temporary, "wx", 384);
|
|
25359
|
+
try {
|
|
25360
|
+
await handle.writeFile(contents, "utf8");
|
|
25361
|
+
await handle.sync();
|
|
25362
|
+
await handle.close();
|
|
25363
|
+
await rename3(temporary, path);
|
|
25364
|
+
await sync(parent);
|
|
25365
|
+
} catch (error52) {
|
|
25366
|
+
await handle.close().catch(() => void 0);
|
|
25367
|
+
await rm4(temporary, { force: true }).catch(() => void 0);
|
|
25368
|
+
throw error52;
|
|
25369
|
+
}
|
|
25370
|
+
}
|
|
25371
|
+
async function activateInstalledRelease(entry, root = versionsRoot(), platform = process.platform, sync = syncDirectory3) {
|
|
25372
|
+
if (installedReleaseVersion(entry, root) === null) {
|
|
25373
|
+
throw new Error("release entry is outside the Zixt Host versions directory");
|
|
25374
|
+
}
|
|
25375
|
+
await access2(entry);
|
|
25376
|
+
if (platform === "win32") {
|
|
25377
|
+
await mkdir4(root, { recursive: true, mode: 448 });
|
|
25378
|
+
const launcher = currentReleaseEntry(root, platform);
|
|
25379
|
+
const existingLauncher = await lstat5(launcher).catch((error52) => {
|
|
25380
|
+
if (error52.code === "ENOENT") return null;
|
|
25381
|
+
throw error52;
|
|
25382
|
+
});
|
|
25383
|
+
if (existingLauncher) {
|
|
25384
|
+
if (!existingLauncher.isFile()) {
|
|
25385
|
+
throw new Error("the Zixt Host Windows launcher is not a regular file");
|
|
25386
|
+
}
|
|
25387
|
+
const contents = await readFile6(launcher, "utf8");
|
|
25388
|
+
if (!contents.startsWith(WINDOWS_LAUNCHER_OWNED_MARKER)) {
|
|
25389
|
+
throw new Error("the Zixt Host Windows launcher is not owned by Zixt");
|
|
25390
|
+
}
|
|
25391
|
+
if (contents !== WINDOWS_STABLE_LAUNCHER) {
|
|
25392
|
+
await replaceDurableFile(launcher, WINDOWS_STABLE_LAUNCHER, sync);
|
|
25393
|
+
}
|
|
25394
|
+
} else {
|
|
25395
|
+
await replaceDurableFile(launcher, WINDOWS_STABLE_LAUNCHER, sync);
|
|
25396
|
+
}
|
|
25397
|
+
const pointerPath = windowsReleasePointer(root);
|
|
25398
|
+
const existingPointer = await lstat5(pointerPath).catch((error52) => {
|
|
25399
|
+
if (error52.code === "ENOENT") return null;
|
|
25400
|
+
throw error52;
|
|
25401
|
+
});
|
|
25402
|
+
if (existingPointer) {
|
|
25403
|
+
if (!existingPointer.isFile() || existingPointer.size > 4 * 1024) {
|
|
25404
|
+
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
25405
|
+
}
|
|
25406
|
+
let prior;
|
|
25407
|
+
try {
|
|
25408
|
+
prior = JSON.parse(await readFile6(pointerPath, "utf8"));
|
|
25409
|
+
} catch {
|
|
25410
|
+
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
25411
|
+
}
|
|
25412
|
+
const candidate = prior;
|
|
25413
|
+
if (!prior || typeof prior !== "object" || candidate.schema !== 1 || typeof candidate.entry !== "string" || installedReleaseVersion(candidate.entry, root) === null) {
|
|
25414
|
+
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
25415
|
+
}
|
|
25416
|
+
}
|
|
25417
|
+
await replaceDurableFile(
|
|
25418
|
+
pointerPath,
|
|
25419
|
+
`${JSON.stringify({ schema: 1, entry: resolve5(entry) })}
|
|
25420
|
+
`,
|
|
25421
|
+
sync
|
|
25422
|
+
);
|
|
25423
|
+
return launcher;
|
|
25424
|
+
}
|
|
25425
|
+
if (platform !== "linux" && platform !== "darwin") return entry;
|
|
25426
|
+
await mkdir4(root, { recursive: true, mode: 448 });
|
|
25427
|
+
const current = currentReleaseEntry(root, platform);
|
|
25428
|
+
const existing = await lstat5(current).catch((error52) => {
|
|
25429
|
+
if (error52.code === "ENOENT") return null;
|
|
25430
|
+
throw error52;
|
|
25431
|
+
});
|
|
25432
|
+
if (existing && !existing.isSymbolicLink()) {
|
|
25433
|
+
throw new Error("the Zixt Host current-release entry is not a symbolic link");
|
|
25434
|
+
}
|
|
25435
|
+
const temporary = join7(root, `.${CURRENT_RELEASE_ENTRY}.${process.pid}.${crypto.randomUUID()}`);
|
|
25436
|
+
try {
|
|
25437
|
+
await symlink(entry, temporary, "file");
|
|
25438
|
+
await rename3(temporary, current);
|
|
25439
|
+
await sync(root);
|
|
25440
|
+
} catch (error52) {
|
|
25441
|
+
await rm4(temporary, { force: true }).catch(() => void 0);
|
|
25442
|
+
throw error52;
|
|
25443
|
+
}
|
|
25444
|
+
return current;
|
|
25445
|
+
}
|
|
25446
|
+
async function activatedReleaseVersion(root = versionsRoot(), platform = process.platform) {
|
|
25447
|
+
if (platform === "win32") {
|
|
25448
|
+
try {
|
|
25449
|
+
const pointerPath = windowsReleasePointer(root);
|
|
25450
|
+
const metadata = await lstat5(pointerPath);
|
|
25451
|
+
if (!metadata.isFile() || metadata.size > 4 * 1024) return null;
|
|
25452
|
+
const value = JSON.parse(await readFile6(pointerPath, "utf8"));
|
|
25453
|
+
return value.schema === 1 && typeof value.entry === "string" ? installedReleaseVersion(value.entry, root) : null;
|
|
25454
|
+
} catch {
|
|
25455
|
+
return null;
|
|
25456
|
+
}
|
|
25457
|
+
}
|
|
25458
|
+
if (platform !== "linux" && platform !== "darwin") return null;
|
|
25459
|
+
try {
|
|
25460
|
+
const current = currentReleaseEntry(root, platform);
|
|
25461
|
+
const target = await readlink(current);
|
|
25462
|
+
return installedReleaseVersion(resolve5(dirname4(current), target), root);
|
|
25463
|
+
} catch {
|
|
25464
|
+
return null;
|
|
25465
|
+
}
|
|
25466
|
+
}
|
|
25467
|
+
function sanitizedInstallerEnv(inherited) {
|
|
25468
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
25469
|
+
"PATH",
|
|
25470
|
+
"HOME",
|
|
25471
|
+
"USERPROFILE",
|
|
25472
|
+
"SYSTEMROOT",
|
|
25473
|
+
"WINDIR",
|
|
25474
|
+
"TEMP",
|
|
25475
|
+
"TMP",
|
|
25476
|
+
"TMPDIR",
|
|
25477
|
+
"LOCALAPPDATA",
|
|
25478
|
+
"APPDATA",
|
|
25479
|
+
"XDG_CONFIG_HOME",
|
|
24768
25480
|
"XDG_CACHE_HOME",
|
|
24769
25481
|
"HTTP_PROXY",
|
|
24770
25482
|
"HTTPS_PROXY",
|
|
@@ -24851,13 +25563,13 @@ async function installRelease(version2, options = {}) {
|
|
|
24851
25563
|
const platform = options.platform ?? process.platform;
|
|
24852
25564
|
const installerCommand = options.installerCommand ?? npmCommand(platform);
|
|
24853
25565
|
const root = options.root ?? versionsRoot();
|
|
24854
|
-
const prefix =
|
|
25566
|
+
const prefix = join7(root, version2);
|
|
24855
25567
|
const entry = installedReleaseEntry(version2, root);
|
|
24856
25568
|
if (await validReleaseAtPrefix(prefix, version2)) return entry;
|
|
24857
25569
|
if (options.signal?.aborted) return null;
|
|
24858
|
-
await
|
|
24859
|
-
const staging =
|
|
24860
|
-
const quarantine =
|
|
25570
|
+
await mkdir4(root, { recursive: true, mode: 448 });
|
|
25571
|
+
const staging = join7(root, `.install-${version2}-${process.pid}-${crypto.randomUUID()}`);
|
|
25572
|
+
const quarantine = join7(root, `.invalid-${version2}-${process.pid}-${crypto.randomUUID()}`);
|
|
24861
25573
|
const usesWindowsInstallerGuardian = platform === "win32" && options.spawnInstaller === void 0;
|
|
24862
25574
|
const installerGateNonce = usesWindowsInstallerGuardian ? crypto.randomUUID() : null;
|
|
24863
25575
|
const installerArguments = (installPrefix, installVersion) => [
|
|
@@ -24872,14 +25584,14 @@ async function installRelease(version2, options = {}) {
|
|
|
24872
25584
|
const spawnInstaller = options.spawnInstaller ?? ((installPrefix, installVersion) => {
|
|
24873
25585
|
const installerArgs = installerArguments(installPrefix, installVersion);
|
|
24874
25586
|
if (usesWindowsInstallerGuardian) {
|
|
24875
|
-
return
|
|
25587
|
+
return spawn5(process.execPath, ["-e", WINDOWS_INSTALLER_GUARDIAN, installerGateNonce], {
|
|
24876
25588
|
cwd: root,
|
|
24877
25589
|
stdio: ["pipe", "inherit", "inherit", "ipc"],
|
|
24878
25590
|
env: sanitizedInstallerEnv(process.env),
|
|
24879
25591
|
windowsHide: true
|
|
24880
25592
|
});
|
|
24881
25593
|
}
|
|
24882
|
-
return
|
|
25594
|
+
return spawn5(installerCommand, installerArgs, {
|
|
24883
25595
|
stdio: "inherit",
|
|
24884
25596
|
env: sanitizedInstallerEnv(process.env),
|
|
24885
25597
|
detached: true
|
|
@@ -24889,7 +25601,7 @@ async function installRelease(version2, options = {}) {
|
|
|
24889
25601
|
try {
|
|
24890
25602
|
child = spawnInstaller(staging, version2);
|
|
24891
25603
|
} catch {
|
|
24892
|
-
await
|
|
25604
|
+
await rm4(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
24893
25605
|
return null;
|
|
24894
25606
|
}
|
|
24895
25607
|
let resolveChildExited;
|
|
@@ -24934,7 +25646,7 @@ async function installRelease(version2, options = {}) {
|
|
|
24934
25646
|
if (cleanupStarted || finished) return;
|
|
24935
25647
|
cleanupStarted = true;
|
|
24936
25648
|
void (async () => {
|
|
24937
|
-
for (let attempt = 1; attempt <=
|
|
25649
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
24938
25650
|
try {
|
|
24939
25651
|
let containment = null;
|
|
24940
25652
|
try {
|
|
@@ -24948,7 +25660,7 @@ async function installRelease(version2, options = {}) {
|
|
|
24948
25660
|
finish(successfulCleanupResult);
|
|
24949
25661
|
return;
|
|
24950
25662
|
} catch (error52) {
|
|
24951
|
-
if (attempt ===
|
|
25663
|
+
if (attempt === CLEANUP_ATTEMPTS2) {
|
|
24952
25664
|
finished = true;
|
|
24953
25665
|
clearTimeout(timer);
|
|
24954
25666
|
options.signal?.removeEventListener("abort", requestCleanup);
|
|
@@ -25026,39 +25738,39 @@ async function installRelease(version2, options = {}) {
|
|
|
25026
25738
|
return null;
|
|
25027
25739
|
}
|
|
25028
25740
|
if (await validReleaseAtPrefix(prefix, version2)) return entry;
|
|
25029
|
-
const existing = await
|
|
25741
|
+
const existing = await lstat5(prefix).catch((error52) => {
|
|
25030
25742
|
if (error52.code === "ENOENT") return null;
|
|
25031
25743
|
throw error52;
|
|
25032
25744
|
});
|
|
25033
|
-
if (existing) await
|
|
25745
|
+
if (existing) await rename3(prefix, quarantine);
|
|
25034
25746
|
try {
|
|
25035
|
-
await
|
|
25747
|
+
await rename3(staging, prefix);
|
|
25036
25748
|
} catch {
|
|
25037
25749
|
return await validReleaseAtPrefix(prefix, version2) ? entry : null;
|
|
25038
25750
|
}
|
|
25039
25751
|
await syncDirectory3(root);
|
|
25040
25752
|
return await validReleaseAtPrefix(prefix, version2) ? entry : null;
|
|
25041
25753
|
} finally {
|
|
25042
|
-
await
|
|
25043
|
-
await
|
|
25754
|
+
await rm4(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
25755
|
+
await rm4(quarantine, { recursive: true, force: true }).catch(() => void 0);
|
|
25044
25756
|
}
|
|
25045
25757
|
}
|
|
25046
25758
|
async function pruneInstalledVersions(keep, root = versionsRoot()) {
|
|
25047
25759
|
const protectedDirs = new Set(keep);
|
|
25048
|
-
const running = process.argv[1] ?
|
|
25760
|
+
const running = process.argv[1] ? resolve5(process.argv[1]) : null;
|
|
25049
25761
|
let entries;
|
|
25050
25762
|
try {
|
|
25051
|
-
entries = await
|
|
25763
|
+
entries = await readdir3(root);
|
|
25052
25764
|
} catch {
|
|
25053
25765
|
return [];
|
|
25054
25766
|
}
|
|
25055
25767
|
const removed = [];
|
|
25056
25768
|
for (const name of entries) {
|
|
25057
25769
|
if (protectedDirs.has(name) || !VERSION_DIR.test(name)) continue;
|
|
25058
|
-
const dir =
|
|
25059
|
-
if (running && running.startsWith(`${dir}${
|
|
25770
|
+
const dir = join7(root, name);
|
|
25771
|
+
if (running && running.startsWith(`${dir}${sep4}`)) continue;
|
|
25060
25772
|
try {
|
|
25061
|
-
await
|
|
25773
|
+
await rm4(dir, { recursive: true, force: true });
|
|
25062
25774
|
removed.push(name);
|
|
25063
25775
|
} catch {
|
|
25064
25776
|
}
|
|
@@ -25069,7 +25781,7 @@ function durableState(phase, candidateVersion, fallbackVersion) {
|
|
|
25069
25781
|
return { schema: 1, phase, candidateVersion, fallbackVersion };
|
|
25070
25782
|
}
|
|
25071
25783
|
async function validInstalledRelease(version2, root = versionsRoot()) {
|
|
25072
|
-
return validReleaseAtPrefix(
|
|
25784
|
+
return validReleaseAtPrefix(join7(root, version2), version2);
|
|
25073
25785
|
}
|
|
25074
25786
|
async function recoverDurableReleaseState(store, runningVersion, activeBootVersion, activate, log2) {
|
|
25075
25787
|
const state = await store.load();
|
|
@@ -25181,14 +25893,14 @@ function defaultSpawn(command, argv, supervisorVersion, watchdog, compatibilityO
|
|
|
25181
25893
|
delete env.ZIXT_HOST_REJECT_VERSION;
|
|
25182
25894
|
if (command.rejectedVersion) env.ZIXT_HOST_REJECT_VERSION = command.rejectedVersion;
|
|
25183
25895
|
const entry = compatibilityProxy ? process.argv[1] ?? "" : command.entry ?? process.argv[1] ?? "";
|
|
25184
|
-
const child =
|
|
25896
|
+
const child = spawn5(process.execPath, [entry, ...argv, ...ownership?.argv ?? []], {
|
|
25185
25897
|
// stdin is a pipe this process owns: closing it is how the worker is
|
|
25186
25898
|
// asked to stop, which works identically on Windows, where there is no
|
|
25187
25899
|
// real SIGINT to send. stdout/stderr stay the person's terminal.
|
|
25188
25900
|
stdio: watchdog ? ["pipe", "inherit", "inherit", "ipc"] : ["pipe", "inherit", "inherit"],
|
|
25189
25901
|
env,
|
|
25190
25902
|
...containmentGateNonce ? {
|
|
25191
|
-
cwd: ownership?.ownershipFile ?
|
|
25903
|
+
cwd: ownership?.ownershipFile ? dirname4(ownership.ownershipFile) : dirname4(entry)
|
|
25192
25904
|
} : {},
|
|
25193
25905
|
// The launch nonce is not a user-facing CLI argument. Keeping it as
|
|
25194
25906
|
// argv[0] gives the stable launcher an exact cross-platform process
|
|
@@ -25217,7 +25929,7 @@ async function runWorkerCompatibilityProxy(env = process.env, argv = process.arg
|
|
|
25217
25929
|
const ownership = consumeWorkerOwnershipArguments(argv, env);
|
|
25218
25930
|
const nonce = env[WORKER_WATCHDOG_NONCE_ENV];
|
|
25219
25931
|
const ownershipFile = env[WORKER_OWNERSHIP_FILE_ENV];
|
|
25220
|
-
if (typeof target !== "string" || !
|
|
25932
|
+
if (typeof target !== "string" || !isAbsolute8(target) || typeof nonce !== "string" || typeof ownershipFile !== "string" || !ownership.requested || !ownership.valid) {
|
|
25221
25933
|
return 1;
|
|
25222
25934
|
}
|
|
25223
25935
|
if (!recordWorkerOwnership(ownershipFile, nonce)) return 1;
|
|
@@ -25232,7 +25944,7 @@ async function runWorkerCompatibilityProxy(env = process.env, argv = process.arg
|
|
|
25232
25944
|
delete workerEnv[WORKER_OWNERSHIP_FILE_ENV];
|
|
25233
25945
|
delete workerEnv[LAUNCHER_OWNERSHIP_DIR_ENV];
|
|
25234
25946
|
delete workerEnv[SUPERVISOR_OWNERSHIP_FILE_ENV];
|
|
25235
|
-
const child =
|
|
25947
|
+
const child = spawn5(process.execPath, [target, ...ownership.argv], {
|
|
25236
25948
|
stdio: ["pipe", "inherit", "inherit"],
|
|
25237
25949
|
env: workerEnv,
|
|
25238
25950
|
// The proxy is already a detached group/session leader on POSIX. The old
|
|
@@ -25288,7 +26000,7 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25288
26000
|
const generationNonce = ownershipDirectory ? basename3(ownershipDirectory) : null;
|
|
25289
26001
|
if (ownershipDirectory && generationNonce) {
|
|
25290
26002
|
env[LAUNCHER_OWNERSHIP_DIR_ENV] = ownershipDirectory;
|
|
25291
|
-
env[SUPERVISOR_OWNERSHIP_FILE_ENV] =
|
|
26003
|
+
env[SUPERVISOR_OWNERSHIP_FILE_ENV] = join7(ownershipDirectory, `${generationNonce}.json`);
|
|
25292
26004
|
} else {
|
|
25293
26005
|
delete env[LAUNCHER_OWNERSHIP_DIR_ENV];
|
|
25294
26006
|
}
|
|
@@ -25296,10 +26008,10 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25296
26008
|
if (!containmentGateNonce) delete env[WINDOWS_POST_CONTAINMENT_CWD_ENV];
|
|
25297
26009
|
if (version2) env[SUPERVISOR_VERSION_ENV] = version2;
|
|
25298
26010
|
else delete env[SUPERVISOR_VERSION_ENV];
|
|
25299
|
-
const child2 =
|
|
26011
|
+
const child2 = spawn5(process.execPath, [supervisorEntry, ...argv], {
|
|
25300
26012
|
stdio: ["pipe", "inherit", "inherit"],
|
|
25301
26013
|
env,
|
|
25302
|
-
...containmentGateNonce ? { cwd: ownershipDirectory ??
|
|
26014
|
+
...containmentGateNonce ? { cwd: ownershipDirectory ?? dirname4(supervisorEntry) } : {},
|
|
25303
26015
|
detached: platform !== "win32",
|
|
25304
26016
|
// The new launcher can discover the durable PID record and still has
|
|
25305
26017
|
// to bind it to this exact process before signalling a recycled PID.
|
|
@@ -25311,7 +26023,8 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25311
26023
|
});
|
|
25312
26024
|
const customDelay = options.delay;
|
|
25313
26025
|
const ownsWorkerBoundary = options.spawnSupervisor === void 0 || options.ownershipRoot !== void 0;
|
|
25314
|
-
const ownershipRoot = options.ownershipRoot ??
|
|
26026
|
+
const ownershipRoot = options.ownershipRoot ?? join7(versionsRoot(), "launcher-ownership");
|
|
26027
|
+
const runRegistryRoot2 = options.runRegistryRoot ?? (options.ownershipRoot ? join7(dirname4(options.ownershipRoot), "run-registry") : defaultRunRegistryRoot());
|
|
25315
26028
|
const terminateRecordedOwnership = options.terminateRecordedOwnership ?? terminateRecordedProcessTree;
|
|
25316
26029
|
const createSupervisorContainment = options.createSupervisorContainment ?? (platform === "win32" && options.spawnSupervisor === void 0 ? async (target, identityNonce, signal) => {
|
|
25317
26030
|
if (!target.pid) throw new Error("supervisor process id is unavailable");
|
|
@@ -25346,14 +26059,17 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25346
26059
|
return !stopping;
|
|
25347
26060
|
};
|
|
25348
26061
|
const cleanupSupervisorBoundary = async (exitedSupervisor, supervisorExited, ownershipDirectory, containment) => {
|
|
25349
|
-
for (let attempt = 1; attempt <=
|
|
26062
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
25350
26063
|
try {
|
|
25351
26064
|
if (containment) await containment.terminate();
|
|
25352
26065
|
else await terminateProcessTree(exitedSupervisor, supervisorExited);
|
|
25353
26066
|
if (ownershipDirectory) await cleanupOwnershipGeneration(ownershipDirectory);
|
|
26067
|
+
if (ownsWorkerBoundary) {
|
|
26068
|
+
await terminateRecordedRunProcesses(runRegistryRoot2, terminateRecordedOwnership);
|
|
26069
|
+
}
|
|
25354
26070
|
return true;
|
|
25355
26071
|
} catch (error52) {
|
|
25356
|
-
const finalAttempt = attempt ===
|
|
26072
|
+
const finalAttempt = attempt === CLEANUP_ATTEMPTS2;
|
|
25357
26073
|
log2(
|
|
25358
26074
|
`Zixt Host launcher: old supervisor cleanup is incomplete (${error52 instanceof Error ? error52.message : "unknown error"}); ${finalAttempt ? "exiting for service-manager recovery" : "retrying"}`
|
|
25359
26075
|
);
|
|
@@ -25436,15 +26152,16 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25436
26152
|
}
|
|
25437
26153
|
const recoverPriorOwnership = async () => {
|
|
25438
26154
|
if (!ownsWorkerBoundary) return true;
|
|
25439
|
-
for (let attempt = 1; attempt <=
|
|
26155
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
25440
26156
|
try {
|
|
25441
26157
|
const generations = await readLauncherOwnershipGenerations(ownershipRoot);
|
|
25442
26158
|
for (const generation of generations) {
|
|
25443
26159
|
await cleanupOwnershipGeneration(generation.directory);
|
|
25444
26160
|
}
|
|
26161
|
+
await terminateRecordedRunProcesses(runRegistryRoot2, terminateRecordedOwnership);
|
|
25445
26162
|
return true;
|
|
25446
26163
|
} catch (error52) {
|
|
25447
|
-
const finalAttempt = attempt ===
|
|
26164
|
+
const finalAttempt = attempt === CLEANUP_ATTEMPTS2;
|
|
25448
26165
|
log2(
|
|
25449
26166
|
`Zixt Host launcher: prior worker cleanup is incomplete (${error52 instanceof Error ? error52.message : "unknown error"}); ${finalAttempt ? "exiting for service-manager recovery" : "retrying"}`
|
|
25450
26167
|
);
|
|
@@ -25574,10 +26291,10 @@ async function superviseHost(options = {}) {
|
|
|
25574
26291
|
const log2 = options.log ?? ((message) => console.error(message));
|
|
25575
26292
|
const signalWorker = options.signalWorker ?? signalWorkerGroup;
|
|
25576
26293
|
const inheritedOwnershipDirectory = process.env[LAUNCHER_OWNERSHIP_DIR_ENV];
|
|
25577
|
-
const launcherOwnershipDirectory = productionLifecycle && typeof inheritedOwnershipDirectory === "string" &&
|
|
26294
|
+
const launcherOwnershipDirectory = productionLifecycle && typeof inheritedOwnershipDirectory === "string" && isAbsolute8(inheritedOwnershipDirectory) ? inheritedOwnershipDirectory : null;
|
|
25578
26295
|
if (productionLifecycle && isSupervisorRole() && launcherOwnershipDirectory) {
|
|
25579
26296
|
const generationNonce = basename3(launcherOwnershipDirectory);
|
|
25580
|
-
const expectedOwnershipFile =
|
|
26297
|
+
const expectedOwnershipFile = join7(launcherOwnershipDirectory, `${generationNonce}.json`);
|
|
25581
26298
|
const configuredOwnershipFile = process.env[SUPERVISOR_OWNERSHIP_FILE_ENV];
|
|
25582
26299
|
if (process.argv0 !== generationNonce || configuredOwnershipFile !== expectedOwnershipFile || !recordWorkerOwnership(expectedOwnershipFile, generationNonce)) {
|
|
25583
26300
|
log2("Zixt Host: supervisor ownership could not be committed; refusing to start a worker");
|
|
@@ -25675,7 +26392,7 @@ async function superviseHost(options = {}) {
|
|
|
25675
26392
|
return waitOrShutdown(backoff);
|
|
25676
26393
|
};
|
|
25677
26394
|
const cleanupExitedWorker = async (exitedChild, childExited, workerBoundary, containment) => {
|
|
25678
|
-
for (let attempt = 1; attempt <=
|
|
26395
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
25679
26396
|
try {
|
|
25680
26397
|
if (containment) await containment.terminate();
|
|
25681
26398
|
else if (cleanupWorker) await cleanupWorker(exitedChild, childExited);
|
|
@@ -25694,7 +26411,7 @@ async function superviseHost(options = {}) {
|
|
|
25694
26411
|
}
|
|
25695
26412
|
return true;
|
|
25696
26413
|
} catch (error52) {
|
|
25697
|
-
const finalAttempt = attempt ===
|
|
26414
|
+
const finalAttempt = attempt === CLEANUP_ATTEMPTS2;
|
|
25698
26415
|
log2(
|
|
25699
26416
|
finalAttempt ? `Zixt Host: exited worker cleanup could not be confirmed (${error52 instanceof Error ? error52.message : "unknown error"}); handing recovery to the stable launcher` : "Zixt Host: exited worker cleanup could not be confirmed; retrying"
|
|
25700
26417
|
);
|
|
@@ -26104,9 +26821,9 @@ import { homedir as homedir12, hostname as hostname3 } from "node:os";
|
|
|
26104
26821
|
// src/hardware.ts
|
|
26105
26822
|
import { existsSync } from "node:fs";
|
|
26106
26823
|
import { statfs } from "node:fs/promises";
|
|
26107
|
-
import { cpus, freemem, homedir as
|
|
26108
|
-
import { dirname as
|
|
26109
|
-
async function machineHardware(workRoot =
|
|
26824
|
+
import { cpus, freemem, homedir as homedir3, totalmem } from "node:os";
|
|
26825
|
+
import { dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
26826
|
+
async function machineHardware(workRoot = homedir3()) {
|
|
26110
26827
|
return {
|
|
26111
26828
|
// A container or cgroup can hide processors from this count; it is what
|
|
26112
26829
|
// this process can see, which is what its Tasks will actually get.
|
|
@@ -26131,10 +26848,10 @@ async function diskSpace(workRoot) {
|
|
|
26131
26848
|
}
|
|
26132
26849
|
}
|
|
26133
26850
|
function nearestExistingPath(start) {
|
|
26134
|
-
let candidate =
|
|
26851
|
+
let candidate = resolve6(start);
|
|
26135
26852
|
for (let depth = 0; depth < 16; depth++) {
|
|
26136
26853
|
if (existsSync(candidate)) return candidate;
|
|
26137
|
-
const parent =
|
|
26854
|
+
const parent = dirname5(candidate);
|
|
26138
26855
|
if (parent === candidate) return null;
|
|
26139
26856
|
candidate = parent;
|
|
26140
26857
|
}
|
|
@@ -26328,17 +27045,17 @@ function createDemoBrowserAdapterFactory() {
|
|
|
26328
27045
|
}
|
|
26329
27046
|
|
|
26330
27047
|
// src/browser/manager.ts
|
|
26331
|
-
import { lstat as
|
|
26332
|
-
import { homedir as
|
|
26333
|
-
import { dirname as
|
|
26334
|
-
var
|
|
27048
|
+
import { lstat as lstat6, mkdir as mkdir5, open as open5, opendir, readFile as readFile7, rename as rename4, rm as rm5 } from "node:fs/promises";
|
|
27049
|
+
import { homedir as homedir4 } from "node:os";
|
|
27050
|
+
import { dirname as dirname6, join as join8, resolve as resolve7 } from "node:path";
|
|
27051
|
+
var SAFE_SEGMENT2 = /^[A-Za-z0-9_-]{1,200}$/;
|
|
26335
27052
|
var FRAME_MIN_INTERVAL_MS = 100;
|
|
26336
27053
|
var IDLE_TIMEOUT_MS = 15 * 6e4;
|
|
26337
27054
|
var BrowserManager = class {
|
|
26338
27055
|
constructor(opts) {
|
|
26339
27056
|
this.opts = opts;
|
|
26340
|
-
this.profileRoot = opts.profileRoot ??
|
|
26341
|
-
this.profileStateRoot =
|
|
27057
|
+
this.profileRoot = opts.profileRoot ?? join8(homedir4(), ".zixt", "browser-profiles");
|
|
27058
|
+
this.profileStateRoot = join8(this.profileRoot, ".profile-state");
|
|
26342
27059
|
this.viewport = opts.viewport ?? BROWSER_DEFAULT_VIEWPORT;
|
|
26343
27060
|
this.idleTimeoutMs = opts.idleTimeoutMs ?? IDLE_TIMEOUT_MS;
|
|
26344
27061
|
this.frameMinIntervalMs = opts.frameMinIntervalMs ?? FRAME_MIN_INTERVAL_MS;
|
|
@@ -26423,21 +27140,21 @@ var BrowserManager = class {
|
|
|
26423
27140
|
return next;
|
|
26424
27141
|
}
|
|
26425
27142
|
validateAgentId(agentId) {
|
|
26426
|
-
if (!
|
|
27143
|
+
if (!SAFE_SEGMENT2.test(agentId) || !AgentId.safeParse(agentId).success) {
|
|
26427
27144
|
throw new Error("invalid agent id for a browser profile path");
|
|
26428
27145
|
}
|
|
26429
27146
|
}
|
|
26430
27147
|
exactChild(root, child) {
|
|
26431
|
-
const canonicalRoot =
|
|
26432
|
-
const target =
|
|
26433
|
-
if (
|
|
27148
|
+
const canonicalRoot = resolve7(root);
|
|
27149
|
+
const target = resolve7(canonicalRoot, child);
|
|
27150
|
+
if (dirname6(target) !== canonicalRoot) {
|
|
26434
27151
|
throw new Error("browser profile path escaped its owned root");
|
|
26435
27152
|
}
|
|
26436
27153
|
return target;
|
|
26437
27154
|
}
|
|
26438
27155
|
async ensureOwnedDirectory(path) {
|
|
26439
|
-
await
|
|
26440
|
-
const stat3 = await
|
|
27156
|
+
await mkdir5(path, { recursive: true, mode: 448 });
|
|
27157
|
+
const stat3 = await lstat6(path);
|
|
26441
27158
|
if (!stat3.isDirectory() || stat3.isSymbolicLink()) {
|
|
26442
27159
|
throw new Error("browser profile root must be an owned directory, not a symbolic link");
|
|
26443
27160
|
}
|
|
@@ -26450,7 +27167,7 @@ var BrowserManager = class {
|
|
|
26450
27167
|
*/
|
|
26451
27168
|
async syncDirectory(path) {
|
|
26452
27169
|
try {
|
|
26453
|
-
const directory = await
|
|
27170
|
+
const directory = await open5(path, "r");
|
|
26454
27171
|
try {
|
|
26455
27172
|
await directory.sync();
|
|
26456
27173
|
} finally {
|
|
@@ -26472,7 +27189,7 @@ var BrowserManager = class {
|
|
|
26472
27189
|
}
|
|
26473
27190
|
async readProfileState(agentId) {
|
|
26474
27191
|
try {
|
|
26475
|
-
const raw = JSON.parse(await
|
|
27192
|
+
const raw = JSON.parse(await readFile7(this.statePath(agentId), "utf8"));
|
|
26476
27193
|
if (typeof raw !== "object" || raw === null || !Number.isInteger(raw.revision) || Number(raw.revision) < 0 || !["allowed", "purged"].includes(String(raw.state))) {
|
|
26477
27194
|
throw new Error("browser profile lifecycle marker is invalid");
|
|
26478
27195
|
}
|
|
@@ -26491,18 +27208,18 @@ var BrowserManager = class {
|
|
|
26491
27208
|
`${agentId}.${crypto.randomUUID()}.tmp`
|
|
26492
27209
|
);
|
|
26493
27210
|
try {
|
|
26494
|
-
const marker = await
|
|
27211
|
+
const marker = await open5(temporary, "wx+", 384);
|
|
26495
27212
|
try {
|
|
26496
27213
|
await marker.writeFile(JSON.stringify(state), "utf8");
|
|
26497
27214
|
await marker.sync();
|
|
26498
27215
|
} finally {
|
|
26499
27216
|
await marker.close();
|
|
26500
27217
|
}
|
|
26501
|
-
await
|
|
27218
|
+
await rename4(temporary, destination);
|
|
26502
27219
|
await this.syncDirectory(this.profileStateRoot);
|
|
26503
27220
|
await this.syncDirectory(this.profileRoot);
|
|
26504
27221
|
} catch (error52) {
|
|
26505
|
-
await
|
|
27222
|
+
await rm5(temporary, { force: true }).catch(() => {
|
|
26506
27223
|
});
|
|
26507
27224
|
throw error52;
|
|
26508
27225
|
}
|
|
@@ -26571,14 +27288,14 @@ var BrowserManager = class {
|
|
|
26571
27288
|
await this.ensureOwnedDirectory(this.profileRoot);
|
|
26572
27289
|
const profileDir = this.profilePath(agentId);
|
|
26573
27290
|
try {
|
|
26574
|
-
const existingProfile = await
|
|
27291
|
+
const existingProfile = await lstat6(profileDir);
|
|
26575
27292
|
if (existingProfile.isSymbolicLink() || !existingProfile.isDirectory()) {
|
|
26576
27293
|
throw new Error("browser profile path is not an owned directory");
|
|
26577
27294
|
}
|
|
26578
27295
|
} catch (error52) {
|
|
26579
27296
|
if (error52.code !== "ENOENT") throw error52;
|
|
26580
27297
|
}
|
|
26581
|
-
await
|
|
27298
|
+
await mkdir5(profileDir, { recursive: true, mode: 448 });
|
|
26582
27299
|
const adapter = await this.opts.factory.open({
|
|
26583
27300
|
agentId,
|
|
26584
27301
|
profileDir,
|
|
@@ -26657,7 +27374,7 @@ var BrowserManager = class {
|
|
|
26657
27374
|
purgeId
|
|
26658
27375
|
});
|
|
26659
27376
|
await this.closeLocked(agentId, "stopped");
|
|
26660
|
-
await
|
|
27377
|
+
await rm5(this.profilePath(agentId), { recursive: true, force: true, maxRetries: 3 });
|
|
26661
27378
|
await this.syncDirectory(this.profileRoot);
|
|
26662
27379
|
});
|
|
26663
27380
|
}
|
|
@@ -30073,18 +30790,18 @@ function createGithubPushOrchestrator(input) {
|
|
|
30073
30790
|
}
|
|
30074
30791
|
|
|
30075
30792
|
// src/tool-packs/github/git-bridge.ts
|
|
30076
|
-
import { spawn as
|
|
30793
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
30077
30794
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
30078
|
-
import { chmod as
|
|
30079
|
-
import { dirname as
|
|
30795
|
+
import { chmod as chmod4, lstat as lstat8, mkdir as mkdir6, realpath as realpath5, rm as rm6 } from "node:fs/promises";
|
|
30796
|
+
import { dirname as dirname7, isAbsolute as isAbsolute10, join as join10, relative as relative6 } from "node:path";
|
|
30080
30797
|
|
|
30081
30798
|
// src/tool-packs/github/git-credential-broker.ts
|
|
30082
30799
|
import { createServer } from "node:http";
|
|
30083
30800
|
import { randomBytes, randomUUID as randomUUID6, timingSafeEqual } from "node:crypto";
|
|
30084
|
-
import { chmod as
|
|
30085
|
-
import { isAbsolute as
|
|
30801
|
+
import { chmod as chmod3, lstat as lstat7, realpath as realpath4, writeFile as writeFile2 } from "node:fs/promises";
|
|
30802
|
+
import { isAbsolute as isAbsolute9, join as join9, relative as relative5 } from "node:path";
|
|
30086
30803
|
var MAX_REQUEST_BYTES = 16 * 1024;
|
|
30087
|
-
var
|
|
30804
|
+
var FILE_MODE2 = 384;
|
|
30088
30805
|
var HELPER_SOURCE = String.raw`'use strict';
|
|
30089
30806
|
const http = require('node:http');
|
|
30090
30807
|
|
|
@@ -30195,8 +30912,8 @@ async function readBoundedBody2(request) {
|
|
|
30195
30912
|
return Buffer.concat(chunks, size);
|
|
30196
30913
|
}
|
|
30197
30914
|
function assertChildPath(parent, child) {
|
|
30198
|
-
const path =
|
|
30199
|
-
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") ||
|
|
30915
|
+
const path = relative5(parent, child);
|
|
30916
|
+
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute9(path)) {
|
|
30200
30917
|
throw new Error("Git credential helper path escaped its private run directory");
|
|
30201
30918
|
}
|
|
30202
30919
|
}
|
|
@@ -30206,15 +30923,15 @@ async function createGithubGitCredentialBroker(input) {
|
|
|
30206
30923
|
throw new Error("GitHub credential authority has expired");
|
|
30207
30924
|
}
|
|
30208
30925
|
assertRepositoryFullName(input.repositoryFullName);
|
|
30209
|
-
const rootEntry = await
|
|
30926
|
+
const rootEntry = await lstat7(input.runArtifactsRoot);
|
|
30210
30927
|
if (!rootEntry.isDirectory() || rootEntry.isSymbolicLink()) {
|
|
30211
30928
|
throw new Error("Git credential broker requires a private real run directory");
|
|
30212
30929
|
}
|
|
30213
|
-
const runRoot = await
|
|
30214
|
-
const helperPath =
|
|
30930
|
+
const runRoot = await realpath4(input.runArtifactsRoot);
|
|
30931
|
+
const helperPath = join9(runRoot, `git-credential-${randomUUID6()}.cjs`);
|
|
30215
30932
|
assertChildPath(runRoot, helperPath);
|
|
30216
|
-
await
|
|
30217
|
-
await
|
|
30933
|
+
await writeFile2(helperPath, HELPER_SOURCE, { flag: "wx", mode: FILE_MODE2 });
|
|
30934
|
+
await chmod3(helperPath, FILE_MODE2);
|
|
30218
30935
|
const capability2 = randomBytes(32).toString("base64url");
|
|
30219
30936
|
const expectedPath = `${input.repositoryFullName}.git`;
|
|
30220
30937
|
let closed = false;
|
|
@@ -30285,7 +31002,7 @@ password=${input.accessToken}
|
|
|
30285
31002
|
}
|
|
30286
31003
|
|
|
30287
31004
|
// src/tool-packs/github/git-bridge.ts
|
|
30288
|
-
var
|
|
31005
|
+
var DIRECTORY_MODE2 = 448;
|
|
30289
31006
|
var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
|
|
30290
31007
|
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
30291
31008
|
var SHA = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/;
|
|
@@ -30309,27 +31026,27 @@ ${stderr}`;
|
|
|
30309
31026
|
);
|
|
30310
31027
|
return explicitGithubRefusal ? "provider_rejected" : "command_failed";
|
|
30311
31028
|
}
|
|
30312
|
-
function
|
|
30313
|
-
const path =
|
|
30314
|
-
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") ||
|
|
31029
|
+
function assertBelow2(parent, child, label) {
|
|
31030
|
+
const path = relative6(parent, child);
|
|
31031
|
+
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute10(path)) {
|
|
30315
31032
|
throw new GithubGitProcessError("invalid_input");
|
|
30316
31033
|
}
|
|
30317
31034
|
void label;
|
|
30318
31035
|
}
|
|
30319
|
-
async function
|
|
30320
|
-
const entry = await
|
|
31036
|
+
async function requireRealDirectory2(path, label) {
|
|
31037
|
+
const entry = await lstat8(path).catch(() => null);
|
|
30321
31038
|
if (!entry || !entry.isDirectory() || entry.isSymbolicLink()) {
|
|
30322
31039
|
void label;
|
|
30323
31040
|
throw new GithubGitProcessError("invalid_input");
|
|
30324
31041
|
}
|
|
30325
|
-
return
|
|
31042
|
+
return realpath5(path);
|
|
30326
31043
|
}
|
|
30327
31044
|
async function validateTokenlessPaths(command) {
|
|
30328
31045
|
if (command.kind === "clone-from-bridge") {
|
|
30329
|
-
if (!
|
|
30330
|
-
const parent = await
|
|
30331
|
-
|
|
30332
|
-
const destination = await
|
|
31046
|
+
if (!isAbsolute10(command.destination)) throw new GithubGitProcessError("invalid_input");
|
|
31047
|
+
const parent = await requireRealDirectory2(dirname7(command.destination), "clone parent");
|
|
31048
|
+
assertBelow2(parent, command.destination, "clone destination");
|
|
31049
|
+
const destination = await lstat8(command.destination).catch((error52) => {
|
|
30333
31050
|
if (error52.code === "ENOENT") return null;
|
|
30334
31051
|
throw error52;
|
|
30335
31052
|
});
|
|
@@ -30337,8 +31054,8 @@ async function validateTokenlessPaths(command) {
|
|
|
30337
31054
|
return;
|
|
30338
31055
|
}
|
|
30339
31056
|
if ("repositoryPath" in command) {
|
|
30340
|
-
if (!
|
|
30341
|
-
const repositoryPath5 = await
|
|
31057
|
+
if (!isAbsolute10(command.repositoryPath)) throw new GithubGitProcessError("invalid_input");
|
|
31058
|
+
const repositoryPath5 = await requireRealDirectory2(command.repositoryPath, "repository path");
|
|
30342
31059
|
if (repositoryPath5 !== command.repositoryPath) throw new GithubGitProcessError("invalid_input");
|
|
30343
31060
|
}
|
|
30344
31061
|
}
|
|
@@ -30417,13 +31134,13 @@ async function runGit(input, args, env) {
|
|
|
30417
31134
|
if (input.authoritySignal.aborted || input.cancelledNow()) {
|
|
30418
31135
|
throw new GithubGitProcessError("cancelled");
|
|
30419
31136
|
}
|
|
30420
|
-
if (!
|
|
31137
|
+
if (!isAbsolute10(input.executablePath)) throw new GithubGitProcessError("invalid_input");
|
|
30421
31138
|
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
30422
31139
|
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > DEFAULT_TIMEOUT_MS2) {
|
|
30423
31140
|
throw new GithubGitProcessError("invalid_input");
|
|
30424
31141
|
}
|
|
30425
31142
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
30426
|
-
const child =
|
|
31143
|
+
const child = spawn6(input.executablePath, [...input.commandPrefixArgs ?? [], ...args], {
|
|
30427
31144
|
cwd: input.trustedCwd,
|
|
30428
31145
|
env,
|
|
30429
31146
|
shell: false,
|
|
@@ -30523,7 +31240,7 @@ function tokenlessArgs(command) {
|
|
|
30523
31240
|
switch (command.kind) {
|
|
30524
31241
|
case "clone-from-bridge":
|
|
30525
31242
|
assertRef(command.branch);
|
|
30526
|
-
if (!
|
|
31243
|
+
if (!isAbsolute10(command.destination)) throw new GithubGitProcessError("invalid_input");
|
|
30527
31244
|
return [
|
|
30528
31245
|
"clone",
|
|
30529
31246
|
"--no-recurse-submodules",
|
|
@@ -30535,7 +31252,7 @@ function tokenlessArgs(command) {
|
|
|
30535
31252
|
];
|
|
30536
31253
|
case "fetch-from-bridge":
|
|
30537
31254
|
assertFetchRefspecs(command.refspecs);
|
|
30538
|
-
if (!
|
|
31255
|
+
if (!isAbsolute10(command.repositoryPath)) throw new GithubGitProcessError("invalid_input");
|
|
30539
31256
|
return [
|
|
30540
31257
|
"-C",
|
|
30541
31258
|
command.repositoryPath,
|
|
@@ -30547,7 +31264,7 @@ function tokenlessArgs(command) {
|
|
|
30547
31264
|
...command.refspecs
|
|
30548
31265
|
];
|
|
30549
31266
|
case "copy-commit-to-bridge":
|
|
30550
|
-
if (!
|
|
31267
|
+
if (!isAbsolute10(command.repositoryPath) || !SHA.test(command.sha)) {
|
|
30551
31268
|
throw new GithubGitProcessError("invalid_input");
|
|
30552
31269
|
}
|
|
30553
31270
|
return [
|
|
@@ -30559,11 +31276,11 @@ function tokenlessArgs(command) {
|
|
|
30559
31276
|
`${command.sha}:refs/zixt/push-source`
|
|
30560
31277
|
];
|
|
30561
31278
|
case "rev-parse":
|
|
30562
|
-
if (!
|
|
31279
|
+
if (!isAbsolute10(command.repositoryPath)) throw new GithubGitProcessError("invalid_input");
|
|
30563
31280
|
assertRef(command.ref);
|
|
30564
31281
|
return ["-C", command.repositoryPath, "rev-parse", "--verify", `${command.ref}^{commit}`];
|
|
30565
31282
|
case "remote-configure":
|
|
30566
|
-
if (!
|
|
31283
|
+
if (!isAbsolute10(command.repositoryPath) || !REPOSITORY.test(command.repositoryFullName)) {
|
|
30567
31284
|
throw new GithubGitProcessError("invalid_input");
|
|
30568
31285
|
}
|
|
30569
31286
|
return [
|
|
@@ -30575,7 +31292,7 @@ function tokenlessArgs(command) {
|
|
|
30575
31292
|
`https://github.com/${command.repositoryFullName}.git`
|
|
30576
31293
|
];
|
|
30577
31294
|
case "status":
|
|
30578
|
-
if (!
|
|
31295
|
+
if (!isAbsolute10(command.repositoryPath)) throw new GithubGitProcessError("invalid_input");
|
|
30579
31296
|
return [
|
|
30580
31297
|
"-C",
|
|
30581
31298
|
command.repositoryPath,
|
|
@@ -30593,21 +31310,21 @@ function createGithubGitBridge(input) {
|
|
|
30593
31310
|
let closed = false;
|
|
30594
31311
|
let rootsPromise;
|
|
30595
31312
|
const roots = () => rootsPromise ??= (async () => {
|
|
30596
|
-
const trustedCwd = await
|
|
30597
|
-
const runRoot = await
|
|
30598
|
-
const bridges = await
|
|
31313
|
+
const trustedCwd = await requireRealDirectory2(input.trustedCwd, "trusted cwd");
|
|
31314
|
+
const runRoot = await requireRealDirectory2(input.artifacts.runRoot, "run root");
|
|
31315
|
+
const bridges = await requireRealDirectory2(
|
|
30599
31316
|
input.artifacts.gitBridgesDirectory,
|
|
30600
31317
|
"git bridge root"
|
|
30601
31318
|
);
|
|
30602
|
-
|
|
31319
|
+
assertBelow2(runRoot, bridges, "git bridge root");
|
|
30603
31320
|
if (trustedCwd !== runRoot) throw new GithubGitProcessError("invalid_input");
|
|
30604
31321
|
return { trustedCwd, runRoot, bridges };
|
|
30605
31322
|
})();
|
|
30606
31323
|
const requireBridge = async (value) => {
|
|
30607
31324
|
const current = await roots();
|
|
30608
|
-
if (!
|
|
30609
|
-
const real = await
|
|
30610
|
-
|
|
31325
|
+
if (!isAbsolute10(value) || !active.has(value)) throw new GithubGitProcessError("invalid_input");
|
|
31326
|
+
const real = await requireRealDirectory2(value, "git bridge");
|
|
31327
|
+
assertBelow2(current.bridges, real, "git bridge");
|
|
30611
31328
|
if (real !== value) throw new GithubGitProcessError("invalid_input");
|
|
30612
31329
|
return real;
|
|
30613
31330
|
};
|
|
@@ -30615,10 +31332,10 @@ function createGithubGitBridge(input) {
|
|
|
30615
31332
|
async createPrivateBridge() {
|
|
30616
31333
|
if (closed) throw new GithubGitProcessError("cancelled");
|
|
30617
31334
|
const current = await roots();
|
|
30618
|
-
const path =
|
|
30619
|
-
|
|
30620
|
-
await
|
|
30621
|
-
await
|
|
31335
|
+
const path = join10(current.bridges, `${randomUUID7()}.git`);
|
|
31336
|
+
assertBelow2(current.bridges, path, "git bridge");
|
|
31337
|
+
await mkdir6(path, { mode: DIRECTORY_MODE2 });
|
|
31338
|
+
await chmod4(path, DIRECTORY_MODE2);
|
|
30622
31339
|
try {
|
|
30623
31340
|
await runGit(
|
|
30624
31341
|
{ ...input, trustedCwd: current.trustedCwd },
|
|
@@ -30631,18 +31348,18 @@ function createGithubGitBridge(input) {
|
|
|
30631
31348
|
],
|
|
30632
31349
|
minimalEnvironment(input.artifacts)
|
|
30633
31350
|
);
|
|
30634
|
-
const real = await
|
|
30635
|
-
|
|
30636
|
-
const hooks =
|
|
30637
|
-
await
|
|
30638
|
-
await
|
|
30639
|
-
await
|
|
30640
|
-
const config2 =
|
|
30641
|
-
await
|
|
31351
|
+
const real = await requireRealDirectory2(path, "git bridge");
|
|
31352
|
+
assertBelow2(current.bridges, real, "git bridge");
|
|
31353
|
+
const hooks = join10(real, "hooks");
|
|
31354
|
+
await rm6(hooks, { recursive: true, force: true });
|
|
31355
|
+
await mkdir6(hooks, { mode: DIRECTORY_MODE2 });
|
|
31356
|
+
await chmod4(hooks, DIRECTORY_MODE2);
|
|
31357
|
+
const config2 = join10(real, "config");
|
|
31358
|
+
await chmod4(config2, 384);
|
|
30642
31359
|
active.add(real);
|
|
30643
31360
|
return real;
|
|
30644
31361
|
} catch (error52) {
|
|
30645
|
-
await
|
|
31362
|
+
await rm6(path, { recursive: true, force: true }).catch(() => {
|
|
30646
31363
|
});
|
|
30647
31364
|
throw error52;
|
|
30648
31365
|
}
|
|
@@ -30736,7 +31453,7 @@ function createGithubGitBridge(input) {
|
|
|
30736
31453
|
},
|
|
30737
31454
|
async destroyPrivateBridge(path) {
|
|
30738
31455
|
const bridge = await requireBridge(path);
|
|
30739
|
-
await
|
|
31456
|
+
await rm6(bridge, { recursive: true, force: true });
|
|
30740
31457
|
active.delete(bridge);
|
|
30741
31458
|
credentialed2.delete(bridge);
|
|
30742
31459
|
},
|
|
@@ -30744,7 +31461,7 @@ function createGithubGitBridge(input) {
|
|
|
30744
31461
|
if (closed) return;
|
|
30745
31462
|
closed = true;
|
|
30746
31463
|
const paths = [...active];
|
|
30747
|
-
await Promise.all(paths.map((path) =>
|
|
31464
|
+
await Promise.all(paths.map((path) => rm6(path, { recursive: true, force: true })));
|
|
30748
31465
|
active.clear();
|
|
30749
31466
|
credentialed2.clear();
|
|
30750
31467
|
}
|
|
@@ -31085,12 +31802,12 @@ function createRepositoryTools(runtime) {
|
|
|
31085
31802
|
|
|
31086
31803
|
// src/tool-packs/github/workspace.ts
|
|
31087
31804
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
31088
|
-
import { chmod as
|
|
31089
|
-
import { isAbsolute as
|
|
31805
|
+
import { chmod as chmod5, lstat as lstat9, mkdir as mkdir7, readFile as readFile8, realpath as realpath6, rename as rename5, rm as rm7, writeFile as writeFile3 } from "node:fs/promises";
|
|
31806
|
+
import { isAbsolute as isAbsolute11, join as join11, relative as relative7, resolve as resolve8 } from "node:path";
|
|
31090
31807
|
var SAFE_LOCAL_ID = /^[A-Za-z0-9_-]{1,200}$/;
|
|
31091
31808
|
var FULL_NAME2 = /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/;
|
|
31092
|
-
var
|
|
31093
|
-
var
|
|
31809
|
+
var DIRECTORY_MODE3 = 448;
|
|
31810
|
+
var FILE_MODE3 = 384;
|
|
31094
31811
|
var METADATA_VERSION = 1;
|
|
31095
31812
|
var workspaceLocks = /* @__PURE__ */ new Map();
|
|
31096
31813
|
function assertSafeLocalId(value, label) {
|
|
@@ -31102,37 +31819,37 @@ function hasControlCharacter2(value) {
|
|
|
31102
31819
|
return codePoint <= 31 || codePoint === 127;
|
|
31103
31820
|
});
|
|
31104
31821
|
}
|
|
31105
|
-
function
|
|
31106
|
-
const path =
|
|
31107
|
-
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") ||
|
|
31822
|
+
function assertBelow3(parent, child, label) {
|
|
31823
|
+
const path = relative7(parent, child);
|
|
31824
|
+
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute11(path)) {
|
|
31108
31825
|
throw new Error(`${label} escaped the task workspace`);
|
|
31109
31826
|
}
|
|
31110
31827
|
}
|
|
31111
31828
|
function samePath(left, right) {
|
|
31112
|
-
return process.platform === "win32" ?
|
|
31829
|
+
return process.platform === "win32" ? resolve8(left).toLowerCase() === resolve8(right).toLowerCase() : resolve8(left) === resolve8(right);
|
|
31113
31830
|
}
|
|
31114
|
-
async function
|
|
31115
|
-
const entry = await
|
|
31831
|
+
async function requireRealDirectory3(path, label) {
|
|
31832
|
+
const entry = await lstat9(path).catch(() => null);
|
|
31116
31833
|
if (!entry || !entry.isDirectory() || entry.isSymbolicLink()) {
|
|
31117
31834
|
throw new Error(`${label} must be a real directory, not a symbolic link or junction`);
|
|
31118
31835
|
}
|
|
31119
|
-
const real = await
|
|
31836
|
+
const real = await realpath6(path);
|
|
31120
31837
|
if (!samePath(real, path)) {
|
|
31121
31838
|
throw new Error(`${label} must not traverse a symbolic link or junction`);
|
|
31122
31839
|
}
|
|
31123
31840
|
return real;
|
|
31124
31841
|
}
|
|
31125
31842
|
async function createOrRequirePrivateDirectory(parent, name, label) {
|
|
31126
|
-
const path =
|
|
31127
|
-
|
|
31843
|
+
const path = join11(parent, name);
|
|
31844
|
+
assertBelow3(parent, path, label);
|
|
31128
31845
|
try {
|
|
31129
|
-
await
|
|
31846
|
+
await mkdir7(path, { mode: DIRECTORY_MODE3 });
|
|
31130
31847
|
} catch (error52) {
|
|
31131
31848
|
if (error52.code !== "EEXIST") throw error52;
|
|
31132
31849
|
}
|
|
31133
|
-
const real = await
|
|
31134
|
-
|
|
31135
|
-
await
|
|
31850
|
+
const real = await requireRealDirectory3(path, label);
|
|
31851
|
+
assertBelow3(parent, real, label);
|
|
31852
|
+
await chmod5(real, DIRECTORY_MODE3);
|
|
31136
31853
|
return real;
|
|
31137
31854
|
}
|
|
31138
31855
|
function parseMetadata(text) {
|
|
@@ -31149,7 +31866,7 @@ function parseMetadata(text) {
|
|
|
31149
31866
|
}
|
|
31150
31867
|
async function pathExists(path) {
|
|
31151
31868
|
try {
|
|
31152
|
-
await
|
|
31869
|
+
await lstat9(path);
|
|
31153
31870
|
return true;
|
|
31154
31871
|
} catch (error52) {
|
|
31155
31872
|
if (error52.code === "ENOENT") return false;
|
|
@@ -31176,7 +31893,7 @@ async function createGithubWorkspaceService(input) {
|
|
|
31176
31893
|
const grant = validateGithubTaskGrant(input.grant);
|
|
31177
31894
|
assertSafeLocalId(input.context.agentId, "agent ID");
|
|
31178
31895
|
assertSafeLocalId(input.context.taskId, "task ID");
|
|
31179
|
-
const taskRoot = await
|
|
31896
|
+
const taskRoot = await requireRealDirectory3(input.context.taskRoot, "task workspace");
|
|
31180
31897
|
const repositoriesRoot = await createOrRequirePrivateDirectory(
|
|
31181
31898
|
taskRoot,
|
|
31182
31899
|
"repositories",
|
|
@@ -31206,33 +31923,33 @@ async function createGithubWorkspaceService(input) {
|
|
|
31206
31923
|
if (expectedFullName !== void 0 && repository.fullName !== expectedFullName) {
|
|
31207
31924
|
throw new Error("GitHub repository name does not match this task grant");
|
|
31208
31925
|
}
|
|
31209
|
-
const destination =
|
|
31210
|
-
const metadataPath =
|
|
31926
|
+
const destination = join11(repositoriesRoot, parsed.data);
|
|
31927
|
+
const metadataPath = join11(metadataRoot, `${parsed.data}.json`);
|
|
31211
31928
|
if (!await pathExists(destination) || !await pathExists(metadataPath)) {
|
|
31212
31929
|
throw new Error("GitHub repository workspace has not been prepared");
|
|
31213
31930
|
}
|
|
31214
|
-
const metadataEntry = await
|
|
31931
|
+
const metadataEntry = await lstat9(metadataPath);
|
|
31215
31932
|
if (!metadataEntry.isFile() || metadataEntry.isSymbolicLink()) {
|
|
31216
31933
|
throw new Error("GitHub workspace metadata is invalid");
|
|
31217
31934
|
}
|
|
31218
|
-
const metadata = parseMetadata(await
|
|
31935
|
+
const metadata = parseMetadata(await readFile8(metadataPath, "utf8"));
|
|
31219
31936
|
if (metadata.repositoryId !== parsed.data || metadata.fullName !== repository.fullName || !samePath(metadata.path, destination)) {
|
|
31220
31937
|
throw new Error("GitHub workspace metadata does not match this repository");
|
|
31221
31938
|
}
|
|
31222
|
-
const real = await
|
|
31223
|
-
|
|
31939
|
+
const real = await requireRealDirectory3(destination, "GitHub repository");
|
|
31940
|
+
assertBelow3(repositoriesRoot, real, "repository path");
|
|
31224
31941
|
return real;
|
|
31225
31942
|
};
|
|
31226
31943
|
const cloneRepository = async (clone2) => {
|
|
31227
|
-
const destination =
|
|
31228
|
-
const metadataPath =
|
|
31229
|
-
|
|
31230
|
-
|
|
31944
|
+
const destination = join11(repositoriesRoot, clone2.repositoryId);
|
|
31945
|
+
const metadataPath = join11(metadataRoot, `${clone2.repositoryId}.json`);
|
|
31946
|
+
assertBelow3(repositoriesRoot, destination, "repository path");
|
|
31947
|
+
assertBelow3(metadataRoot, metadataPath, "repository metadata");
|
|
31231
31948
|
if (await pathExists(destination) || await pathExists(metadataPath)) {
|
|
31232
31949
|
throw new Error("GitHub repository workspace already exists or is inconsistent");
|
|
31233
31950
|
}
|
|
31234
|
-
const temporary =
|
|
31235
|
-
|
|
31951
|
+
const temporary = join11(repositoriesRoot, `.clone-${randomUUID8()}`);
|
|
31952
|
+
assertBelow3(repositoriesRoot, temporary, "temporary clone");
|
|
31236
31953
|
try {
|
|
31237
31954
|
await input.git.clone({
|
|
31238
31955
|
accessToken: clone2.accessToken,
|
|
@@ -31241,8 +31958,8 @@ async function createGithubWorkspaceService(input) {
|
|
|
31241
31958
|
destination: temporary,
|
|
31242
31959
|
branch: clone2.defaultBranch
|
|
31243
31960
|
});
|
|
31244
|
-
const temporaryReal = await
|
|
31245
|
-
|
|
31961
|
+
const temporaryReal = await requireRealDirectory3(temporary, "temporary GitHub clone");
|
|
31962
|
+
assertBelow3(repositoriesRoot, temporaryReal, "temporary clone");
|
|
31246
31963
|
await input.git.configureOrigin({
|
|
31247
31964
|
repositoryPath: temporaryReal,
|
|
31248
31965
|
repositoryFullName: clone2.fullName
|
|
@@ -31256,27 +31973,27 @@ async function createGithubWorkspaceService(input) {
|
|
|
31256
31973
|
path: destination,
|
|
31257
31974
|
...clone2.createIntentId === void 0 ? {} : { createIntentId: clone2.createIntentId }
|
|
31258
31975
|
};
|
|
31259
|
-
const metadataTemporary =
|
|
31260
|
-
|
|
31261
|
-
await
|
|
31976
|
+
const metadataTemporary = join11(metadataRoot, `.${clone2.repositoryId}-${randomUUID8()}.tmp`);
|
|
31977
|
+
assertBelow3(metadataRoot, metadataTemporary, "temporary repository metadata");
|
|
31978
|
+
await writeFile3(metadataTemporary, `${JSON.stringify(metadata)}
|
|
31262
31979
|
`, {
|
|
31263
31980
|
flag: "wx",
|
|
31264
|
-
mode:
|
|
31981
|
+
mode: FILE_MODE3
|
|
31265
31982
|
});
|
|
31266
|
-
await
|
|
31983
|
+
await chmod5(metadataTemporary, FILE_MODE3);
|
|
31267
31984
|
try {
|
|
31268
|
-
await
|
|
31985
|
+
await rename5(temporaryReal, destination);
|
|
31269
31986
|
try {
|
|
31270
|
-
await
|
|
31987
|
+
await rename5(metadataTemporary, metadataPath);
|
|
31271
31988
|
} catch (error52) {
|
|
31272
|
-
await
|
|
31989
|
+
await rm7(destination, { recursive: true, force: true });
|
|
31273
31990
|
throw error52;
|
|
31274
31991
|
}
|
|
31275
31992
|
} finally {
|
|
31276
|
-
await
|
|
31993
|
+
await rm7(metadataTemporary, { force: true }).catch(() => {
|
|
31277
31994
|
});
|
|
31278
31995
|
}
|
|
31279
|
-
const path = await
|
|
31996
|
+
const path = await requireRealDirectory3(destination, "GitHub repository");
|
|
31280
31997
|
return {
|
|
31281
31998
|
provider: "github",
|
|
31282
31999
|
repositoryId: clone2.repositoryId,
|
|
@@ -31285,16 +32002,16 @@ async function createGithubWorkspaceService(input) {
|
|
|
31285
32002
|
headSha
|
|
31286
32003
|
};
|
|
31287
32004
|
} finally {
|
|
31288
|
-
await
|
|
32005
|
+
await rm7(temporary, { recursive: true, force: true }).catch(() => {
|
|
31289
32006
|
});
|
|
31290
32007
|
}
|
|
31291
32008
|
};
|
|
31292
32009
|
const prepareRepository = async (authority) => {
|
|
31293
32010
|
const { repository } = authority;
|
|
31294
|
-
const destination =
|
|
31295
|
-
const metadataPath =
|
|
31296
|
-
|
|
31297
|
-
|
|
32011
|
+
const destination = join11(repositoriesRoot, repository.repositoryId);
|
|
32012
|
+
const metadataPath = join11(metadataRoot, `${repository.repositoryId}.json`);
|
|
32013
|
+
assertBelow3(repositoriesRoot, destination, "repository path");
|
|
32014
|
+
assertBelow3(metadataRoot, metadataPath, "repository metadata");
|
|
31298
32015
|
return withWorkspaceLock(destination, async () => {
|
|
31299
32016
|
const destinationExists = await pathExists(destination);
|
|
31300
32017
|
const metadataExists = await pathExists(metadataPath);
|
|
@@ -31313,16 +32030,16 @@ async function createGithubWorkspaceService(input) {
|
|
|
31313
32030
|
expiresAt: authority.expiresAt
|
|
31314
32031
|
});
|
|
31315
32032
|
}
|
|
31316
|
-
const metadataEntry = await
|
|
32033
|
+
const metadataEntry = await lstat9(metadataPath);
|
|
31317
32034
|
if (!metadataEntry.isFile() || metadataEntry.isSymbolicLink()) {
|
|
31318
32035
|
throw new Error("GitHub workspace metadata is invalid");
|
|
31319
32036
|
}
|
|
31320
|
-
const metadata = parseMetadata(await
|
|
32037
|
+
const metadata = parseMetadata(await readFile8(metadataPath, "utf8"));
|
|
31321
32038
|
if (metadata.repositoryId !== repository.repositoryId || metadata.fullName !== repository.fullName || !samePath(metadata.path, destination)) {
|
|
31322
32039
|
throw new Error("GitHub workspace metadata does not match this repository");
|
|
31323
32040
|
}
|
|
31324
|
-
const repositoryPath6 = await
|
|
31325
|
-
|
|
32041
|
+
const repositoryPath6 = await requireRealDirectory3(destination, "GitHub repository");
|
|
32042
|
+
assertBelow3(repositoriesRoot, repositoryPath6, "repository path");
|
|
31326
32043
|
if (!authority.operations.includes("repository.fetch")) {
|
|
31327
32044
|
throw new Error("GitHub authority cannot refresh this repository");
|
|
31328
32045
|
}
|
|
@@ -31457,7 +32174,7 @@ async function createGithubWorkspaceService(input) {
|
|
|
31457
32174
|
throw new Error("GitHub created repository is outside this installation");
|
|
31458
32175
|
}
|
|
31459
32176
|
parseGitRef(cloneInput.repository.defaultBranch, "default branch");
|
|
31460
|
-
return withWorkspaceLock(
|
|
32177
|
+
return withWorkspaceLock(join11(repositoriesRoot, repositoryId2), async () => {
|
|
31461
32178
|
const prepared = await cloneRepository({
|
|
31462
32179
|
repositoryId: repositoryId2,
|
|
31463
32180
|
fullName: cloneInput.repository.fullName,
|
|
@@ -32998,8 +33715,8 @@ function createDefaultToolPackRegistry() {
|
|
|
32998
33715
|
}
|
|
32999
33716
|
|
|
33000
33717
|
// src/runners/attachments.ts
|
|
33001
|
-
import { mkdir as
|
|
33002
|
-
import { join as
|
|
33718
|
+
import { mkdir as mkdir8, writeFile as writeFile4 } from "node:fs/promises";
|
|
33719
|
+
import { join as join12 } from "node:path";
|
|
33003
33720
|
var WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
|
|
33004
33721
|
function sanitizeAttachmentFileName(name) {
|
|
33005
33722
|
const base = name.split(/[/\\]/).pop() ?? "";
|
|
@@ -33028,10 +33745,10 @@ async function materializeAttachments(task, taskRoot) {
|
|
|
33028
33745
|
`attached file "${attachment.name}" arrived incomplete (${bytes.byteLength} of ${attachment.size} bytes)`
|
|
33029
33746
|
);
|
|
33030
33747
|
}
|
|
33031
|
-
const directory =
|
|
33032
|
-
await
|
|
33033
|
-
const path =
|
|
33034
|
-
await
|
|
33748
|
+
const directory = join12(taskRoot, ".zixt-attachments", task.taskId, attachment.id);
|
|
33749
|
+
await mkdir8(directory, { recursive: true });
|
|
33750
|
+
const path = join12(directory, sanitizeAttachmentFileName(attachment.name));
|
|
33751
|
+
await writeFile4(path, bytes);
|
|
33035
33752
|
materialized.push({
|
|
33036
33753
|
path,
|
|
33037
33754
|
name: attachment.name,
|
|
@@ -33995,7 +34712,7 @@ function createAskUserServer() {
|
|
|
33995
34712
|
}
|
|
33996
34713
|
|
|
33997
34714
|
// src/runners/runner-env.ts
|
|
33998
|
-
import { delimiter as delimiter2, isAbsolute as
|
|
34715
|
+
import { delimiter as delimiter2, isAbsolute as isAbsolute12 } from "node:path";
|
|
33999
34716
|
var PROVIDER_AUTHORITY_PREFIXES = ["GH_", "GITHUB_", "GIT_", "SSH_"];
|
|
34000
34717
|
var HOST_AUTHORITY_PREFIXES = [
|
|
34001
34718
|
"ZIXT_",
|
|
@@ -34054,7 +34771,7 @@ function inheritedValue(env, name) {
|
|
|
34054
34771
|
}
|
|
34055
34772
|
function sanitizeInheritedSearchPath(path) {
|
|
34056
34773
|
if (!path) return "";
|
|
34057
|
-
return path.split(delimiter2).filter((entry) => entry !== "" &&
|
|
34774
|
+
return path.split(delimiter2).filter((entry) => entry !== "" && isAbsolute12(entry)).join(delimiter2);
|
|
34058
34775
|
}
|
|
34059
34776
|
function buildRunnerEnv(input) {
|
|
34060
34777
|
const env = {};
|
|
@@ -34135,11 +34852,11 @@ function buildRunnerEnv(input) {
|
|
|
34135
34852
|
// src/runners/github-shell-auth.ts
|
|
34136
34853
|
import { execFile } from "node:child_process";
|
|
34137
34854
|
import { randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
34138
|
-
import { chmod as
|
|
34855
|
+
import { chmod as chmod6, lstat as lstat10, mkdir as mkdir9, realpath as realpath7, writeFile as writeFile5 } from "node:fs/promises";
|
|
34139
34856
|
import { createServer as createServer3 } from "node:http";
|
|
34140
|
-
import { isAbsolute as
|
|
34857
|
+
import { isAbsolute as isAbsolute13, join as join13, relative as relative8 } from "node:path";
|
|
34141
34858
|
var MAX_REQUEST_BYTES2 = 16 * 1024;
|
|
34142
|
-
var
|
|
34859
|
+
var DIRECTORY_MODE4 = 448;
|
|
34143
34860
|
var PRIVATE_FILE_MODE = 384;
|
|
34144
34861
|
var EXECUTABLE_FILE_MODE = 448;
|
|
34145
34862
|
var GIT_HELPER_SOURCE = String.raw`'use strict';
|
|
@@ -34343,7 +35060,7 @@ function parseGhInvocation(body) {
|
|
|
34343
35060
|
}
|
|
34344
35061
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
34345
35062
|
const { args, cwd } = value;
|
|
34346
|
-
if (!Array.isArray(args) || args.length > 256 || args.some((argument) => typeof argument !== "string" || argument.length > 4096) || typeof cwd !== "string" || cwd.length === 0 || cwd.length > 4096 || !
|
|
35063
|
+
if (!Array.isArray(args) || args.length > 256 || args.some((argument) => typeof argument !== "string" || argument.length > 4096) || typeof cwd !== "string" || cwd.length === 0 || cwd.length > 4096 || !isAbsolute13(cwd)) {
|
|
34347
35064
|
return null;
|
|
34348
35065
|
}
|
|
34349
35066
|
return { args, cwd };
|
|
@@ -34501,8 +35218,8 @@ function activationCredential(grant, now = Date.now()) {
|
|
|
34501
35218
|
return expiresAt.getTime() <= now ? null : { accessToken: grant.accessToken, expiresAt };
|
|
34502
35219
|
}
|
|
34503
35220
|
function assertChildPath2(parent, child) {
|
|
34504
|
-
const path =
|
|
34505
|
-
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") ||
|
|
35221
|
+
const path = relative8(parent, child);
|
|
35222
|
+
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute13(path)) {
|
|
34506
35223
|
throw new Error("GitHub shell helper path escaped its private run directory");
|
|
34507
35224
|
}
|
|
34508
35225
|
}
|
|
@@ -34513,19 +35230,19 @@ function quoteForPosixShell(value) {
|
|
|
34513
35230
|
return quoteForGitShell2(value);
|
|
34514
35231
|
}
|
|
34515
35232
|
async function writePrivate(path, content, executable = false) {
|
|
34516
|
-
await
|
|
35233
|
+
await writeFile5(path, content, {
|
|
34517
35234
|
flag: "wx",
|
|
34518
35235
|
mode: executable ? EXECUTABLE_FILE_MODE : PRIVATE_FILE_MODE
|
|
34519
35236
|
});
|
|
34520
|
-
await
|
|
35237
|
+
await chmod6(path, executable ? EXECUTABLE_FILE_MODE : PRIVATE_FILE_MODE);
|
|
34521
35238
|
}
|
|
34522
35239
|
async function prepareHelpers(input) {
|
|
34523
|
-
const rootEntry = await
|
|
35240
|
+
const rootEntry = await lstat10(input.runRoot);
|
|
34524
35241
|
if (!rootEntry.isDirectory() || rootEntry.isSymbolicLink()) {
|
|
34525
35242
|
throw new Error("GitHub shell authentication requires a private real run directory");
|
|
34526
35243
|
}
|
|
34527
|
-
const runRoot = await
|
|
34528
|
-
const helperPath =
|
|
35244
|
+
const runRoot = await realpath7(input.runRoot);
|
|
35245
|
+
const helperPath = join13(runRoot, "github-shell-git-credential.cjs");
|
|
34529
35246
|
assertChildPath2(runRoot, helperPath);
|
|
34530
35247
|
await writePrivate(helperPath, GIT_HELPER_SOURCE);
|
|
34531
35248
|
if (!input.ghExecutablePath) {
|
|
@@ -34536,14 +35253,14 @@ async function prepareHelpers(input) {
|
|
|
34536
35253
|
wrapperSourcePath: null
|
|
34537
35254
|
};
|
|
34538
35255
|
}
|
|
34539
|
-
const shellToolsDirectory =
|
|
35256
|
+
const shellToolsDirectory = join13(runRoot, "shell-tools");
|
|
34540
35257
|
assertChildPath2(runRoot, shellToolsDirectory);
|
|
34541
|
-
await
|
|
34542
|
-
await
|
|
34543
|
-
const wrapperSourcePath =
|
|
35258
|
+
await mkdir9(shellToolsDirectory, { mode: DIRECTORY_MODE4 });
|
|
35259
|
+
await chmod6(shellToolsDirectory, DIRECTORY_MODE4);
|
|
35260
|
+
const wrapperSourcePath = join13(runRoot, "github-shell-gh-wrapper.cjs");
|
|
34544
35261
|
assertChildPath2(runRoot, wrapperSourcePath);
|
|
34545
35262
|
await writePrivate(wrapperSourcePath, GH_WRAPPER_SOURCE);
|
|
34546
|
-
const wrapperPath =
|
|
35263
|
+
const wrapperPath = join13(shellToolsDirectory, process.platform === "win32" ? "gh.cmd" : "gh");
|
|
34547
35264
|
assertChildPath2(runRoot, wrapperPath);
|
|
34548
35265
|
const launcher = process.platform === "win32" ? `@"${process.execPath.replaceAll('"', '""')}" "${wrapperSourcePath.replaceAll('"', '""')}" %*\r
|
|
34549
35266
|
` : `#!/bin/sh
|
|
@@ -34766,8 +35483,8 @@ password=${credential.accessToken}
|
|
|
34766
35483
|
}
|
|
34767
35484
|
|
|
34768
35485
|
// src/runners/working-context.ts
|
|
34769
|
-
import { spawn as
|
|
34770
|
-
import { resolve as
|
|
35486
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
35487
|
+
import { resolve as resolve9 } from "node:path";
|
|
34771
35488
|
var COMMAND_TIMEOUT_MS = 5e3;
|
|
34772
35489
|
var OUTPUT_LIMIT_BYTES = 128 * 1024;
|
|
34773
35490
|
var COMMAND_STOP_TIMEOUT_MS = 2e4;
|
|
@@ -34897,7 +35614,7 @@ async function stopWorkingContextCommand(child, childExited, options = {}) {
|
|
|
34897
35614
|
function run(command, args, cwd, env, signal) {
|
|
34898
35615
|
if (signal?.aborted) return Promise.resolve(null);
|
|
34899
35616
|
return new Promise((resolvePromise) => {
|
|
34900
|
-
const child =
|
|
35617
|
+
const child = spawn7(command, [...args], {
|
|
34901
35618
|
cwd,
|
|
34902
35619
|
env: { ...env, GIT_OPTIONAL_LOCKS: "0" },
|
|
34903
35620
|
detached: process.platform !== "win32",
|
|
@@ -35162,8 +35879,8 @@ async function repositoryState(directory, git, env, signal) {
|
|
|
35162
35879
|
const pathLines = paths.trim().split(/\r?\n/);
|
|
35163
35880
|
if (pathLines.length < 3 || !pathLines[0] || !pathLines[1] || !pathLines[2]) return null;
|
|
35164
35881
|
const root = pathLines[0];
|
|
35165
|
-
const gitDirectory =
|
|
35166
|
-
const commonDirectory =
|
|
35882
|
+
const gitDirectory = resolve9(directory, pathLines[1]);
|
|
35883
|
+
const commonDirectory = resolve9(directory, pathLines[2]);
|
|
35167
35884
|
const records = status.split(/\0|\r?\n/).filter(Boolean);
|
|
35168
35885
|
const rawBranch = statusField(records, "branch.head");
|
|
35169
35886
|
if (!rawBranch || rawBranch.length > 512 || !isSafeSingleLineDisplayText(rawBranch)) return null;
|
|
@@ -35314,657 +36031,6 @@ var WorkingContextPullRequestCache = class {
|
|
|
35314
36031
|
}
|
|
35315
36032
|
};
|
|
35316
36033
|
|
|
35317
|
-
// src/runners/run-artifacts.ts
|
|
35318
|
-
import { spawn as spawn7 } from "node:child_process";
|
|
35319
|
-
import {
|
|
35320
|
-
chmod as chmod6,
|
|
35321
|
-
lstat as lstat10,
|
|
35322
|
-
mkdir as mkdir9,
|
|
35323
|
-
open as open5,
|
|
35324
|
-
readdir as readdir3,
|
|
35325
|
-
readFile as readFile8,
|
|
35326
|
-
realpath as realpath7,
|
|
35327
|
-
rename as rename5,
|
|
35328
|
-
rm as rm7,
|
|
35329
|
-
writeFile as writeFile5
|
|
35330
|
-
} from "node:fs/promises";
|
|
35331
|
-
import { homedir as homedir4 } from "node:os";
|
|
35332
|
-
import { dirname as dirname7, isAbsolute as isAbsolute13, join as join13, relative as relative8, resolve as resolve9, sep as sep4, win32 as win322 } from "node:path";
|
|
35333
|
-
var SAFE_SEGMENT2 = /^[A-Za-z0-9_-]{1,200}$/;
|
|
35334
|
-
var DIRECTORY_MODE4 = 448;
|
|
35335
|
-
var FILE_MODE3 = 384;
|
|
35336
|
-
var CLEANUP_ATTEMPTS2 = 3;
|
|
35337
|
-
var CLEANUP_RETRY_DELAY_MS = 50;
|
|
35338
|
-
var WINDOWS_ACL_TIMEOUT_MS = 5e3;
|
|
35339
|
-
var RUNNER_GUARDIAN = String.raw`
|
|
35340
|
-
const { spawn } = require('node:child_process');
|
|
35341
|
-
|
|
35342
|
-
const quote = (value) => '"' + String(value).replace(/\\/g, '/').replaceAll('"', '""') + '"';
|
|
35343
|
-
const launchMarker = '__ZIXT_RELEASE_RUNNER__';
|
|
35344
|
-
const exitMarker = process.argv[2];
|
|
35345
|
-
const guardianNonce = process.argv[3];
|
|
35346
|
-
if (!/^[A-Za-z0-9_-]{16,200}$/.test(guardianNonce || '')) process.exit(1);
|
|
35347
|
-
const containmentNonce = process.env[${JSON.stringify(WINDOWS_CONTAINMENT_GATE_ENV)}];
|
|
35348
|
-
const containmentMarker = ${JSON.stringify(WINDOWS_CONTAINMENT_GATE_PREFIX)};
|
|
35349
|
-
let containmentReady = !containmentNonce;
|
|
35350
|
-
const maxReleaseBytes = 32 * 1024 * 1024;
|
|
35351
|
-
let target = null;
|
|
35352
|
-
let pending = Buffer.alloc(0);
|
|
35353
|
-
let stdinEnded = false;
|
|
35354
|
-
let expectedBytes = null;
|
|
35355
|
-
let config = null;
|
|
35356
|
-
let launchPending = false;
|
|
35357
|
-
let stderrTail = '';
|
|
35358
|
-
let postReleaseInput = Buffer.alloc(0);
|
|
35359
|
-
let reported = false;
|
|
35360
|
-
const report = (result) => {
|
|
35361
|
-
if (reported) return;
|
|
35362
|
-
reported = true;
|
|
35363
|
-
process.stdout.write(
|
|
35364
|
-
'\n' + exitMarker + JSON.stringify({ ...result, stderrTail }) + '\n'
|
|
35365
|
-
);
|
|
35366
|
-
setInterval(() => {}, 2_147_483_647);
|
|
35367
|
-
};
|
|
35368
|
-
|
|
35369
|
-
// Job helper setup + assignment each have a bounded Windows phase. This gate
|
|
35370
|
-
// must outlive both, including slow first-run Add-Type under endpoint protection.
|
|
35371
|
-
const prelaunchTimeout = setTimeout(() => process.exit(1), 130_000);
|
|
35372
|
-
prelaunchTimeout.unref();
|
|
35373
|
-
|
|
35374
|
-
const launchTarget = (release) => {
|
|
35375
|
-
try {
|
|
35376
|
-
const command = config.comspec || config.command;
|
|
35377
|
-
const args = config.comspec
|
|
35378
|
-
? ['/d', '/s', '/c', '"' + [config.command, ...config.args].map(quote).join(' ') + '"']
|
|
35379
|
-
: config.args;
|
|
35380
|
-
target = spawn(command, args, {
|
|
35381
|
-
cwd: config.cwd,
|
|
35382
|
-
env: config.env,
|
|
35383
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
35384
|
-
windowsHide: true,
|
|
35385
|
-
windowsVerbatimArguments: Boolean(config.comspec),
|
|
35386
|
-
});
|
|
35387
|
-
launchPending = false;
|
|
35388
|
-
} catch {
|
|
35389
|
-
process.exit(1);
|
|
35390
|
-
return;
|
|
35391
|
-
}
|
|
35392
|
-
|
|
35393
|
-
target.stdin.on('error', () => {});
|
|
35394
|
-
target.stdout.pipe(process.stdout, { end: false });
|
|
35395
|
-
target.stderr.on('data', (chunk) => {
|
|
35396
|
-
process.stderr.write(chunk);
|
|
35397
|
-
stderrTail = (stderrTail + String(chunk)).slice(-32_768);
|
|
35398
|
-
});
|
|
35399
|
-
let targetResult;
|
|
35400
|
-
let stdoutEnded = false;
|
|
35401
|
-
const maybeReport = () => {
|
|
35402
|
-
if (targetResult && stdoutEnded) report(targetResult);
|
|
35403
|
-
};
|
|
35404
|
-
target.stdout.once('end', () => {
|
|
35405
|
-
stdoutEnded = true;
|
|
35406
|
-
maybeReport();
|
|
35407
|
-
});
|
|
35408
|
-
target.stdout.once('close', () => {
|
|
35409
|
-
stdoutEnded = true;
|
|
35410
|
-
maybeReport();
|
|
35411
|
-
});
|
|
35412
|
-
target.once('error', () => {
|
|
35413
|
-
targetResult = { kind: 'spawn_error' };
|
|
35414
|
-
stdoutEnded = true;
|
|
35415
|
-
maybeReport();
|
|
35416
|
-
});
|
|
35417
|
-
target.once('close', (code, signal) => {
|
|
35418
|
-
targetResult = { kind: 'close', code, signal };
|
|
35419
|
-
maybeReport();
|
|
35420
|
-
});
|
|
35421
|
-
if (config.prompt) target.stdin.write(config.prompt);
|
|
35422
|
-
if (postReleaseInput.length) {
|
|
35423
|
-
target.stdin.write(postReleaseInput);
|
|
35424
|
-
postReleaseInput = Buffer.alloc(0);
|
|
35425
|
-
}
|
|
35426
|
-
if (stdinEnded) target.stdin.end();
|
|
35427
|
-
};
|
|
35428
|
-
|
|
35429
|
-
const launch = (release) => {
|
|
35430
|
-
clearTimeout(prelaunchTimeout);
|
|
35431
|
-
config = release;
|
|
35432
|
-
launchPending = true;
|
|
35433
|
-
if (process.platform === 'win32') {
|
|
35434
|
-
launchTarget(release);
|
|
35435
|
-
return;
|
|
35436
|
-
}
|
|
35437
|
-
|
|
35438
|
-
// A detached POSIX group's numeric id outlives its leader, but without a
|
|
35439
|
-
// live identity witness a stale registry entry cannot distinguish that
|
|
35440
|
-
// inherited group from the same PGID being recycled later. This
|
|
35441
|
-
// credential-free sentinel inherits the guardian's exact group and carries
|
|
35442
|
-
// only its high-entropy nonce in argv. It deliberately survives SIGTERM so
|
|
35443
|
-
// checked recovery can keep the PGID reserved through the graceful pass and
|
|
35444
|
-
// validate the nonce again immediately before a group-wide SIGKILL.
|
|
35445
|
-
const identitySentinel = spawn(
|
|
35446
|
-
process.execPath,
|
|
35447
|
-
[
|
|
35448
|
-
'-e',
|
|
35449
|
-
"process.on('SIGTERM', () => {}); setInterval(() => {}, 2147483647);",
|
|
35450
|
-
guardianNonce,
|
|
35451
|
-
],
|
|
35452
|
-
{ env: process.env, stdio: 'ignore' },
|
|
35453
|
-
);
|
|
35454
|
-
identitySentinel.once('error', () => process.exit(1));
|
|
35455
|
-
identitySentinel.once('spawn', () => launchTarget(release));
|
|
35456
|
-
identitySentinel.once('exit', () => {
|
|
35457
|
-
if (!reported) process.exit(1);
|
|
35458
|
-
});
|
|
35459
|
-
};
|
|
35460
|
-
|
|
35461
|
-
process.stdin.on('data', (chunk) => {
|
|
35462
|
-
if (target) {
|
|
35463
|
-
target.stdin.write(chunk);
|
|
35464
|
-
return;
|
|
35465
|
-
}
|
|
35466
|
-
if (config) {
|
|
35467
|
-
postReleaseInput = Buffer.concat([postReleaseInput, chunk]);
|
|
35468
|
-
if (postReleaseInput.length > maxReleaseBytes) process.exit(1);
|
|
35469
|
-
return;
|
|
35470
|
-
}
|
|
35471
|
-
pending = Buffer.concat([pending, chunk]);
|
|
35472
|
-
if (pending.length > maxReleaseBytes + 256) process.exit(1);
|
|
35473
|
-
if (!containmentReady) {
|
|
35474
|
-
const gateNewline = pending.indexOf(10);
|
|
35475
|
-
if (gateNewline < 0) return;
|
|
35476
|
-
const gate = pending.subarray(0, gateNewline).toString().replace(/\r$/, '');
|
|
35477
|
-
if (gate !== containmentMarker + ' ' + containmentNonce) process.exit(1);
|
|
35478
|
-
containmentReady = true;
|
|
35479
|
-
pending = pending.subarray(gateNewline + 1);
|
|
35480
|
-
}
|
|
35481
|
-
if (expectedBytes === null) {
|
|
35482
|
-
const newline = pending.indexOf(10);
|
|
35483
|
-
if (newline < 0) return;
|
|
35484
|
-
const header = pending.subarray(0, newline).toString();
|
|
35485
|
-
const match = new RegExp('^' + launchMarker + ' ([1-9][0-9]{0,8})$').exec(header);
|
|
35486
|
-
if (!match) process.exit(1);
|
|
35487
|
-
expectedBytes = Number(match[1]);
|
|
35488
|
-
if (expectedBytes > maxReleaseBytes) process.exit(1);
|
|
35489
|
-
pending = pending.subarray(newline + 1);
|
|
35490
|
-
}
|
|
35491
|
-
if (pending.length < expectedBytes) return;
|
|
35492
|
-
try {
|
|
35493
|
-
const release = JSON.parse(pending.subarray(0, expectedBytes).toString('utf8'));
|
|
35494
|
-
postReleaseInput = pending.subarray(expectedBytes);
|
|
35495
|
-
pending = Buffer.alloc(0);
|
|
35496
|
-
launch(release);
|
|
35497
|
-
} catch {
|
|
35498
|
-
process.exit(1);
|
|
35499
|
-
}
|
|
35500
|
-
});
|
|
35501
|
-
process.stdin.on('end', () => {
|
|
35502
|
-
stdinEnded = true;
|
|
35503
|
-
if (!containmentReady) process.exit(1);
|
|
35504
|
-
if (target) target.stdin.end();
|
|
35505
|
-
else if (!launchPending) process.exit(1);
|
|
35506
|
-
});
|
|
35507
|
-
process.stdin.on('error', () => process.exit(1));
|
|
35508
|
-
`;
|
|
35509
|
-
var WINDOWS_PRIVATE_DACL_SCRIPT = String.raw`
|
|
35510
|
-
$ErrorActionPreference = 'Stop'
|
|
35511
|
-
$paths = ConvertFrom-Json $env:ZIXT_RUN_ARTIFACT_PATHS
|
|
35512
|
-
$current = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
35513
|
-
$system = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-18')
|
|
35514
|
-
$admins = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
|
|
35515
|
-
$allowed = @($current.Value, $system.Value, $admins.Value)
|
|
35516
|
-
foreach ($path in $paths) {
|
|
35517
|
-
$acl = [System.Security.AccessControl.DirectorySecurity]::new()
|
|
35518
|
-
$acl.SetAccessRuleProtection($true, $false)
|
|
35519
|
-
foreach ($sid in @($current, $system, $admins)) {
|
|
35520
|
-
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
|
|
35521
|
-
$sid,
|
|
35522
|
-
[System.Security.AccessControl.FileSystemRights]::FullControl,
|
|
35523
|
-
[System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit',
|
|
35524
|
-
[System.Security.AccessControl.PropagationFlags]::None,
|
|
35525
|
-
[System.Security.AccessControl.AccessControlType]::Allow
|
|
35526
|
-
)
|
|
35527
|
-
[void]$acl.AddAccessRule($rule)
|
|
35528
|
-
}
|
|
35529
|
-
$entry = [System.IO.DirectoryInfo]::new($path)
|
|
35530
|
-
$entry.SetAccessControl($acl)
|
|
35531
|
-
$check = $entry.GetAccessControl()
|
|
35532
|
-
if (-not $check.AreAccessRulesProtected) { throw 'DACL inheritance remained enabled' }
|
|
35533
|
-
$seen = @{}
|
|
35534
|
-
foreach ($rule in $check.Access) {
|
|
35535
|
-
$sid = $rule.IdentityReference.Translate(
|
|
35536
|
-
[System.Security.Principal.SecurityIdentifier]
|
|
35537
|
-
).Value
|
|
35538
|
-
if ($rule.AccessControlType -ne 'Allow' -or $allowed -notcontains $sid) {
|
|
35539
|
-
throw 'Unexpected DACL entry'
|
|
35540
|
-
}
|
|
35541
|
-
$seen[$sid] = $true
|
|
35542
|
-
}
|
|
35543
|
-
foreach ($sid in $allowed) {
|
|
35544
|
-
if (-not $seen.ContainsKey($sid)) { throw 'Required DACL entry is missing' }
|
|
35545
|
-
}
|
|
35546
|
-
}
|
|
35547
|
-
`;
|
|
35548
|
-
function defaultRunArtifactRoot() {
|
|
35549
|
-
return join13(homedir4(), ".zixt", "run-artifacts");
|
|
35550
|
-
}
|
|
35551
|
-
function requireSafeSegment(value, field) {
|
|
35552
|
-
if (!SAFE_SEGMENT2.test(value)) {
|
|
35553
|
-
throw new Error(`${field} must be a safe path segment`);
|
|
35554
|
-
}
|
|
35555
|
-
}
|
|
35556
|
-
function isMissing(error52) {
|
|
35557
|
-
return error52.code === "ENOENT";
|
|
35558
|
-
}
|
|
35559
|
-
function assertBelow3(parent, child) {
|
|
35560
|
-
const path = relative8(parent, child);
|
|
35561
|
-
const escapes = path === "" || path === ".." || path.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute13(path);
|
|
35562
|
-
if (escapes) throw new Error("run artifact path escapes its private root");
|
|
35563
|
-
}
|
|
35564
|
-
async function requireRealDirectory3(path, label) {
|
|
35565
|
-
const entry = await lstat10(path);
|
|
35566
|
-
if (entry.isSymbolicLink()) throw new Error(`${label} must not be a symbolic link`);
|
|
35567
|
-
if (!entry.isDirectory()) throw new Error(`${label} must be a directory`);
|
|
35568
|
-
return realpath7(path);
|
|
35569
|
-
}
|
|
35570
|
-
function assertWindowsProfileBoundary(profile, target) {
|
|
35571
|
-
const path = win322.relative(win322.resolve(profile), win322.resolve(target));
|
|
35572
|
-
if (path === "" || path === ".." || path.startsWith("..\\") || win322.isAbsolute(path)) {
|
|
35573
|
-
throw new Error("run artifact root must be inside the current Windows user profile");
|
|
35574
|
-
}
|
|
35575
|
-
}
|
|
35576
|
-
async function rejectWindowsSymlinkAncestors(profile, target) {
|
|
35577
|
-
const path = win322.relative(win322.resolve(profile), win322.resolve(target));
|
|
35578
|
-
let current = win322.resolve(profile);
|
|
35579
|
-
for (const segment of path.split("\\").filter(Boolean)) {
|
|
35580
|
-
current = win322.join(current, segment);
|
|
35581
|
-
try {
|
|
35582
|
-
const entry = await lstat10(current);
|
|
35583
|
-
if (entry.isSymbolicLink()) {
|
|
35584
|
-
throw new Error("run artifact path must not contain symbolic links or junctions");
|
|
35585
|
-
}
|
|
35586
|
-
} catch (error52) {
|
|
35587
|
-
if (isMissing(error52)) return;
|
|
35588
|
-
throw error52;
|
|
35589
|
-
}
|
|
35590
|
-
}
|
|
35591
|
-
}
|
|
35592
|
-
async function prepareRoot(root) {
|
|
35593
|
-
const absolute = resolve9(root);
|
|
35594
|
-
let realProfile;
|
|
35595
|
-
if (process.platform === "win32") {
|
|
35596
|
-
const profile = resolve9(homedir4());
|
|
35597
|
-
assertWindowsProfileBoundary(profile, absolute);
|
|
35598
|
-
await rejectWindowsSymlinkAncestors(profile, absolute);
|
|
35599
|
-
realProfile = await realpath7(profile);
|
|
35600
|
-
}
|
|
35601
|
-
try {
|
|
35602
|
-
await lstat10(absolute);
|
|
35603
|
-
} catch (error52) {
|
|
35604
|
-
if (!isMissing(error52)) throw error52;
|
|
35605
|
-
await mkdir9(absolute, { recursive: true, mode: DIRECTORY_MODE4 });
|
|
35606
|
-
}
|
|
35607
|
-
const real = await requireRealDirectory3(absolute, "run artifact root");
|
|
35608
|
-
if (realProfile) assertWindowsProfileBoundary(realProfile, real);
|
|
35609
|
-
await chmod6(real, DIRECTORY_MODE4);
|
|
35610
|
-
return real;
|
|
35611
|
-
}
|
|
35612
|
-
async function prepareAgentRoot(root, agentId) {
|
|
35613
|
-
const path = join13(root, agentId);
|
|
35614
|
-
assertBelow3(root, path);
|
|
35615
|
-
try {
|
|
35616
|
-
await lstat10(path);
|
|
35617
|
-
} catch (error52) {
|
|
35618
|
-
if (!isMissing(error52)) throw error52;
|
|
35619
|
-
try {
|
|
35620
|
-
await mkdir9(path, { mode: DIRECTORY_MODE4 });
|
|
35621
|
-
} catch (mkdirError) {
|
|
35622
|
-
if (mkdirError.code !== "EEXIST") throw mkdirError;
|
|
35623
|
-
}
|
|
35624
|
-
}
|
|
35625
|
-
const real = await requireRealDirectory3(path, "run artifact Agent directory");
|
|
35626
|
-
assertBelow3(root, real);
|
|
35627
|
-
await chmod6(real, DIRECTORY_MODE4);
|
|
35628
|
-
return real;
|
|
35629
|
-
}
|
|
35630
|
-
async function lockDownWindowsDirectories(paths) {
|
|
35631
|
-
if (process.platform !== "win32") return;
|
|
35632
|
-
const windowsRoot = process.env.SystemRoot ?? process.env.WINDIR;
|
|
35633
|
-
if (!windowsRoot || !win322.isAbsolute(windowsRoot)) {
|
|
35634
|
-
throw new Error("private Windows run-artifact ACL authority is unavailable");
|
|
35635
|
-
}
|
|
35636
|
-
const powershell = join13(windowsRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
35637
|
-
const encoded = Buffer.from(WINDOWS_PRIVATE_DACL_SCRIPT, "utf16le").toString("base64");
|
|
35638
|
-
await new Promise((resolvePromise, reject3) => {
|
|
35639
|
-
const helper = spawn7(
|
|
35640
|
-
powershell,
|
|
35641
|
-
["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded],
|
|
35642
|
-
{
|
|
35643
|
-
env: {
|
|
35644
|
-
SystemRoot: windowsRoot,
|
|
35645
|
-
WINDIR: windowsRoot,
|
|
35646
|
-
...process.env.TEMP ? { TEMP: process.env.TEMP } : {},
|
|
35647
|
-
...process.env.TMP ? { TMP: process.env.TMP } : {},
|
|
35648
|
-
ZIXT_RUN_ARTIFACT_PATHS: JSON.stringify(paths)
|
|
35649
|
-
},
|
|
35650
|
-
stdio: "ignore",
|
|
35651
|
-
windowsHide: true
|
|
35652
|
-
}
|
|
35653
|
-
);
|
|
35654
|
-
let finished = false;
|
|
35655
|
-
const timeout = setTimeout(() => {
|
|
35656
|
-
helper.kill("SIGKILL");
|
|
35657
|
-
finish(new Error("private Windows run-artifact ACL update timed out"));
|
|
35658
|
-
}, WINDOWS_ACL_TIMEOUT_MS);
|
|
35659
|
-
timeout.unref?.();
|
|
35660
|
-
const finish = (error52) => {
|
|
35661
|
-
if (finished) return;
|
|
35662
|
-
finished = true;
|
|
35663
|
-
clearTimeout(timeout);
|
|
35664
|
-
if (error52) reject3(error52);
|
|
35665
|
-
else resolvePromise();
|
|
35666
|
-
};
|
|
35667
|
-
helper.once(
|
|
35668
|
-
"error",
|
|
35669
|
-
() => finish(new Error("private Windows run-artifact ACL update could not start"))
|
|
35670
|
-
);
|
|
35671
|
-
helper.once(
|
|
35672
|
-
"close",
|
|
35673
|
-
(code) => finish(code === 0 ? void 0 : new Error("private Windows run-artifact ACL update failed"))
|
|
35674
|
-
);
|
|
35675
|
-
});
|
|
35676
|
-
}
|
|
35677
|
-
async function createPrivateDirectory(parent, name) {
|
|
35678
|
-
const path = join13(parent, name);
|
|
35679
|
-
assertBelow3(parent, path);
|
|
35680
|
-
await mkdir9(path, { mode: DIRECTORY_MODE4 });
|
|
35681
|
-
await chmod6(path, DIRECTORY_MODE4);
|
|
35682
|
-
const real = await realpath7(path);
|
|
35683
|
-
assertBelow3(parent, real);
|
|
35684
|
-
return real;
|
|
35685
|
-
}
|
|
35686
|
-
async function writePrivateFile(path, content) {
|
|
35687
|
-
await writeFile5(path, content, { flag: "wx", mode: FILE_MODE3 });
|
|
35688
|
-
await chmod6(path, FILE_MODE3);
|
|
35689
|
-
}
|
|
35690
|
-
function quotePosix(value) {
|
|
35691
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
35692
|
-
}
|
|
35693
|
-
function quoteWindows(value) {
|
|
35694
|
-
if (value.includes('"') || /[\r\n]/.test(value)) {
|
|
35695
|
-
throw new Error("Windows command paths must not contain quotes or newlines");
|
|
35696
|
-
}
|
|
35697
|
-
return `"${value}"`;
|
|
35698
|
-
}
|
|
35699
|
-
function buildDenySshCommand(executablePath, scriptPath, platform = process.platform) {
|
|
35700
|
-
const quote2 = platform === "win32" ? quoteWindows : quotePosix;
|
|
35701
|
-
return `${quote2(executablePath)} ${quote2(scriptPath)}`;
|
|
35702
|
-
}
|
|
35703
|
-
async function createRunArtifacts(input) {
|
|
35704
|
-
requireSafeSegment(input.agentId, "agentId");
|
|
35705
|
-
requireSafeSegment(input.runToken, "runToken");
|
|
35706
|
-
const root = await prepareRoot(input.root);
|
|
35707
|
-
const removeTree = input.removeTree ?? ((path) => rm7(path, { recursive: true, force: true }));
|
|
35708
|
-
const cleanupRetryDelayMs = input.cleanupRetryDelayMs ?? CLEANUP_RETRY_DELAY_MS;
|
|
35709
|
-
const agentRoot = await prepareAgentRoot(root, input.agentId);
|
|
35710
|
-
await lockDownWindowsDirectories([root, agentRoot]);
|
|
35711
|
-
const runRoot = join13(agentRoot, input.runToken);
|
|
35712
|
-
assertBelow3(agentRoot, runRoot);
|
|
35713
|
-
try {
|
|
35714
|
-
await mkdir9(runRoot, { mode: DIRECTORY_MODE4 });
|
|
35715
|
-
await chmod6(runRoot, DIRECTORY_MODE4);
|
|
35716
|
-
const realRunRoot = await realpath7(runRoot);
|
|
35717
|
-
assertBelow3(agentRoot, realRunRoot);
|
|
35718
|
-
const emptyGithubConfigDirectory = await createPrivateDirectory(realRunRoot, "gh-config");
|
|
35719
|
-
const emptyGitHooksDirectory = await createPrivateDirectory(realRunRoot, "git-hooks");
|
|
35720
|
-
const gitBridgesDirectory = await createPrivateDirectory(realRunRoot, "git-bridges");
|
|
35721
|
-
const denySshScript = join13(realRunRoot, "deny-ssh.cjs");
|
|
35722
|
-
const runnerWrapperScript = join13(realRunRoot, "runner-wrapper.cjs");
|
|
35723
|
-
const systemPromptPath = join13(realRunRoot, "system-prompt.txt");
|
|
35724
|
-
const mcpConfigPath = join13(realRunRoot, "mcp.json");
|
|
35725
|
-
await writePrivateFile(denySshScript, "process.exitCode = 127;");
|
|
35726
|
-
await writePrivateFile(runnerWrapperScript, RUNNER_GUARDIAN);
|
|
35727
|
-
return {
|
|
35728
|
-
runRoot: realRunRoot,
|
|
35729
|
-
emptyGithubConfigDirectory,
|
|
35730
|
-
emptyGitHooksDirectory,
|
|
35731
|
-
gitBridgesDirectory,
|
|
35732
|
-
denySshScript,
|
|
35733
|
-
denySshCommand: buildDenySshCommand(process.execPath, denySshScript),
|
|
35734
|
-
runnerWrapperScript,
|
|
35735
|
-
nullConfigPath: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
35736
|
-
async writeSystemPrompt(content) {
|
|
35737
|
-
await writePrivateFile(systemPromptPath, content);
|
|
35738
|
-
return systemPromptPath;
|
|
35739
|
-
},
|
|
35740
|
-
async writeMcpConfig(content) {
|
|
35741
|
-
await writePrivateFile(mcpConfigPath, content);
|
|
35742
|
-
return mcpConfigPath;
|
|
35743
|
-
},
|
|
35744
|
-
async cleanup() {
|
|
35745
|
-
await removePrivateTreeWithRetries(realRunRoot, removeTree, cleanupRetryDelayMs);
|
|
35746
|
-
}
|
|
35747
|
-
};
|
|
35748
|
-
} catch (error52) {
|
|
35749
|
-
await removePrivateTreeWithRetries(runRoot, removeTree, cleanupRetryDelayMs).catch(() => {
|
|
35750
|
-
});
|
|
35751
|
-
throw error52;
|
|
35752
|
-
}
|
|
35753
|
-
}
|
|
35754
|
-
async function removePrivateTreeWithRetries(path, removeTree, retryDelayMs) {
|
|
35755
|
-
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
35756
|
-
try {
|
|
35757
|
-
await removeTree(path);
|
|
35758
|
-
return;
|
|
35759
|
-
} catch (error52) {
|
|
35760
|
-
if (attempt === CLEANUP_ATTEMPTS2) throw error52;
|
|
35761
|
-
await new Promise((resolveDelay) => setTimeout(resolveDelay, retryDelayMs));
|
|
35762
|
-
}
|
|
35763
|
-
}
|
|
35764
|
-
}
|
|
35765
|
-
async function sweepOrphanedRunArtifacts(root) {
|
|
35766
|
-
const absolute = resolve9(root);
|
|
35767
|
-
let realProfile;
|
|
35768
|
-
if (process.platform === "win32") {
|
|
35769
|
-
const profile = resolve9(homedir4());
|
|
35770
|
-
assertWindowsProfileBoundary(profile, absolute);
|
|
35771
|
-
await rejectWindowsSymlinkAncestors(profile, absolute);
|
|
35772
|
-
realProfile = await realpath7(profile);
|
|
35773
|
-
}
|
|
35774
|
-
let realRoot;
|
|
35775
|
-
try {
|
|
35776
|
-
realRoot = await requireRealDirectory3(absolute, "run artifact root");
|
|
35777
|
-
} catch (error52) {
|
|
35778
|
-
if (isMissing(error52)) return 0;
|
|
35779
|
-
throw error52;
|
|
35780
|
-
}
|
|
35781
|
-
if (realProfile) assertWindowsProfileBoundary(realProfile, realRoot);
|
|
35782
|
-
await chmod6(realRoot, DIRECTORY_MODE4);
|
|
35783
|
-
await lockDownWindowsDirectories([realRoot]);
|
|
35784
|
-
const agents = await readdir3(realRoot, { withFileTypes: true });
|
|
35785
|
-
let removed = 0;
|
|
35786
|
-
for (const agent of agents) {
|
|
35787
|
-
if (!SAFE_SEGMENT2.test(agent.name) || !agent.isDirectory() || agent.isSymbolicLink()) continue;
|
|
35788
|
-
const agentPath = join13(realRoot, agent.name);
|
|
35789
|
-
const runs = await readdir3(agentPath, { withFileTypes: true });
|
|
35790
|
-
for (const run3 of runs) {
|
|
35791
|
-
if (!SAFE_SEGMENT2.test(run3.name) || !run3.isDirectory() || run3.isSymbolicLink()) continue;
|
|
35792
|
-
const runPath = join13(agentPath, run3.name);
|
|
35793
|
-
assertBelow3(agentPath, runPath);
|
|
35794
|
-
await rm7(runPath, { recursive: true, force: true });
|
|
35795
|
-
removed++;
|
|
35796
|
-
}
|
|
35797
|
-
}
|
|
35798
|
-
return removed;
|
|
35799
|
-
}
|
|
35800
|
-
function defaultRunRegistryRoot() {
|
|
35801
|
-
return join13(homedir4(), ".zixt", "run-registry");
|
|
35802
|
-
}
|
|
35803
|
-
async function syncRunRegistryDirectory(path) {
|
|
35804
|
-
const handle = await open5(path, "r");
|
|
35805
|
-
try {
|
|
35806
|
-
await handle.sync();
|
|
35807
|
-
} finally {
|
|
35808
|
-
await handle.close();
|
|
35809
|
-
}
|
|
35810
|
-
}
|
|
35811
|
-
async function ensureDurableRunRegistryRoot(registryRoot, syncDirectory7) {
|
|
35812
|
-
const firstCreated = await mkdir9(registryRoot, { recursive: true, mode: DIRECTORY_MODE4 });
|
|
35813
|
-
if (firstCreated && process.platform !== "win32") {
|
|
35814
|
-
const first = resolve9(firstCreated);
|
|
35815
|
-
const target = resolve9(registryRoot);
|
|
35816
|
-
await syncDirectory7(dirname7(first));
|
|
35817
|
-
let current = first;
|
|
35818
|
-
for (const part of relative8(first, target).split(sep4).filter(Boolean)) {
|
|
35819
|
-
await syncDirectory7(current);
|
|
35820
|
-
current = join13(current, part);
|
|
35821
|
-
}
|
|
35822
|
-
}
|
|
35823
|
-
await chmod6(registryRoot, DIRECTORY_MODE4);
|
|
35824
|
-
}
|
|
35825
|
-
async function recordRunAssignment(runToken, record2, registryRoot = defaultRunRegistryRoot(), options = {}) {
|
|
35826
|
-
if (!SAFE_SEGMENT2.test(runToken)) return false;
|
|
35827
|
-
const destination = join13(registryRoot, `${runToken}.json`);
|
|
35828
|
-
const temporary = join13(registryRoot, `.${runToken}.${process.pid}.${Date.now()}.tmp`);
|
|
35829
|
-
let handle;
|
|
35830
|
-
try {
|
|
35831
|
-
const syncDirectory7 = options.syncDirectory ?? syncRunRegistryDirectory;
|
|
35832
|
-
await ensureDurableRunRegistryRoot(registryRoot, syncDirectory7);
|
|
35833
|
-
handle = await open5(temporary, "wx", FILE_MODE3);
|
|
35834
|
-
await handle.writeFile(JSON.stringify(record2), "utf8");
|
|
35835
|
-
await handle.sync();
|
|
35836
|
-
await handle.close();
|
|
35837
|
-
handle = void 0;
|
|
35838
|
-
await rename5(temporary, destination);
|
|
35839
|
-
if (process.platform !== "win32") {
|
|
35840
|
-
await syncDirectory7(registryRoot);
|
|
35841
|
-
}
|
|
35842
|
-
return true;
|
|
35843
|
-
} catch {
|
|
35844
|
-
return false;
|
|
35845
|
-
} finally {
|
|
35846
|
-
await handle?.close().catch(() => {
|
|
35847
|
-
});
|
|
35848
|
-
await rm7(temporary, { force: true }).catch(() => {
|
|
35849
|
-
});
|
|
35850
|
-
}
|
|
35851
|
-
}
|
|
35852
|
-
var retainingAssignments = false;
|
|
35853
|
-
function retainRunAssignments() {
|
|
35854
|
-
retainingAssignments = true;
|
|
35855
|
-
}
|
|
35856
|
-
async function forgetRunAssignment(runToken, registryRoot = defaultRunRegistryRoot()) {
|
|
35857
|
-
if (retainingAssignments) return;
|
|
35858
|
-
if (!SAFE_SEGMENT2.test(runToken)) return;
|
|
35859
|
-
try {
|
|
35860
|
-
await rm7(join13(registryRoot, `${runToken}.json`), { force: true });
|
|
35861
|
-
} catch {
|
|
35862
|
-
}
|
|
35863
|
-
}
|
|
35864
|
-
function parseAssignment(text) {
|
|
35865
|
-
let parsed;
|
|
35866
|
-
try {
|
|
35867
|
-
parsed = JSON.parse(text);
|
|
35868
|
-
} catch {
|
|
35869
|
-
return null;
|
|
35870
|
-
}
|
|
35871
|
-
if (typeof parsed !== "object" || parsed === null) return null;
|
|
35872
|
-
const value = parsed;
|
|
35873
|
-
if (typeof value.taskId !== "string" || value.taskId === "") return null;
|
|
35874
|
-
if (typeof value.epoch !== "number" || !Number.isInteger(value.epoch) || value.epoch < 1) {
|
|
35875
|
-
return null;
|
|
35876
|
-
}
|
|
35877
|
-
const pid = value.pid;
|
|
35878
|
-
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 1) return null;
|
|
35879
|
-
let identity;
|
|
35880
|
-
if (value.identity !== void 0) {
|
|
35881
|
-
if (typeof value.identity !== "object" || value.identity === null || value.identity.kind !== "guardian_nonce" || typeof value.identity.nonce !== "string" || !/^[A-Za-z0-9_-]{16,200}$/.test(value.identity.nonce)) {
|
|
35882
|
-
return null;
|
|
35883
|
-
}
|
|
35884
|
-
identity = {
|
|
35885
|
-
kind: "guardian_nonce",
|
|
35886
|
-
nonce: value.identity.nonce
|
|
35887
|
-
};
|
|
35888
|
-
}
|
|
35889
|
-
return { taskId: value.taskId, epoch: value.epoch, pid, ...identity ? { identity } : {} };
|
|
35890
|
-
}
|
|
35891
|
-
async function readRecordedRunAssignmentEntries(registryRoot = defaultRunRegistryRoot()) {
|
|
35892
|
-
let entries;
|
|
35893
|
-
try {
|
|
35894
|
-
entries = await readdir3(registryRoot, { withFileTypes: true });
|
|
35895
|
-
} catch {
|
|
35896
|
-
return [];
|
|
35897
|
-
}
|
|
35898
|
-
const found = [];
|
|
35899
|
-
for (const entry of entries) {
|
|
35900
|
-
if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith(".json")) continue;
|
|
35901
|
-
const runToken = entry.name.slice(0, -".json".length);
|
|
35902
|
-
if (!SAFE_SEGMENT2.test(runToken)) continue;
|
|
35903
|
-
let text;
|
|
35904
|
-
try {
|
|
35905
|
-
text = await readFile8(join13(registryRoot, entry.name), "utf8");
|
|
35906
|
-
} catch {
|
|
35907
|
-
continue;
|
|
35908
|
-
}
|
|
35909
|
-
const record2 = parseAssignment(text);
|
|
35910
|
-
if (record2) found.push({ runToken, record: record2 });
|
|
35911
|
-
}
|
|
35912
|
-
return found;
|
|
35913
|
-
}
|
|
35914
|
-
async function readRecordedRunAssignmentEntriesStrict(registryRoot = defaultRunRegistryRoot()) {
|
|
35915
|
-
let rootStat;
|
|
35916
|
-
try {
|
|
35917
|
-
rootStat = await lstat10(registryRoot);
|
|
35918
|
-
} catch (error52) {
|
|
35919
|
-
if (isMissing(error52)) return [];
|
|
35920
|
-
throw new Error("run registry state could not be observed");
|
|
35921
|
-
}
|
|
35922
|
-
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
35923
|
-
throw new Error("run registry root is not a trusted directory");
|
|
35924
|
-
}
|
|
35925
|
-
let entries;
|
|
35926
|
-
try {
|
|
35927
|
-
entries = await readdir3(registryRoot, { withFileTypes: true });
|
|
35928
|
-
} catch {
|
|
35929
|
-
throw new Error("run registry state could not be observed");
|
|
35930
|
-
}
|
|
35931
|
-
const found = [];
|
|
35932
|
-
for (const entry of entries) {
|
|
35933
|
-
if (!entry.name.endsWith(".json")) continue;
|
|
35934
|
-
const runToken = entry.name.slice(0, -".json".length);
|
|
35935
|
-
if (!SAFE_SEGMENT2.test(runToken)) continue;
|
|
35936
|
-
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
35937
|
-
throw new Error("committed run registry witness is not a regular file");
|
|
35938
|
-
}
|
|
35939
|
-
let text;
|
|
35940
|
-
try {
|
|
35941
|
-
text = await readFile8(join13(registryRoot, entry.name), "utf8");
|
|
35942
|
-
} catch {
|
|
35943
|
-
throw new Error("committed run registry witness could not be read");
|
|
35944
|
-
}
|
|
35945
|
-
const record2 = parseAssignment(text);
|
|
35946
|
-
if (!record2) throw new Error("committed run registry witness is malformed");
|
|
35947
|
-
found.push({ runToken, record: record2 });
|
|
35948
|
-
}
|
|
35949
|
-
return found;
|
|
35950
|
-
}
|
|
35951
|
-
async function forgetAcknowledgedRunAssignments(assignments, registryRoot = defaultRunRegistryRoot()) {
|
|
35952
|
-
if (assignments.length === 0) return;
|
|
35953
|
-
const acknowledged = new Set(
|
|
35954
|
-
assignments.map((assignment) => `${assignment.taskId}:${assignment.epoch}`)
|
|
35955
|
-
);
|
|
35956
|
-
const entries = await readRecordedRunAssignmentEntries(registryRoot);
|
|
35957
|
-
await Promise.all(
|
|
35958
|
-
entries.filter(({ record: record2 }) => acknowledged.has(`${record2.taskId}:${record2.epoch}`)).map(({ runToken }) => rm7(join13(registryRoot, `${runToken}.json`), { force: true }))
|
|
35959
|
-
);
|
|
35960
|
-
}
|
|
35961
|
-
async function forgetSupersededRunAssignments(taskId, epoch, registryRoot = defaultRunRegistryRoot()) {
|
|
35962
|
-
const entries = await readRecordedRunAssignmentEntries(registryRoot);
|
|
35963
|
-
await Promise.all(
|
|
35964
|
-
entries.filter(({ record: record2 }) => record2.taskId === taskId && record2.epoch < epoch).map(({ runToken }) => rm7(join13(registryRoot, `${runToken}.json`), { force: true }))
|
|
35965
|
-
);
|
|
35966
|
-
}
|
|
35967
|
-
|
|
35968
36034
|
// src/runners/cli-runner.ts
|
|
35969
36035
|
var PRIVATE_CLEANUP_FAILURE = "Private runner cleanup could not be completed. Restart the Zixt Host before accepting more work.";
|
|
35970
36036
|
var PROCESS_CLEANUP_FAILURE = "Runner process cleanup could not be confirmed. Restart the Zixt Host before accepting more work.";
|
|
@@ -35996,21 +36062,10 @@ function defaultRunnerArtifactRoot() {
|
|
|
35996
36062
|
async function sweepOrphanedRunnerArtifacts(artifactRoot = defaultRunnerArtifactRoot()) {
|
|
35997
36063
|
return sweepOrphanedRunArtifacts(artifactRoot);
|
|
35998
36064
|
}
|
|
35999
|
-
async function recoverRecordedRunAssignments(registryRoot = defaultRunRegistryRoot(), terminate
|
|
36000
|
-
const
|
|
36001
|
-
const terminated = /* @__PURE__ */ new Set();
|
|
36002
|
-
const orderedEntries = [...entries].sort(
|
|
36003
|
-
(left, right) => Number(Boolean(right.record.identity)) - Number(Boolean(left.record.identity))
|
|
36004
|
-
);
|
|
36005
|
-
for (const { record: record2 } of orderedEntries) {
|
|
36006
|
-
const identityKey = record2.identity ? `${record2.identity.kind}:${record2.identity.nonce}` : "legacy";
|
|
36007
|
-
const witnessKey = `${record2.pid}:${identityKey}`;
|
|
36008
|
-
if (terminated.has(witnessKey)) continue;
|
|
36009
|
-
await terminate(record2.pid, record2.identity);
|
|
36010
|
-
terminated.add(witnessKey);
|
|
36011
|
-
}
|
|
36065
|
+
async function recoverRecordedRunAssignments(registryRoot = defaultRunRegistryRoot(), terminate) {
|
|
36066
|
+
const records = terminate ? await terminateRecordedRunProcesses(registryRoot, terminate) : await terminateRecordedRunProcesses(registryRoot);
|
|
36012
36067
|
const assignments = /* @__PURE__ */ new Map();
|
|
36013
|
-
for (const
|
|
36068
|
+
for (const record2 of records) {
|
|
36014
36069
|
const assignment = { taskId: record2.taskId, epoch: record2.epoch };
|
|
36015
36070
|
assignments.set(`${record2.taskId}:${record2.epoch}`, assignment);
|
|
36016
36071
|
}
|