@evomap/evolver-proxy 2.0.0-beta.2 → 2.0.0-beta.22
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/bin/evolver-llm-proxy.js +0 -0
- package/dist/bin/evolver-proxy.d.ts +105 -7
- package/dist/bin/evolver-proxy.js +877 -121
- package/dist/daemon/atpConsent.js +5 -2
- package/dist/daemon/collaborationFacade.js +26 -16
- package/dist/daemon/proxyDaemon.d.ts +65 -0
- package/dist/daemon/proxyDaemon.js +1384 -29
- package/dist/daemon/publishRecallVerifier.d.ts +114 -0
- package/dist/daemon/publishRecallVerifier.js +495 -0
- package/dist/daemon/selectHub.js +5 -3
- package/dist/daemon/systemdNotifier.d.ts +48 -0
- package/dist/daemon/systemdNotifier.js +163 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/lifecycle/claimNudge.d.ts +20 -0
- package/dist/lifecycle/claimNudge.js +124 -0
- package/dist/lifecycle/legacyNodeId.d.ts +11 -13
- package/dist/lifecycle/legacyNodeId.js +35 -20
- package/dist/lifecycle/manager.d.ts +4 -0
- package/dist/lifecycle/manager.js +15 -2
- package/dist/llm/server.js +24 -4
- package/dist/llm/traceControl.js +1 -1
- package/dist/llm/upstream.d.ts +5 -1
- package/dist/llm/upstream.js +72 -2
- package/dist/private/accountAssetCompatibility.d.ts +29 -0
- package/dist/private/accountAssetCompatibility.js +196 -0
- package/dist/private/adapterLoader.d.ts +21 -1
- package/dist/private/adapterLoader.js +242 -7
- package/dist/private/nodeCredentialStore.d.ts +23 -0
- package/dist/private/nodeCredentialStore.js +210 -0
- package/dist/router/messagesRoute.js +9 -3
- package/dist/router/providerRoutes.js +7 -3
- package/dist/selfUpdate/bootstrap.d.ts +162 -0
- package/dist/selfUpdate/bootstrap.js +3524 -0
- package/dist/selfUpdate/bootstrapReadiness.d.ts +9 -0
- package/dist/selfUpdate/bootstrapReadiness.js +153 -0
- package/dist/selfUpdate/builtinKey.d.ts +4 -0
- package/dist/selfUpdate/builtinKey.js +16 -0
- package/dist/selfUpdate/controllerLifecycleAuthority.d.ts +45 -0
- package/dist/selfUpdate/controllerLifecycleAuthority.js +61 -0
- package/dist/selfUpdate/executor.d.ts +27 -11
- package/dist/selfUpdate/executor.js +233 -58
- package/dist/selfUpdate/failureCodes.d.ts +10 -0
- package/dist/selfUpdate/failureCodes.js +13 -0
- package/dist/selfUpdate/index.d.ts +5 -1
- package/dist/selfUpdate/index.js +5 -1
- package/dist/selfUpdate/lastUpdate.d.ts +3 -1
- package/dist/selfUpdate/lastUpdate.js +37 -6
- package/dist/selfUpdate/migration.d.ts +158 -0
- package/dist/selfUpdate/migration.js +2672 -0
- package/dist/selfUpdate/policy.d.ts +19 -2
- package/dist/selfUpdate/policy.js +76 -2
- package/dist/selfUpdate/recoveryChildStartGate.d.ts +29 -0
- package/dist/selfUpdate/recoveryChildStartGate.js +319 -0
- package/dist/selfUpdate/releaseBinary.d.ts +13 -0
- package/dist/selfUpdate/releaseBinary.js +93 -10
- package/dist/selfUpdate/transaction.d.ts +117 -0
- package/dist/selfUpdate/transaction.js +1322 -0
- package/dist/selfUpdate/unixController.d.ts +23 -0
- package/dist/selfUpdate/unixController.js +514 -0
- package/dist/selfUpdate/version.d.ts +6 -2
- package/dist/selfUpdate/version.js +5 -3
- package/dist/selfUpdate/windowsController.d.ts +35 -0
- package/dist/selfUpdate/windowsController.js +655 -0
- package/dist/selfUpdate/windowsUpdater.d.ts +104 -0
- package/dist/selfUpdate/windowsUpdater.js +882 -0
- package/dist/sync/engine.d.ts +12 -0
- package/dist/sync/engine.js +255 -64
- package/package.json +10 -3
|
@@ -1,3 +1,20 @@
|
|
|
1
1
|
export type SelfUpdatePolicy = 'off' | 'prompt' | 'auto';
|
|
2
|
-
/** Resolve EVOLVER_SELF_UPDATE to a policy. Unset
|
|
3
|
-
export declare function resolveSelfUpdatePolicy(env?: NodeJS.ProcessEnv): SelfUpdatePolicy;
|
|
2
|
+
/** Resolve EVOLVER_SELF_UPDATE to a policy. Unset → 'auto' (still gated by supervisor + public key). Unrecognized → 'off' (fail-closed). */
|
|
3
|
+
export declare function resolveSelfUpdatePolicy(env?: NodeJS.ProcessEnv): SelfUpdatePolicy;
|
|
4
|
+
/** True when the operator set EVOLVER_SELF_UPDATE to any non-blank value. */
|
|
5
|
+
export declare function isSelfUpdateExplicit(env?: NodeJS.ProcessEnv): boolean;
|
|
6
|
+
/** Attested only by the generated durable launchers; env files and operators cannot forge the marker. */
|
|
7
|
+
export declare function selfUpdateSupervisorAttested(env: NodeJS.ProcessEnv): boolean;
|
|
8
|
+
export interface EffectiveSelfUpdatePolicy {
|
|
9
|
+
policy: SelfUpdatePolicy;
|
|
10
|
+
/** True when a DEFAULT auto was degraded to 'off' because the supervisor attestation is missing. */
|
|
11
|
+
degraded: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the policy that startup should actually run with. A default (unset) 'auto' without durable
|
|
15
|
+
* supervisor attestation OR without any available public key OR without a bindable self-update target
|
|
16
|
+
* (npm/JS install shape) degrades to 'off' so direct/foreground runs — and residual launchers from a
|
|
17
|
+
* bad bootstrap on a JS install — keep working; an explicit 'auto' passes through untouched and fails
|
|
18
|
+
* closed at assembly time. `execPath` overrides process.execPath for the target-bindability check.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveEffectiveSelfUpdatePolicy(env?: NodeJS.ProcessEnv, execPath?: string): EffectiveSelfUpdatePolicy;
|
|
@@ -1,9 +1,83 @@
|
|
|
1
|
-
|
|
1
|
+
// Self-update policy resolution from the EVOLVER_SELF_UPDATE env var.
|
|
2
|
+
//
|
|
3
|
+
// Default is AUTO. An auto-applying self-update channel is the single highest-value attack surface
|
|
4
|
+
// in the fleet, so the auto default stays hard-gated: production auto requires supervisor attestation
|
|
5
|
+
// (a generated durable launcher owns relaunch) plus an Ed25519 public key for signed-manifest
|
|
6
|
+
// verification, and anything missing fails closed before any update is applied.
|
|
7
|
+
//
|
|
8
|
+
// The DEFAULT auto is an install-convenience, not an operator assertion: when no durable supervisor
|
|
9
|
+
// attestation is present OR no verification public key is available (configured env value or the
|
|
10
|
+
// built-in distribution key) OR no self-update target is bindable (npm/JS install shape without a
|
|
11
|
+
// standalone binary), resolveEffectiveSelfUpdatePolicy
|
|
12
|
+
// degrades it to 'off' (with a warning at startup) so unsupervised foreground runs still start —
|
|
13
|
+
// keeping 'auto' would just make createSelfUpdateDeps throw at assembly and crash startup. The
|
|
14
|
+
// target-bindability check also rescues residual bad launchers left behind by older bootstraps:
|
|
15
|
+
// an attested supervisor running through node degrades to 'off' instead of crash-looping.
|
|
16
|
+
// An EXPLICIT EVOLVER_SELF_UPDATE=auto keeps failing closed at assembly (createSelfUpdateDeps throws)
|
|
17
|
+
// — the operator asked for auto, silence would hide a misconfiguration.
|
|
18
|
+
// Operators can still narrow it explicitly:
|
|
19
|
+
// off : never apply.
|
|
20
|
+
// prompt : recognized but not auto-applied until an approval store/UI exists.
|
|
21
|
+
// auto : apply automatically after verification (default).
|
|
22
|
+
// Enterprise/air-gap deployments should pin 'off' explicitly in their env file (decided by the
|
|
23
|
+
// private adapter, not core).
|
|
24
|
+
import { resolveSelfUpdatePublicKey } from './builtinKey.js';
|
|
25
|
+
import { selfUpdateProcessTargetBindable } from './releaseBinary.js';
|
|
26
|
+
/** Resolve EVOLVER_SELF_UPDATE to a policy. Unset → 'auto' (still gated by supervisor + public key). Unrecognized → 'off' (fail-closed). */
|
|
2
27
|
export function resolveSelfUpdatePolicy(env = process.env) {
|
|
3
28
|
const raw = (env['EVOLVER_SELF_UPDATE'] ?? '').trim().toLowerCase();
|
|
29
|
+
if (raw === 'off')
|
|
30
|
+
return 'off';
|
|
4
31
|
if (raw === 'prompt')
|
|
5
32
|
return 'prompt';
|
|
6
|
-
if (raw === 'auto')
|
|
33
|
+
if (raw === 'auto' || raw === '')
|
|
7
34
|
return 'auto';
|
|
8
35
|
return 'off';
|
|
36
|
+
}
|
|
37
|
+
/** True when the operator set EVOLVER_SELF_UPDATE to any non-blank value. */
|
|
38
|
+
export function isSelfUpdateExplicit(env = process.env) {
|
|
39
|
+
return (env['EVOLVER_SELF_UPDATE'] ?? '').trim() !== '';
|
|
40
|
+
}
|
|
41
|
+
/** Attested only by the generated durable launchers; env files and operators cannot forge the marker. */
|
|
42
|
+
export function selfUpdateSupervisorAttested(env) {
|
|
43
|
+
const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR']?.trim();
|
|
44
|
+
return supervisor === 'systemd'
|
|
45
|
+
|| supervisor === 'launchd'
|
|
46
|
+
|| supervisor === 'windows-scheduled-task';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* True when a verification public key will be available at assembly time: a configured
|
|
50
|
+
* EVOLVER_SELF_UPDATE_PUBLIC_KEY or the built-in distribution key. Must stay aligned with
|
|
51
|
+
* the resolver used by createSelfUpdateDeps so policy never degrades for a key that
|
|
52
|
+
* assembly would accept.
|
|
53
|
+
*/
|
|
54
|
+
function selfUpdatePublicKeyAvailable(env) {
|
|
55
|
+
return Boolean(resolveSelfUpdatePublicKey(env).trim());
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* True when a self-update target can be bound at assembly time: an explicit
|
|
59
|
+
* EVOLVER_SELF_UPDATE_TARGET_PATH or a standalone release binary execPath. Must stay aligned
|
|
60
|
+
* with resolveSelfUpdateTarget as used by createSelfUpdateDeps so policy never keeps 'auto'
|
|
61
|
+
* for a target that assembly would reject.
|
|
62
|
+
*/
|
|
63
|
+
function selfUpdateTargetBindable(env, execPath) {
|
|
64
|
+
return selfUpdateProcessTargetBindable({ env, processExecPath: execPath });
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the policy that startup should actually run with. A default (unset) 'auto' without durable
|
|
68
|
+
* supervisor attestation OR without any available public key OR without a bindable self-update target
|
|
69
|
+
* (npm/JS install shape) degrades to 'off' so direct/foreground runs — and residual launchers from a
|
|
70
|
+
* bad bootstrap on a JS install — keep working; an explicit 'auto' passes through untouched and fails
|
|
71
|
+
* closed at assembly time. `execPath` overrides process.execPath for the target-bindability check.
|
|
72
|
+
*/
|
|
73
|
+
export function resolveEffectiveSelfUpdatePolicy(env = process.env, execPath) {
|
|
74
|
+
const policy = resolveSelfUpdatePolicy(env);
|
|
75
|
+
if (policy === 'auto'
|
|
76
|
+
&& !isSelfUpdateExplicit(env)
|
|
77
|
+
&& (!selfUpdateSupervisorAttested(env)
|
|
78
|
+
|| !selfUpdatePublicKeyAvailable(env)
|
|
79
|
+
|| !selfUpdateTargetBindable(env, execPath))) {
|
|
80
|
+
return { policy: 'off', degraded: true };
|
|
81
|
+
}
|
|
82
|
+
return { policy, degraded: false };
|
|
9
83
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
import { util } from '@evomap/evolver-core';
|
|
3
|
+
export declare const RECOVERY_CHILD_START_GATE_ENV = "EVOLVER_INTERNAL_RECOVERY_CHILD_START_GATE";
|
|
4
|
+
export declare const DEFAULT_RECOVERY_CHILD_START_GATE_TIMEOUT_MS = 120000;
|
|
5
|
+
export type RecoveryChildStartGateRole = 'proxy-target' | 'windows-updater';
|
|
6
|
+
export interface PreparedRecoveryChildStartGate {
|
|
7
|
+
env: NodeJS.ProcessEnv;
|
|
8
|
+
startupGateToken: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ConsumeRecoveryChildStartGateOptions {
|
|
11
|
+
descriptor?: number;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
parentPid?: number;
|
|
14
|
+
inspectParentProcess?: (parent: Pick<util.FileLockOwnerRecord, 'pid' | 'processStartIdentity'>) => util.FileLockOwnerProcessStatus;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Bind a one-shot child start gate to this exact parent PID generation.
|
|
18
|
+
* The token travels in both the environment capability and a private fd4 pipe;
|
|
19
|
+
* neither input is sufficient on its own.
|
|
20
|
+
*/
|
|
21
|
+
export declare function prepareRecoveryChildStartGate(env: NodeJS.ProcessEnv, role: RecoveryChildStartGateRole, expectedParent?: Pick<util.FileLockOwnerRecord, 'pid' | 'processStartIdentity'>): PreparedRecoveryChildStartGate;
|
|
22
|
+
/**
|
|
23
|
+
* Consume the gate before controller/worker dispatch or durable recovery.
|
|
24
|
+
* Invalid capability, wrong role/parent generation, EOF, malformed input and
|
|
25
|
+
* timeout all fail closed. The inherited capability is scrubbed on every path.
|
|
26
|
+
*/
|
|
27
|
+
export declare function consumeRecoveryChildStartGate(env: NodeJS.ProcessEnv, expectedRole: RecoveryChildStartGateRole | undefined, options?: ConsumeRecoveryChildStartGateOptions): Promise<boolean>;
|
|
28
|
+
/** Deliver fd4 only after the caller establishes its owned guardian or delegated authority. */
|
|
29
|
+
export declare function deliverRecoveryChildStartGate(child: ChildProcess, token: string, timeoutMs?: number): Promise<boolean>;
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { closeSync, createReadStream } from 'node:fs';
|
|
3
|
+
import { Socket } from 'node:net';
|
|
4
|
+
import { util } from '@evomap/evolver-core';
|
|
5
|
+
export const RECOVERY_CHILD_START_GATE_ENV = 'EVOLVER_INTERNAL_RECOVERY_CHILD_START_GATE';
|
|
6
|
+
const RECOVERY_CHILD_START_GATE_DESCRIPTOR = 4;
|
|
7
|
+
export const DEFAULT_RECOVERY_CHILD_START_GATE_TIMEOUT_MS = 120_000;
|
|
8
|
+
const MAX_RECOVERY_CHILD_START_GATE_CAPABILITY_BYTES = 1024;
|
|
9
|
+
const MAX_RECOVERY_CHILD_START_GATE_SIGNAL_BYTES = 128;
|
|
10
|
+
const BUN_READER_CANCEL_TIMEOUT_MS = 1_000;
|
|
11
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
12
|
+
/**
|
|
13
|
+
* Bind a one-shot child start gate to this exact parent PID generation.
|
|
14
|
+
* The token travels in both the environment capability and a private fd4 pipe;
|
|
15
|
+
* neither input is sufficient on its own.
|
|
16
|
+
*/
|
|
17
|
+
export function prepareRecoveryChildStartGate(env, role, expectedParent) {
|
|
18
|
+
if (expectedParent?.pid !== undefined && expectedParent.pid !== process.pid) {
|
|
19
|
+
throw new Error('self_update_recovery_child_start_gate_parent_invalid');
|
|
20
|
+
}
|
|
21
|
+
const processStartIdentity = util.readFileLockProcessStartIdentity(process.pid);
|
|
22
|
+
if (!processStartIdentity
|
|
23
|
+
|| (expectedParent
|
|
24
|
+
&& !util.sameFileLockProcessStartIdentity(expectedParent.processStartIdentity, processStartIdentity))) {
|
|
25
|
+
throw new Error('self_update_recovery_child_start_gate_parent_invalid');
|
|
26
|
+
}
|
|
27
|
+
const startupGateToken = randomUUID();
|
|
28
|
+
const capability = {
|
|
29
|
+
v: 1,
|
|
30
|
+
role,
|
|
31
|
+
pid: process.pid,
|
|
32
|
+
processStartIdentity,
|
|
33
|
+
token: startupGateToken,
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
env: {
|
|
37
|
+
...env,
|
|
38
|
+
[RECOVERY_CHILD_START_GATE_ENV]: JSON.stringify(capability),
|
|
39
|
+
},
|
|
40
|
+
startupGateToken,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function clearRecoveryChildStartGate(env) {
|
|
44
|
+
delete env[RECOVERY_CHILD_START_GATE_ENV];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Consume the gate before controller/worker dispatch or durable recovery.
|
|
48
|
+
* Invalid capability, wrong role/parent generation, EOF, malformed input and
|
|
49
|
+
* timeout all fail closed. The inherited capability is scrubbed on every path.
|
|
50
|
+
*/
|
|
51
|
+
export async function consumeRecoveryChildStartGate(env, expectedRole, options = {}) {
|
|
52
|
+
const raw = env[RECOVERY_CHILD_START_GATE_ENV];
|
|
53
|
+
if (raw === undefined)
|
|
54
|
+
return false;
|
|
55
|
+
const descriptor = options.descriptor ?? RECOVERY_CHILD_START_GATE_DESCRIPTOR;
|
|
56
|
+
try {
|
|
57
|
+
const capability = parseRecoveryChildStartGateCapability(raw, expectedRole, options.parentPid ?? process.ppid, options.inspectParentProcess ?? util.inspectFileLockOwnerProcess);
|
|
58
|
+
if (!capability) {
|
|
59
|
+
await closeDescriptorBestEffort(descriptor);
|
|
60
|
+
throw new Error('self_update_recovery_child_start_gate_invalid');
|
|
61
|
+
}
|
|
62
|
+
const accepted = await readExactGateToken(descriptor, capability.token, positiveTimeout(options.timeoutMs));
|
|
63
|
+
if (!accepted) {
|
|
64
|
+
throw new Error('self_update_recovery_child_start_gate_rejected');
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
clearRecoveryChildStartGate(env);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Deliver fd4 only after the caller establishes its owned guardian or delegated authority. */
|
|
73
|
+
export function deliverRecoveryChildStartGate(child, token, timeoutMs = DEFAULT_RECOVERY_CHILD_START_GATE_TIMEOUT_MS) {
|
|
74
|
+
if (!UUID_PATTERN.test(token))
|
|
75
|
+
return Promise.resolve(false);
|
|
76
|
+
const stream = child.stdio[RECOVERY_CHILD_START_GATE_DESCRIPTOR];
|
|
77
|
+
const writable = stream;
|
|
78
|
+
if (!writable
|
|
79
|
+
|| typeof writable.on !== 'function'
|
|
80
|
+
|| typeof writable.once !== 'function'
|
|
81
|
+
|| typeof writable.off !== 'function'
|
|
82
|
+
|| typeof writable.end !== 'function'
|
|
83
|
+
|| typeof writable.destroy !== 'function') {
|
|
84
|
+
return Promise.resolve(false);
|
|
85
|
+
}
|
|
86
|
+
const gate = writable;
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
let settled = false;
|
|
89
|
+
const finish = (accepted) => {
|
|
90
|
+
if (settled)
|
|
91
|
+
return;
|
|
92
|
+
settled = true;
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
gate.off('finish', onFinish);
|
|
95
|
+
gate.off('error', onError);
|
|
96
|
+
gate.off('close', onClose);
|
|
97
|
+
resolve(accepted);
|
|
98
|
+
};
|
|
99
|
+
const onFinish = () => { finish(true); };
|
|
100
|
+
const onError = () => { finish(false); };
|
|
101
|
+
const onClose = () => { finish(false); };
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
try {
|
|
104
|
+
gate.destroy();
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// The bounded false result remains authoritative.
|
|
108
|
+
}
|
|
109
|
+
finish(false);
|
|
110
|
+
}, positiveTimeout(timeoutMs));
|
|
111
|
+
gate.once('finish', onFinish);
|
|
112
|
+
gate.once('error', onError);
|
|
113
|
+
gate.once('close', onClose);
|
|
114
|
+
gate.end(`${token}\n`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function parseRecoveryChildStartGateCapability(raw, expectedRole, parentPid, inspectParentProcess) {
|
|
118
|
+
if (!raw || Buffer.byteLength(raw, 'utf8') > MAX_RECOVERY_CHILD_START_GATE_CAPABILITY_BYTES) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(raw);
|
|
123
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
124
|
+
return undefined;
|
|
125
|
+
const record = parsed;
|
|
126
|
+
const keys = Object.keys(record).sort();
|
|
127
|
+
if (keys.length !== 5
|
|
128
|
+
|| keys[0] !== 'pid'
|
|
129
|
+
|| keys[1] !== 'processStartIdentity'
|
|
130
|
+
|| keys[2] !== 'role'
|
|
131
|
+
|| keys[3] !== 'token'
|
|
132
|
+
|| keys[4] !== 'v'
|
|
133
|
+
|| record['v'] !== 1
|
|
134
|
+
|| (record['role'] !== 'proxy-target' && record['role'] !== 'windows-updater')
|
|
135
|
+
|| record['role'] !== expectedRole
|
|
136
|
+
|| !Number.isSafeInteger(record['pid'])
|
|
137
|
+
|| record['pid'] !== parentPid
|
|
138
|
+
|| typeof record['token'] !== 'string'
|
|
139
|
+
|| !UUID_PATTERN.test(record['token'])) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
const processStartIdentity = util.parseFileLockProcessStartIdentity(record['processStartIdentity']);
|
|
143
|
+
if (!processStartIdentity)
|
|
144
|
+
return undefined;
|
|
145
|
+
const capability = {
|
|
146
|
+
v: 1,
|
|
147
|
+
role: record['role'],
|
|
148
|
+
pid: record['pid'],
|
|
149
|
+
processStartIdentity,
|
|
150
|
+
token: record['token'],
|
|
151
|
+
};
|
|
152
|
+
if (inspectParentProcess(capability) !== 'current')
|
|
153
|
+
return undefined;
|
|
154
|
+
return capability;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function readExactGateToken(descriptor, token, timeoutMs) {
|
|
161
|
+
if (typeof process.versions['bun'] === 'string') {
|
|
162
|
+
const bun = activeBunFileRuntime();
|
|
163
|
+
if (!bun)
|
|
164
|
+
return Promise.resolve(false);
|
|
165
|
+
return readExactGateTokenFromBunFile(bun, descriptor, token, timeoutMs);
|
|
166
|
+
}
|
|
167
|
+
const expected = Buffer.from(`${token}\n`, 'utf8');
|
|
168
|
+
return new Promise((resolve) => {
|
|
169
|
+
let raw = Buffer.alloc(0);
|
|
170
|
+
let settled = false;
|
|
171
|
+
let stream;
|
|
172
|
+
try {
|
|
173
|
+
// Node's libuv pipe socket can cancel an outstanding silent read on
|
|
174
|
+
// destroy; fs.ReadStream leaves that read live on Windows after timeout.
|
|
175
|
+
stream = new Socket({ fd: descriptor, readable: true, writable: false });
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Regular-file descriptors are useful for deterministic parser tests;
|
|
179
|
+
// Node production fd4 is always the cancellable pipe path above.
|
|
180
|
+
stream = createReadStream('', { fd: descriptor, autoClose: true });
|
|
181
|
+
}
|
|
182
|
+
const finish = (accepted) => {
|
|
183
|
+
if (settled)
|
|
184
|
+
return;
|
|
185
|
+
settled = true;
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
stream.off('data', onData);
|
|
188
|
+
stream.off('end', onEnd);
|
|
189
|
+
stream.off('error', onError);
|
|
190
|
+
stream.off('close', onClose);
|
|
191
|
+
if (!stream.destroyed)
|
|
192
|
+
stream.destroy();
|
|
193
|
+
resolve(accepted);
|
|
194
|
+
};
|
|
195
|
+
const onData = (chunk) => {
|
|
196
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, 'utf8');
|
|
197
|
+
if (bytes.byteLength > MAX_RECOVERY_CHILD_START_GATE_SIGNAL_BYTES - raw.byteLength) {
|
|
198
|
+
finish(false);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
raw = Buffer.concat([raw, bytes], raw.byteLength + bytes.byteLength);
|
|
202
|
+
if (!raw.equals(expected.subarray(0, raw.byteLength))) {
|
|
203
|
+
finish(false);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const onEnd = () => { finish(raw.equals(expected)); };
|
|
207
|
+
const onError = () => { finish(false); };
|
|
208
|
+
const onClose = () => { finish(false); };
|
|
209
|
+
const timer = setTimeout(() => { finish(false); }, timeoutMs);
|
|
210
|
+
stream.on('data', onData);
|
|
211
|
+
stream.once('end', onEnd);
|
|
212
|
+
stream.once('error', onError);
|
|
213
|
+
stream.once('close', onClose);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async function readExactGateTokenFromBunFile(bun, descriptor, token, timeoutMs) {
|
|
217
|
+
const expected = Buffer.from(`${token}\n`, 'utf8');
|
|
218
|
+
let reader;
|
|
219
|
+
try {
|
|
220
|
+
// Bun's net.Socket accepts an inherited Windows pipe descriptor but never
|
|
221
|
+
// observes its data or EOF. Bun.file(fd).stream() owns that pipe correctly
|
|
222
|
+
// and its reader cancellation also releases a silent outstanding read.
|
|
223
|
+
reader = bun.file(descriptor).stream().getReader();
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const timedOut = Symbol('recovery-child-start-gate-timeout');
|
|
229
|
+
let timer;
|
|
230
|
+
const timeout = new Promise((resolve) => {
|
|
231
|
+
timer = setTimeout(() => { resolve(timedOut); }, timeoutMs);
|
|
232
|
+
});
|
|
233
|
+
let raw = Buffer.alloc(0);
|
|
234
|
+
let completed = false;
|
|
235
|
+
try {
|
|
236
|
+
for (;;) {
|
|
237
|
+
const result = await Promise.race([
|
|
238
|
+
reader.read().catch(() => undefined),
|
|
239
|
+
timeout,
|
|
240
|
+
]);
|
|
241
|
+
if (result === timedOut || result === undefined)
|
|
242
|
+
return false;
|
|
243
|
+
if (result.done) {
|
|
244
|
+
completed = true;
|
|
245
|
+
return raw.equals(expected);
|
|
246
|
+
}
|
|
247
|
+
const bytes = Buffer.from(result.value);
|
|
248
|
+
if (bytes.byteLength > MAX_RECOVERY_CHILD_START_GATE_SIGNAL_BYTES - raw.byteLength) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
raw = Buffer.concat([raw, bytes], raw.byteLength + bytes.byteLength);
|
|
252
|
+
if (!raw.equals(expected.subarray(0, raw.byteLength)))
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
if (timer !== undefined)
|
|
258
|
+
clearTimeout(timer);
|
|
259
|
+
if (!completed) {
|
|
260
|
+
try {
|
|
261
|
+
await cancelBunReaderBestEffort(reader);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Rejection remains authoritative after cancellation was attempted.
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function activeBunFileRuntime() {
|
|
270
|
+
const bun = globalThis.Bun;
|
|
271
|
+
return bun && typeof bun.file === 'function'
|
|
272
|
+
? bun
|
|
273
|
+
: undefined;
|
|
274
|
+
}
|
|
275
|
+
async function cancelBunReaderBestEffort(reader) {
|
|
276
|
+
await new Promise((resolve) => {
|
|
277
|
+
let settled = false;
|
|
278
|
+
const finish = () => {
|
|
279
|
+
if (settled)
|
|
280
|
+
return;
|
|
281
|
+
settled = true;
|
|
282
|
+
clearTimeout(timer);
|
|
283
|
+
resolve();
|
|
284
|
+
};
|
|
285
|
+
const timer = setTimeout(finish, BUN_READER_CANCEL_TIMEOUT_MS);
|
|
286
|
+
try {
|
|
287
|
+
void reader.cancel('self_update_recovery_child_start_gate_closed').then(finish, finish);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
finish();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
async function closeDescriptorBestEffort(descriptor) {
|
|
295
|
+
if (typeof process.versions['bun'] === 'string') {
|
|
296
|
+
const bun = activeBunFileRuntime();
|
|
297
|
+
if (!bun)
|
|
298
|
+
return;
|
|
299
|
+
try {
|
|
300
|
+
const reader = bun.file(descriptor).stream().getReader();
|
|
301
|
+
await cancelBunReaderBestEffort(reader);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// The invalid capability remains authoritative.
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
closeSync(descriptor);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// The capability rejection remains authoritative.
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function positiveTimeout(value) {
|
|
316
|
+
return Number.isFinite(value) && (value ?? 0) > 0
|
|
317
|
+
? Math.floor(value)
|
|
318
|
+
: DEFAULT_RECOVERY_CHILD_START_GATE_TIMEOUT_MS;
|
|
319
|
+
}
|
|
@@ -11,8 +11,18 @@ export interface ReleaseBinaryOptions {
|
|
|
11
11
|
targetPath?: string;
|
|
12
12
|
processExecPath?: string;
|
|
13
13
|
requireSignedManifest?: boolean;
|
|
14
|
+
maxPrimaryBinaryBytes?: number;
|
|
14
15
|
maxExtractedTarballBytes?: number;
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Hard ceiling for primary release binaries. The binary is buffered before its
|
|
19
|
+
* manifest hash is verified, so an unbounded response could exhaust memory
|
|
20
|
+
* before the verification gate runs. 128MiB leaves headroom above current
|
|
21
|
+
* single-platform binaries while bounding that pre-verification allocation.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MAX_PRIMARY_BINARY_BYTES: number;
|
|
24
|
+
/** Release metadata is untrusted and buffered before parsing or verification. */
|
|
25
|
+
export declare const MAX_RELEASE_METADATA_BYTES: number;
|
|
16
26
|
/**
|
|
17
27
|
* Hard ceiling for Channel 1b tarball downloads. A compromised/corrupt release
|
|
18
28
|
* could advertise a multi-GB tar.gz and OOM us because tarballBytes is buffered
|
|
@@ -26,6 +36,7 @@ export declare const MAX_TARBALL_BYTES: number;
|
|
|
26
36
|
* bombs fail before the extractor accumulates unbounded output in memory.
|
|
27
37
|
*/
|
|
28
38
|
export declare const MAX_EXTRACTED_TARBALL_BYTES: number;
|
|
39
|
+
export declare function isStandaloneReleaseBinaryName(name: string): boolean;
|
|
29
40
|
export declare function requiredVersionForRelease(raw: unknown): string;
|
|
30
41
|
export declare function releaseAssetName(platform?: NodeJS.Platform, arch?: NodeJS.Architecture): string;
|
|
31
42
|
export declare function resolveGithubReleaseManifest(directive: ForceUpdateDirective, opts?: ReleaseBinaryOptions): Promise<UpdateManifest>;
|
|
@@ -53,4 +64,6 @@ export declare function resolveSelfUpdateTarget(opts?: ReleaseBinaryOptions): {
|
|
|
53
64
|
path: string;
|
|
54
65
|
explicit: boolean;
|
|
55
66
|
};
|
|
67
|
+
export declare function assertSelfUpdateProcessTargetBound(options: ReleaseBinaryOptions, allowUnresolvedTarget?: boolean): void;
|
|
68
|
+
export declare function selfUpdateProcessTargetBindable(options: ReleaseBinaryOptions): boolean;
|
|
56
69
|
export {};
|