0sec-cli 0.22.0 → 0.22.4
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/0.js +56 -45
- package/README.md +12 -0
- package/chunks/{adapt-loop-7I57LXXR.js → adapt-loop-3CCOWE43.js} +4 -4
- package/chunks/{artifact-scraper-PYZ3RU3O.js → artifact-scraper-Z4UUIOH3.js} +2 -2
- package/chunks/{assumption-mining-MFLF43IR.js → assumption-mining-2UAKQB6V.js} +18 -14
- package/chunks/{chunk-RE5EZDG2.js → chunk-2WWAPKAW.js} +126 -1089
- package/chunks/{chunk-LPTRPRZR.js → chunk-3BPX75OR.js} +1 -1
- package/chunks/chunk-4ZYYX5GY.js +74 -0
- package/chunks/{chunk-RSIUX3RW.js → chunk-5JNTMWPK.js} +2 -2
- package/chunks/{chunk-RRD4XXPU.js → chunk-5WAJGLCW.js} +3 -3
- package/chunks/{chunk-76KSBWOQ.js → chunk-6KFB6AYN.js} +515 -20
- package/chunks/{chunk-HR2TU42B.js → chunk-6WQDXWQC.js} +169 -15
- package/chunks/chunk-6YZE552K.js +5603 -0
- package/chunks/chunk-BGX53365.js +309 -0
- package/chunks/{chunk-7DEHG7YT.js → chunk-C6JPA32H.js} +1 -1
- package/chunks/chunk-CF3MNS3O.js +990 -0
- package/chunks/{chunk-4VGLO2YS.js → chunk-CPCHLL6A.js} +5 -2
- package/chunks/{chunk-DM5C5L5D.js → chunk-DPUXXMJW.js} +1 -1
- package/chunks/{chunk-LYUAM3TG.js → chunk-E3OOBVOK.js} +30 -12
- package/chunks/{chunk-HUNAPTKV.js → chunk-E74LXQFY.js} +1 -1
- package/chunks/{chunk-UU7PQP6N.js → chunk-ELI3BVIK.js} +3 -3
- package/chunks/chunk-FFHRANYC.js +547 -0
- package/chunks/{chunk-IO7R7WBM.js → chunk-G2BE2FC7.js} +1 -1
- package/chunks/chunk-GQ3DGHWE.js +1659 -0
- package/chunks/{chunk-V36HVTUL.js → chunk-GVY3S5E5.js} +21 -18
- package/chunks/{chunk-UV5QWZGE.js → chunk-H4DH7COV.js} +192 -40
- package/chunks/{chunk-SJBCK2EI.js → chunk-H6Q2QAIY.js} +23475 -22747
- package/chunks/{chunk-VMRRNRMR.js → chunk-IHPTOTLL.js} +1 -1
- package/chunks/chunk-IXAHQICR.js +31 -0
- package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
- package/chunks/{chunk-VNRY6AWF.js → chunk-L2CCO4KE.js} +282 -34
- package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
- package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
- package/chunks/{chunk-EGZGZFAH.js → chunk-OAS5SAVZ.js} +6768 -2100
- package/chunks/chunk-PS5E7LUQ.js +277 -0
- package/chunks/{chunk-2GV4HQKE.js → chunk-QVIRWJ45.js} +257 -145
- package/chunks/{chunk-OWLJYJ5O.js → chunk-QWK4R3DB.js} +9 -9
- package/chunks/{chunk-XFCTUKXR.js → chunk-RCRTSKQN.js} +1 -1
- package/chunks/chunk-RS4MAN3Q.js +73 -0
- package/chunks/{chunk-7UFUALR2.js → chunk-RTBZ36BD.js} +8 -274
- package/chunks/{chunk-TZFIAMWZ.js → chunk-SCWWLABR.js} +1 -1
- package/chunks/chunk-VDOWARMJ.js +63 -0
- package/chunks/{chunk-5MWAG3FT.js → chunk-WBRGXCU2.js} +79 -18
- package/chunks/{chunk-LCRTAQN2.js → chunk-WHUAUFSX.js} +1 -1
- package/chunks/{chunk-YIQSGNAA.js → chunk-XODDLNBI.js} +1535 -1306
- package/chunks/chunk-YAZARMJY.js +355 -0
- package/chunks/{chunk-OAYUPGF6.js → chunk-YED25PA4.js} +1 -1
- package/chunks/{codex-models-7TLJSY3L.js → codex-models-Z43V3427.js} +9 -3
- package/chunks/{commands-NEI7L3QA.js → commands-EUGF4FUZ.js} +5844 -4095
- package/chunks/{db-5AP3JB4W.js → db-NVAW4WWR.js} +3 -3
- package/chunks/{dist-3FNZIBB4.js → dist-27BNG6WD.js} +195 -112
- package/chunks/{dist-5YHYNLI3.js → dist-7RUOKPRJ.js} +12 -2
- package/chunks/{dist-KEULE5IQ.js → dist-LNXGATR2.js} +2 -2
- package/chunks/{dist-XZB53PS5.js → dist-M2X367VW.js} +2 -2
- package/chunks/{dist-DD4H6HMQ.js → dist-NG2HT5XU.js} +27 -1
- package/chunks/{eval-runner-43BO2C4T.js → eval-runner-6Q3H52SP.js} +13 -9
- package/chunks/{exploit-agent-N4A5ELBK.js → exploit-agent-FCCR2HYX.js} +1 -1
- package/chunks/{exploit-autoclimb-N7P6KIF5.js → exploit-autoclimb-X5ZS5FAO.js} +1 -1
- package/chunks/{exploit-climb-WNEJUB5W.js → exploit-climb-3JHGEO36.js} +2 -2
- package/chunks/{fix-Z452M4EC.js → fix-NSNPU33B.js} +12 -6
- package/chunks/{harness-QJJJVSG4.js → harness-KXQOVIDQ.js} +3 -3
- package/chunks/{hunt-scan-K6D2V3PX.js → hunt-scan-ELTKGKC4.js} +16 -12
- package/chunks/{kernel-primitive-TKUACPKD.js → kernel-primitive-MT7YZSIG.js} +2 -2
- package/chunks/{kernel-vm-runner-RAAO2PGT.js → kernel-vm-runner-YFRHYC6G.js} +2 -2
- package/chunks/{native-loop-2ZRJENZ3.js → native-loop-OFJDRHPI.js} +18 -8
- package/chunks/orchestrate-DRZ2SVCP.js +62 -0
- package/chunks/{prepare-KYHGFWCJ.js → prepare-72GWTRVI.js} +6 -2
- package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
- package/chunks/{run-U3XU4CTC.js → run-WTJCZZHV.js} +3091 -6037
- package/chunks/{runtime-G75TEWQY.js → runtime-FCKOUB7V.js} +10 -4
- package/chunks/{runtime-XFWJTBUO.js → runtime-LCJ4XSKE.js} +5 -1
- package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-GT6GKQ3U.js} +7 -1
- package/chunks/{scope-WXI46UHJ.js → scope-E5QZUOOH.js} +2 -2
- package/chunks/service-plugins-MSK2EQP6.js +62 -0
- package/chunks/{session-store-27L3ZU73.js → session-store-H3Q7KCYG.js} +6 -4
- package/chunks/{variant-candidates-YWZORZMK.js → variant-candidates-SZKDCOLK.js} +9 -3
- package/chunks/{web-recon-prepass-YVMFXCIK.js → web-recon-prepass-IO3GXQMW.js} +11 -6
- package/dashboard/apple-touch-icon.png +0 -0
- package/dashboard/assets/operations-BvB5Qpmk.css +1 -0
- package/dashboard/assets/operations-D8Ii01SV.js +114 -0
- package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
- package/dashboard/favicon.ico +0 -0
- package/dashboard/favicon.svg +10 -0
- package/dashboard/index.html +5 -3
- package/package.json +1 -1
- package/chunks/chunk-6GHSR47F.js +0 -147
- package/chunks/chunk-CAYANSH6.js +0 -3524
- package/chunks/chunk-GHWODZMR.js +0 -16
- package/chunks/chunk-MICHDTBK.js +0 -193
- package/chunks/orchestrate-2GWGNTBF.js +0 -58
- package/dashboard/assets/bot-Bg1Tp9_d.js +0 -1
- package/dashboard/assets/dist-B_dDCPbu.js +0 -1
- package/dashboard/assets/findings-page-mayGhNx6.js +0 -5
- package/dashboard/assets/format--susmHX7.js +0 -1
- package/dashboard/assets/input-TuVPR3Zl.js +0 -1
- package/dashboard/assets/jsx-runtime-MGseXjj5.js +0 -3
- package/dashboard/assets/live-page-HWWHX99U.js +0 -1
- package/dashboard/assets/meta-tile-ckHo-w7k.js +0 -1
- package/dashboard/assets/operations-BxZ0OoXO.js +0 -10
- package/dashboard/assets/operations-CMFVrXEe.css +0 -2
- package/dashboard/assets/operations-app-D3d35rgo.js +0 -46
- package/dashboard/assets/overview-page-W_4IteEr.js +0 -1
- package/dashboard/assets/page-header-Bw9AJgBV.js +0 -1
- package/dashboard/assets/scans-page-CgzVXHnn.js +0 -1
- package/dashboard/assets/siren-KQj8QAnJ.js +0 -1
- package/dashboard/assets/table-8EDklulN.js +0 -1
- package/dashboard/assets/tabs-CFHPOwg8.js +0 -1
|
@@ -5,9 +5,11 @@ import {
|
|
|
5
5
|
redactAuthHeaders
|
|
6
6
|
} from "./chunk-UELXHK2Y.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
eventBus
|
|
9
|
+
} from "./chunk-4ZYYX5GY.js";
|
|
10
|
+
import {
|
|
11
|
+
ScopePolicy
|
|
12
|
+
} from "./chunk-C6JPA32H.js";
|
|
11
13
|
import {
|
|
12
14
|
allowlistedChildEnv,
|
|
13
15
|
sanitizedEnv
|
|
@@ -15,172 +17,12 @@ import {
|
|
|
15
17
|
import {
|
|
16
18
|
VERSION,
|
|
17
19
|
cloudStateDir,
|
|
18
|
-
createPresentationEvent,
|
|
19
20
|
homeStateDir
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-6KFB6AYN.js";
|
|
21
22
|
import {
|
|
22
23
|
external_exports
|
|
23
24
|
} from "./chunk-VIT5ALXF.js";
|
|
24
25
|
|
|
25
|
-
// packages/core/dist/bench/improvement-promotion.js
|
|
26
|
-
import { createHash } from "node:crypto";
|
|
27
|
-
var DEFAULT_IMPROVEMENT_PROMOTION_POLICY = Object.freeze({
|
|
28
|
-
minimumCases: 10,
|
|
29
|
-
minimumDevelopmentLift: 0.05,
|
|
30
|
-
minimumHeldOutLift: 0.03,
|
|
31
|
-
maximumNegativeControlFpDelta: 0.02,
|
|
32
|
-
maximumCostMultiplier: 1.5
|
|
33
|
-
});
|
|
34
|
-
function canonicalJson(value) {
|
|
35
|
-
if (value === null || typeof value !== "object")
|
|
36
|
-
return JSON.stringify(value);
|
|
37
|
-
if (Array.isArray(value))
|
|
38
|
-
return `[${value.map(canonicalJson).join(",")}]`;
|
|
39
|
-
const record = value;
|
|
40
|
-
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
41
|
-
}
|
|
42
|
-
function digest(value) {
|
|
43
|
-
return `sha256:${createHash("sha256").update(canonicalJson(value)).digest("hex")}`;
|
|
44
|
-
}
|
|
45
|
-
function isSha256(value) {
|
|
46
|
-
return /^sha256:[a-f0-9]{64}$/i.test(value);
|
|
47
|
-
}
|
|
48
|
-
function metricSampleIsSufficient(result, minimumCases) {
|
|
49
|
-
return result.development.champion.cases >= minimumCases && result.development.challenger.cases >= minimumCases && result.heldOut.champion.cases >= minimumCases && result.heldOut.challenger.cases >= minimumCases && result.negativeControls.champion.cases >= minimumCases && result.negativeControls.challenger.cases >= minimumCases;
|
|
50
|
-
}
|
|
51
|
-
function resultRatesAreFinite(result) {
|
|
52
|
-
return [
|
|
53
|
-
result.development.champion.successRate,
|
|
54
|
-
result.development.challenger.successRate,
|
|
55
|
-
result.heldOut.champion.successRate,
|
|
56
|
-
result.heldOut.challenger.successRate,
|
|
57
|
-
result.negativeControls.champion.falsePositiveRate,
|
|
58
|
-
result.negativeControls.challenger.falsePositiveRate
|
|
59
|
-
].every((value) => Number.isFinite(value) && value >= 0 && value <= 1);
|
|
60
|
-
}
|
|
61
|
-
function makeDecision(candidateId, status, checks) {
|
|
62
|
-
const unsigned = {
|
|
63
|
-
schemaVersion: 1,
|
|
64
|
-
candidateId,
|
|
65
|
-
status,
|
|
66
|
-
checks
|
|
67
|
-
};
|
|
68
|
-
return {
|
|
69
|
-
...unsigned,
|
|
70
|
-
decisionDigest: digest(unsigned)
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function evaluateImprovementPromotion(candidate, policy = DEFAULT_IMPROVEMENT_PROMOTION_POLICY) {
|
|
74
|
-
const result = candidate.result;
|
|
75
|
-
const checks = [];
|
|
76
|
-
checks.push({
|
|
77
|
-
id: "candidate_identity",
|
|
78
|
-
passed: candidate.candidateId.length > 0 && candidate.candidateId === result.candidateId,
|
|
79
|
-
detail: "candidate id must be non-empty and match the sealed research result"
|
|
80
|
-
});
|
|
81
|
-
checks.push({
|
|
82
|
-
id: "artifact_digests",
|
|
83
|
-
passed: isSha256(candidate.baseArtifactDigest) && isSha256(candidate.candidateArtifactDigest) && candidate.baseArtifactDigest !== candidate.candidateArtifactDigest,
|
|
84
|
-
detail: "base and candidate artifacts must be distinct sha256 digests"
|
|
85
|
-
});
|
|
86
|
-
checks.push({
|
|
87
|
-
id: "ci",
|
|
88
|
-
passed: result.ciPassed,
|
|
89
|
-
detail: "candidate evaluation must carry a passing CI attestation"
|
|
90
|
-
});
|
|
91
|
-
checks.push({
|
|
92
|
-
id: "evaluator_stability",
|
|
93
|
-
passed: isSha256(result.evaluatorDigestBefore) && result.evaluatorDigestBefore === result.evaluatorDigestAfter,
|
|
94
|
-
detail: "the evaluator digest must remain stable across the candidate evaluation"
|
|
95
|
-
});
|
|
96
|
-
checks.push({
|
|
97
|
-
id: "evidence",
|
|
98
|
-
passed: result.evidenceRefs.length > 0,
|
|
99
|
-
detail: "candidate evaluation must retain at least one evidence reference"
|
|
100
|
-
});
|
|
101
|
-
checks.push({
|
|
102
|
-
id: "sample_size",
|
|
103
|
-
passed: resultRatesAreFinite(result) && metricSampleIsSufficient(result, policy.minimumCases),
|
|
104
|
-
detail: `every sealed lane must contain at least ${policy.minimumCases} finite observations`
|
|
105
|
-
});
|
|
106
|
-
const developmentLift = result.development.challenger.successRate - result.development.champion.successRate;
|
|
107
|
-
checks.push({
|
|
108
|
-
id: "development_lift",
|
|
109
|
-
passed: developmentLift >= policy.minimumDevelopmentLift,
|
|
110
|
-
detail: `development success lift ${developmentLift.toFixed(4)} must be at least ${policy.minimumDevelopmentLift.toFixed(4)}`
|
|
111
|
-
});
|
|
112
|
-
const heldOutLift = result.heldOut.challenger.successRate - result.heldOut.champion.successRate;
|
|
113
|
-
checks.push({
|
|
114
|
-
id: "held_out_lift",
|
|
115
|
-
passed: heldOutLift >= policy.minimumHeldOutLift,
|
|
116
|
-
detail: `held-out success lift ${heldOutLift.toFixed(4)} must be at least ${policy.minimumHeldOutLift.toFixed(4)}`
|
|
117
|
-
});
|
|
118
|
-
const fpDelta = result.negativeControls.challenger.falsePositiveRate - result.negativeControls.champion.falsePositiveRate;
|
|
119
|
-
checks.push({
|
|
120
|
-
id: "negative_control_precision",
|
|
121
|
-
passed: fpDelta <= policy.maximumNegativeControlFpDelta,
|
|
122
|
-
detail: `negative-control FP delta ${fpDelta.toFixed(4)} must not exceed ${policy.maximumNegativeControlFpDelta.toFixed(4)}`
|
|
123
|
-
});
|
|
124
|
-
const championCost = result.heldOut.champion.costPerSuccessUsd;
|
|
125
|
-
const challengerCost = result.heldOut.challenger.costPerSuccessUsd;
|
|
126
|
-
const zeroSuccessBaseline = championCost === null && result.heldOut.champion.successRate === 0 && result.heldOut.champion.inconclusiveRate === 0;
|
|
127
|
-
const costPasses = challengerCost !== null && Number.isFinite(challengerCost) && challengerCost >= 0 && (zeroSuccessBaseline || championCost !== null && Number.isFinite(championCost) && championCost >= 0 && challengerCost <= championCost * policy.maximumCostMultiplier);
|
|
128
|
-
checks.push({
|
|
129
|
-
id: "cost_discipline",
|
|
130
|
-
passed: costPasses,
|
|
131
|
-
detail: `held-out challenger cost must be finite and nonnegative; at most ${policy.maximumCostMultiplier.toFixed(2)}x champion cost unless the fully observed champion has zero successes`
|
|
132
|
-
});
|
|
133
|
-
if (checks.some((check) => !check.passed)) {
|
|
134
|
-
return makeDecision(candidate.candidateId, "rejected", checks);
|
|
135
|
-
}
|
|
136
|
-
return makeDecision(candidate.candidateId, candidate.kind === "source" ? "requires_human_approval" : "eligible_for_canary", checks);
|
|
137
|
-
}
|
|
138
|
-
function appendImprovementLedgerEntry(entries, event) {
|
|
139
|
-
const verification = verifyImprovementLedger(entries);
|
|
140
|
-
if (!verification.valid)
|
|
141
|
-
throw new Error(`cannot append to invalid improvement ledger: ${verification.reason}`);
|
|
142
|
-
if (!isSha256(event.payloadDigest))
|
|
143
|
-
throw new Error("ledger payloadDigest must be a sha256 digest");
|
|
144
|
-
const previousDigest = entries.length === 0 ? null : entries[entries.length - 1].entryDigest;
|
|
145
|
-
const unsigned = {
|
|
146
|
-
schemaVersion: 1,
|
|
147
|
-
sequence: entries.length,
|
|
148
|
-
occurredAt: event.occurredAt,
|
|
149
|
-
type: event.type,
|
|
150
|
-
candidateId: event.candidateId,
|
|
151
|
-
payloadDigest: event.payloadDigest,
|
|
152
|
-
previousDigest
|
|
153
|
-
};
|
|
154
|
-
return {
|
|
155
|
-
...unsigned,
|
|
156
|
-
entryDigest: digest(unsigned)
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
function verifyImprovementLedger(entries) {
|
|
160
|
-
let previousDigest = null;
|
|
161
|
-
for (const [sequence, entry] of entries.entries()) {
|
|
162
|
-
if (entry.sequence !== sequence)
|
|
163
|
-
return { valid: false, reason: `unexpected sequence at ${sequence}` };
|
|
164
|
-
if (entry.previousDigest !== previousDigest)
|
|
165
|
-
return { valid: false, reason: `broken predecessor at ${sequence}` };
|
|
166
|
-
if (!isSha256(entry.payloadDigest))
|
|
167
|
-
return { valid: false, reason: `invalid payload digest at ${sequence}` };
|
|
168
|
-
const unsigned = {
|
|
169
|
-
schemaVersion: entry.schemaVersion,
|
|
170
|
-
sequence: entry.sequence,
|
|
171
|
-
occurredAt: entry.occurredAt,
|
|
172
|
-
type: entry.type,
|
|
173
|
-
candidateId: entry.candidateId,
|
|
174
|
-
payloadDigest: entry.payloadDigest,
|
|
175
|
-
previousDigest: entry.previousDigest
|
|
176
|
-
};
|
|
177
|
-
if (entry.entryDigest !== digest(unsigned))
|
|
178
|
-
return { valid: false, reason: `invalid entry digest at ${sequence}` };
|
|
179
|
-
previousDigest = entry.entryDigest;
|
|
180
|
-
}
|
|
181
|
-
return { valid: true };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
26
|
// packages/core/dist/telemetry/analytics-level.js
|
|
185
27
|
var LEVEL_ORDER = {
|
|
186
28
|
off: 0,
|
|
@@ -217,69 +59,6 @@ function resolveAnalyticsLevel(env = process.env) {
|
|
|
217
59
|
return isAnalyticsLevel(normalized) ? normalized : "off";
|
|
218
60
|
}
|
|
219
61
|
|
|
220
|
-
// packages/core/dist/events/bus.js
|
|
221
|
-
function presentationEventSink(sink, options = {}) {
|
|
222
|
-
let sequence = 0;
|
|
223
|
-
const source = options.source ?? "core";
|
|
224
|
-
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
225
|
-
return {
|
|
226
|
-
emit(type, payload) {
|
|
227
|
-
const scanId = typeof payload["scan_id"] === "string" ? payload["scan_id"] : typeof payload["scanId"] === "string" ? payload["scanId"] : void 0;
|
|
228
|
-
const sessionId = typeof payload["session_id"] === "string" ? payload["session_id"] : typeof payload["sessionId"] === "string" ? payload["sessionId"] : void 0;
|
|
229
|
-
sink.emit(createPresentationEvent({
|
|
230
|
-
source,
|
|
231
|
-
sequence: ++sequence,
|
|
232
|
-
at: now(),
|
|
233
|
-
eventType: type,
|
|
234
|
-
payload,
|
|
235
|
-
...scanId ? { scanId } : {},
|
|
236
|
-
...sessionId ? { sessionId } : {}
|
|
237
|
-
}));
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
var EventBus = class {
|
|
242
|
-
sinks = [];
|
|
243
|
-
/** Subscribe a sink. Returns an unsubscribe function. */
|
|
244
|
-
subscribe(sink) {
|
|
245
|
-
this.sinks.push(sink);
|
|
246
|
-
return () => {
|
|
247
|
-
const idx = this.sinks.indexOf(sink);
|
|
248
|
-
if (idx >= 0)
|
|
249
|
-
this.sinks.splice(idx, 1);
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
/** Remove every subscriber — primarily useful in tests. */
|
|
253
|
-
clear() {
|
|
254
|
-
this.sinks = [];
|
|
255
|
-
}
|
|
256
|
-
/** Returns a read-only snapshot of the current sinks (for tests). */
|
|
257
|
-
get size() {
|
|
258
|
-
return this.sinks.length;
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Fan out a single event to every subscribed sink. Exceptions thrown by
|
|
262
|
-
* any sink are swallowed — one misbehaving consumer must never abort a
|
|
263
|
-
* scan. Diagnostic is written to stderr so the failure is visible.
|
|
264
|
-
*/
|
|
265
|
-
emit(type, payload) {
|
|
266
|
-
const snapshot2 = this.sinks.slice();
|
|
267
|
-
for (const sink of snapshot2) {
|
|
268
|
-
try {
|
|
269
|
-
sink.emit(type, payload);
|
|
270
|
-
} catch (err) {
|
|
271
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
272
|
-
try {
|
|
273
|
-
process.stderr.write(`[0 event-bus] sink threw on ${type}: ${msg}
|
|
274
|
-
`);
|
|
275
|
-
} catch {
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
var eventBus = new EventBus();
|
|
282
|
-
|
|
283
62
|
// packages/core/dist/cloud/credentials.js
|
|
284
63
|
import { readFileSync, statSync } from "node:fs";
|
|
285
64
|
import { join } from "node:path";
|
|
@@ -1205,7 +984,7 @@ function maybeSubscribeAnalyticsPipeline() {
|
|
|
1205
984
|
|
|
1206
985
|
// packages/core/dist/telemetry/run-contribution.js
|
|
1207
986
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1208
|
-
import { createHash
|
|
987
|
+
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
1209
988
|
import { appendFileSync as appendFileSync2, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync3, readdirSync, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1210
989
|
import { isAbsolute, join as join4 } from "node:path";
|
|
1211
990
|
var CONTRIBUTION_ENDPOINT = "/api/run-contributions";
|
|
@@ -1280,7 +1059,7 @@ var contributionClientPolicySchema = external_exports.object({
|
|
|
1280
1059
|
}).strict();
|
|
1281
1060
|
var PROCESS_INSTANCE = randomUUID2();
|
|
1282
1061
|
var context = new AsyncLocalStorage();
|
|
1283
|
-
var hash = (value) =>
|
|
1062
|
+
var hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
1284
1063
|
function syncDirectory(path) {
|
|
1285
1064
|
const fd = openSync(path, "r");
|
|
1286
1065
|
try {
|
|
@@ -1957,7 +1736,7 @@ function reportUnsupportedContributionMode(mode) {
|
|
|
1957
1736
|
|
|
1958
1737
|
// packages/core/dist/runtime/smolvm-provision.js
|
|
1959
1738
|
import { execFile } from "node:child_process";
|
|
1960
|
-
import { createHash as
|
|
1739
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
1961
1740
|
import { constants, createWriteStream } from "node:fs";
|
|
1962
1741
|
import { chmod, copyFile, lstat, mkdir, open, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
1963
1742
|
import { homedir } from "node:os";
|
|
@@ -2287,13 +2066,13 @@ async function privateSmolvmDirectory(path) {
|
|
|
2287
2066
|
throw new Error(`SmolVM state must be a private operator-owned directory: ${path}`);
|
|
2288
2067
|
}
|
|
2289
2068
|
}
|
|
2290
|
-
function smolvmExec(binary,
|
|
2291
|
-
const { promise, resolve:
|
|
2292
|
-
execFile(binary,
|
|
2069
|
+
function smolvmExec(binary, argv, options = {}) {
|
|
2070
|
+
const { promise, resolve: resolve3, reject } = Promise.withResolvers();
|
|
2071
|
+
execFile(binary, argv, { env: { PATH: "/usr/bin:/bin:/usr/sbin:/sbin" }, ...options, timeout: 12e4, maxBuffer: 4 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
2293
2072
|
if (error)
|
|
2294
2073
|
reject(new Error(`${binary} failed: ${stderr.trim() || error.message}`));
|
|
2295
2074
|
else
|
|
2296
|
-
|
|
2075
|
+
resolve3({ stdout, stderr });
|
|
2297
2076
|
});
|
|
2298
2077
|
return promise;
|
|
2299
2078
|
}
|
|
@@ -2301,9 +2080,9 @@ async function smolvmArchiveDigest(path, signal) {
|
|
|
2301
2080
|
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
2302
2081
|
try {
|
|
2303
2082
|
const info = await file.stat();
|
|
2304
|
-
if (!info.isFile() || info.size <= 0 || info.size >
|
|
2305
|
-
throw new Error("SmolVM image must be a regular nonempty archive no larger than
|
|
2306
|
-
const hash3 =
|
|
2083
|
+
if (!info.isFile() || info.size <= 0 || info.size > 8 * 1024 ** 3)
|
|
2084
|
+
throw new Error("SmolVM image must be a regular nonempty archive no larger than 8 GiB");
|
|
2085
|
+
const hash3 = createHash2("sha256");
|
|
2307
2086
|
for await (const chunk of file.createReadStream({ autoClose: false, signal }))
|
|
2308
2087
|
hash3.update(chunk);
|
|
2309
2088
|
return `sha256:${hash3.digest("hex")}`;
|
|
@@ -2313,7 +2092,7 @@ async function smolvmArchiveDigest(path, signal) {
|
|
|
2313
2092
|
}
|
|
2314
2093
|
function runtimePaths(root) {
|
|
2315
2094
|
const bundleRoot = join5(root, "runtime", BUNDLE_NAME);
|
|
2316
|
-
const sourceDigest =
|
|
2095
|
+
const sourceDigest = createHash2("sha256").update(SMOLVM_DARWIN_SUPERVISOR_SOURCE).digest("hex").slice(0, 16);
|
|
2317
2096
|
return { version: VERSION2, binary: join5(bundleRoot, "smolvm"), bundleRoot, supervisor: join5(root, "runtime", `supervisor-${sourceDigest}`) };
|
|
2318
2097
|
}
|
|
2319
2098
|
async function verifyBundle(runtime, signal) {
|
|
@@ -2349,7 +2128,7 @@ async function resolveSmolvmRuntime(options = {}) {
|
|
|
2349
2128
|
throw new Error(`Pinned SmolVM download failed (HTTP ${response.status})`);
|
|
2350
2129
|
const archive = join5(staging, "bundle.tar.gz");
|
|
2351
2130
|
let bytes = 0;
|
|
2352
|
-
const hash3 =
|
|
2131
|
+
const hash3 = createHash2("sha256");
|
|
2353
2132
|
const source = Readable.fromWeb(response.body);
|
|
2354
2133
|
source.on("data", (chunk) => {
|
|
2355
2134
|
bytes += chunk.length;
|
|
@@ -2396,21 +2175,21 @@ async function approveSmolvmWorkbenchImage(options) {
|
|
|
2396
2175
|
await privateSmolvmDirectory(options.stateRoot);
|
|
2397
2176
|
const images = join5(options.stateRoot, "images");
|
|
2398
2177
|
await privateSmolvmDirectory(images);
|
|
2399
|
-
const
|
|
2400
|
-
const destination = join5(images, `${
|
|
2178
|
+
const digest = await smolvmArchiveDigest(options.imageArchive, options.signal);
|
|
2179
|
+
const destination = join5(images, `${digest.slice(7)}.tar`);
|
|
2401
2180
|
if (options.imageArchive !== destination) {
|
|
2402
2181
|
const staging = join5(images, `.approve-${randomBytes(8).toString("hex")}.tar`);
|
|
2403
2182
|
try {
|
|
2404
2183
|
await copyFile(options.imageArchive, staging, constants.COPYFILE_EXCL | constants.COPYFILE_FICLONE);
|
|
2405
2184
|
await chmod(staging, 384);
|
|
2406
|
-
if (await smolvmArchiveDigest(staging, options.signal) !==
|
|
2185
|
+
if (await smolvmArchiveDigest(staging, options.signal) !== digest)
|
|
2407
2186
|
throw new Error("Image changed during approval");
|
|
2408
2187
|
await rename(staging, destination);
|
|
2409
2188
|
} finally {
|
|
2410
2189
|
await rm(staging, { force: true });
|
|
2411
2190
|
}
|
|
2412
2191
|
}
|
|
2413
|
-
const image = { path: destination, digest
|
|
2192
|
+
const image = { path: destination, digest };
|
|
2414
2193
|
await writeFile(`${destination}.json`, JSON.stringify({ schemaVersion: 1, ...image }), { flag: "w", mode: 384 });
|
|
2415
2194
|
return image;
|
|
2416
2195
|
}
|
|
@@ -2456,7 +2235,7 @@ async function getSmolvmWorkbenchStatus(options) {
|
|
|
2456
2235
|
}
|
|
2457
2236
|
|
|
2458
2237
|
// packages/core/dist/runtime/smolvm-broker.js
|
|
2459
|
-
import { createHash as
|
|
2238
|
+
import { createHash as createHash4, randomBytes as randomBytes3 } from "node:crypto";
|
|
2460
2239
|
import { lookup as lookup2 } from "node:dns/promises";
|
|
2461
2240
|
import { constants as constants4, closeSync as closeSync2, fstatSync, lstatSync as lstatSync2, openSync as openSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
2462
2241
|
import { chmod as chmod2, copyFile as copyFile3, lstat as lstat3, mkdir as mkdir4, mkdtemp as mkdtemp4, open as open3, readdir as readdir2, realpath as realpath3, rename as rename2, rm as rm5, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -2484,7 +2263,7 @@ var ALLOWED_SCANNER_BINARIES = /* @__PURE__ */ new Set([
|
|
|
2484
2263
|
"ffuf",
|
|
2485
2264
|
"nuclei"
|
|
2486
2265
|
]);
|
|
2487
|
-
function launchAllowlistedScanner(bin,
|
|
2266
|
+
function launchAllowlistedScanner(bin, argv, env) {
|
|
2488
2267
|
const spawnOpts = {
|
|
2489
2268
|
env,
|
|
2490
2269
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -2493,13 +2272,13 @@ function launchAllowlistedScanner(bin, argv2, env) {
|
|
|
2493
2272
|
};
|
|
2494
2273
|
switch (bin) {
|
|
2495
2274
|
case "sqlmap":
|
|
2496
|
-
return spawn("sqlmap",
|
|
2275
|
+
return spawn("sqlmap", argv, spawnOpts);
|
|
2497
2276
|
case "nmap":
|
|
2498
|
-
return spawn("nmap",
|
|
2277
|
+
return spawn("nmap", argv, spawnOpts);
|
|
2499
2278
|
case "ffuf":
|
|
2500
|
-
return spawn("ffuf",
|
|
2279
|
+
return spawn("ffuf", argv, spawnOpts);
|
|
2501
2280
|
case "nuclei":
|
|
2502
|
-
return spawn("nuclei",
|
|
2281
|
+
return spawn("nuclei", argv, spawnOpts);
|
|
2503
2282
|
default:
|
|
2504
2283
|
return null;
|
|
2505
2284
|
}
|
|
@@ -2574,7 +2353,7 @@ function superviseChild(child, effectiveTimeoutMs, startedAt) {
|
|
|
2574
2353
|
});
|
|
2575
2354
|
});
|
|
2576
2355
|
}
|
|
2577
|
-
async function runScannerProcess(bin,
|
|
2356
|
+
async function runScannerProcess(bin, argv, opts) {
|
|
2578
2357
|
const startedAt = Date.now();
|
|
2579
2358
|
const effectiveTimeout = Math.min(Math.max(1, opts.timeoutMs), Math.max(1, opts.ceilingMs));
|
|
2580
2359
|
if (!ALLOWED_SCANNER_BINARIES.has(bin)) {
|
|
@@ -2586,7 +2365,7 @@ async function runScannerProcess(bin, argv2, opts) {
|
|
|
2586
2365
|
}
|
|
2587
2366
|
let child;
|
|
2588
2367
|
try {
|
|
2589
|
-
child = launchAllowlistedScanner(bin,
|
|
2368
|
+
child = launchAllowlistedScanner(bin, argv, opts.env);
|
|
2590
2369
|
} catch (err) {
|
|
2591
2370
|
return {
|
|
2592
2371
|
kind: "error",
|
|
@@ -2613,68 +2392,68 @@ function sanitizeToken(value, allowed) {
|
|
|
2613
2392
|
return allowed.test(value) ? value : null;
|
|
2614
2393
|
}
|
|
2615
2394
|
function buildSqlmapArgv(opts) {
|
|
2616
|
-
const
|
|
2395
|
+
const argv = ["-u", opts.url, "--batch", "--disable-coloring"];
|
|
2617
2396
|
if (typeof opts.data === "string" && opts.data.length > 0) {
|
|
2618
|
-
|
|
2397
|
+
argv.push("--data", opts.data);
|
|
2619
2398
|
}
|
|
2620
|
-
|
|
2621
|
-
|
|
2399
|
+
argv.push("--level", String(clampInt(opts.level, 1, 5, 1)));
|
|
2400
|
+
argv.push("--risk", String(clampInt(opts.risk, 1, 3, 1)));
|
|
2622
2401
|
if (opts.technique) {
|
|
2623
2402
|
const t = sanitizeToken(opts.technique.toUpperCase(), /^[BEUSTQ]{1,6}$/);
|
|
2624
2403
|
if (t)
|
|
2625
|
-
|
|
2404
|
+
argv.push("--technique", t);
|
|
2626
2405
|
}
|
|
2627
2406
|
if (opts.dbms) {
|
|
2628
2407
|
const d = sanitizeToken(opts.dbms, /^[A-Za-z0-9 _.-]{1,32}$/);
|
|
2629
2408
|
if (d)
|
|
2630
|
-
|
|
2409
|
+
argv.push("--dbms", d);
|
|
2631
2410
|
}
|
|
2632
|
-
|
|
2411
|
+
argv.push("--threads", String(clampInt(opts.threads, 1, 10, 1)));
|
|
2633
2412
|
if (opts.enumerateDbs)
|
|
2634
|
-
|
|
2413
|
+
argv.push("--dbs");
|
|
2635
2414
|
if (opts.dump)
|
|
2636
|
-
|
|
2637
|
-
return
|
|
2415
|
+
argv.push("--dump");
|
|
2416
|
+
return argv;
|
|
2638
2417
|
}
|
|
2639
2418
|
function buildNmapArgv(opts) {
|
|
2640
|
-
const
|
|
2419
|
+
const argv = [];
|
|
2641
2420
|
if (opts.skipPing !== false)
|
|
2642
|
-
|
|
2421
|
+
argv.push("-Pn");
|
|
2643
2422
|
if (opts.serviceDetection)
|
|
2644
|
-
|
|
2423
|
+
argv.push("-sV");
|
|
2645
2424
|
if (opts.ports) {
|
|
2646
2425
|
const p = sanitizeToken(opts.ports, /^[0-9,\-]{1,64}$/);
|
|
2647
2426
|
if (p)
|
|
2648
|
-
|
|
2427
|
+
argv.push("-p", p);
|
|
2649
2428
|
} else if (opts.topPorts) {
|
|
2650
|
-
|
|
2429
|
+
argv.push("--top-ports", String(clampInt(opts.topPorts, 1, 65535, 100)));
|
|
2651
2430
|
}
|
|
2652
|
-
|
|
2653
|
-
return
|
|
2431
|
+
argv.push(opts.target);
|
|
2432
|
+
return argv;
|
|
2654
2433
|
}
|
|
2655
2434
|
function buildFfufArgv(opts) {
|
|
2656
|
-
const
|
|
2435
|
+
const argv = ["-u", opts.url, "-w", opts.wordlist, "-of", "json", "-o", "/dev/stdout", "-s"];
|
|
2657
2436
|
if (opts.matchStatus) {
|
|
2658
2437
|
const m = sanitizeToken(opts.matchStatus, /^[0-9,\-]{1,64}$/);
|
|
2659
2438
|
if (m)
|
|
2660
|
-
|
|
2439
|
+
argv.push("-mc", m);
|
|
2661
2440
|
}
|
|
2662
|
-
|
|
2663
|
-
return
|
|
2441
|
+
argv.push("-t", String(clampInt(opts.threads, 1, 50, 10)));
|
|
2442
|
+
return argv;
|
|
2664
2443
|
}
|
|
2665
2444
|
function buildNucleiArgv(opts) {
|
|
2666
|
-
const
|
|
2445
|
+
const argv = ["-u", opts.target, "-jsonl", "-silent"];
|
|
2667
2446
|
if (opts.severity) {
|
|
2668
2447
|
const s = sanitizeToken(opts.severity.toLowerCase(), /^[a-z,]{1,40}$/);
|
|
2669
2448
|
if (s)
|
|
2670
|
-
|
|
2449
|
+
argv.push("-severity", s);
|
|
2671
2450
|
}
|
|
2672
2451
|
if (opts.tags) {
|
|
2673
2452
|
const t = sanitizeToken(opts.tags.toLowerCase(), /^[a-z0-9,_-]{1,60}$/);
|
|
2674
2453
|
if (t)
|
|
2675
|
-
|
|
2454
|
+
argv.push("-tags", t);
|
|
2676
2455
|
}
|
|
2677
|
-
return
|
|
2456
|
+
return argv;
|
|
2678
2457
|
}
|
|
2679
2458
|
function tail(text, n = 1200) {
|
|
2680
2459
|
return text.length > n ? text.slice(text.length - n) : text;
|
|
@@ -3155,6 +2934,30 @@ function parseRepositoryAcquisition(command) {
|
|
|
3155
2934
|
return null;
|
|
3156
2935
|
}
|
|
3157
2936
|
}
|
|
2937
|
+
function normalizeRepositoryIdentity(value) {
|
|
2938
|
+
try {
|
|
2939
|
+
const url = new URL(value.trim());
|
|
2940
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash || url.port && url.port !== "443" || url.pathname === "/")
|
|
2941
|
+
return null;
|
|
2942
|
+
url.hostname = url.hostname.replace(/\.+$/, "").toLowerCase();
|
|
2943
|
+
let pathname = url.pathname;
|
|
2944
|
+
if (pathname.endsWith("/"))
|
|
2945
|
+
pathname = pathname.slice(0, -1);
|
|
2946
|
+
if (pathname.endsWith(".git"))
|
|
2947
|
+
pathname = pathname.slice(0, -4);
|
|
2948
|
+
if (!pathname || pathname === "/")
|
|
2949
|
+
return null;
|
|
2950
|
+
url.pathname = pathname;
|
|
2951
|
+
return url.href;
|
|
2952
|
+
} catch {
|
|
2953
|
+
return null;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
function repositoryIdentityMatchesTarget(target, cloneUrl) {
|
|
2957
|
+
const targetIdentity = normalizeRepositoryIdentity(target);
|
|
2958
|
+
const cloneIdentity = normalizeRepositoryIdentity(cloneUrl);
|
|
2959
|
+
return targetIdentity !== null && targetIdentity === cloneIdentity;
|
|
2960
|
+
}
|
|
3158
2961
|
function repositoryAcquisitionAllowed(plan, scope) {
|
|
3159
2962
|
if (!scope)
|
|
3160
2963
|
return true;
|
|
@@ -3242,7 +3045,7 @@ async function runRepositoryAcquisition(plan, command, timeoutMs, ceilingMs, sco
|
|
|
3242
3045
|
const address = proxy.address();
|
|
3243
3046
|
if (!address || typeof address === "string")
|
|
3244
3047
|
throw new Error("Cannot open source acquisition tunnel");
|
|
3245
|
-
const
|
|
3048
|
+
const argv = [
|
|
3246
3049
|
"-C",
|
|
3247
3050
|
plan.cwd,
|
|
3248
3051
|
"-c",
|
|
@@ -3265,7 +3068,7 @@ async function runRepositoryAcquisition(plan, command, timeoutMs, ceilingMs, sco
|
|
|
3265
3068
|
...plan.cloneArgs
|
|
3266
3069
|
];
|
|
3267
3070
|
const startedAt = Date.now();
|
|
3268
|
-
const child = spawn2("git",
|
|
3071
|
+
const child = spawn2("git", argv, {
|
|
3269
3072
|
shell: false,
|
|
3270
3073
|
detached: true,
|
|
3271
3074
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -3308,7 +3111,7 @@ async function runRepositoryAcquisition(plan, command, timeoutMs, ceilingMs, sco
|
|
|
3308
3111
|
|
|
3309
3112
|
// packages/core/dist/runtime/smolvm.js
|
|
3310
3113
|
import { execFile as execFile2, spawn as spawn4 } from "node:child_process";
|
|
3311
|
-
import { createHash as
|
|
3114
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
3312
3115
|
import { constants as constants3, createWriteStream as createWriteStream2 } from "node:fs";
|
|
3313
3116
|
import { mkdir as mkdir3, mkdtemp as mkdtemp3, open as open2, readdir, readFile as readFile2, readlink, realpath as realpath2, rm as rm4 } from "node:fs/promises";
|
|
3314
3117
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
@@ -3372,8 +3175,8 @@ async function runSmolvmDarwinProgram(options, network, ownership) {
|
|
|
3372
3175
|
throw new Error("SmolVM image requires a nonempty regular archive no larger than 8 GiB");
|
|
3373
3176
|
const archive = join7(root, "image.tar");
|
|
3374
3177
|
await copyFile2(options.imageArchive, archive, constants2.COPYFILE_EXCL | constants2.COPYFILE_FICLONE);
|
|
3375
|
-
const
|
|
3376
|
-
if (options.imageDigest &&
|
|
3178
|
+
const digest = await smolvmArchiveDigest(archive, lifetime.signal);
|
|
3179
|
+
if (options.imageDigest && digest !== options.imageDigest)
|
|
3377
3180
|
throw new Error("SmolVM archive identity mismatch");
|
|
3378
3181
|
const args = [
|
|
3379
3182
|
"machine",
|
|
@@ -3559,7 +3362,7 @@ async function hashArchive(path, destination, signal) {
|
|
|
3559
3362
|
if (!stat2.isFile() || stat2.size === 0 || stat2.size > MAX_IMAGE_BYTES) {
|
|
3560
3363
|
throw new Error("smolvm image must be a nonempty regular archive no larger than 8 GiB");
|
|
3561
3364
|
}
|
|
3562
|
-
const hash3 =
|
|
3365
|
+
const hash3 = createHash3("sha256");
|
|
3563
3366
|
const source = file.createReadStream({ autoClose: false, signal });
|
|
3564
3367
|
if (destination) {
|
|
3565
3368
|
source.on("data", (chunk) => hash3.update(chunk));
|
|
@@ -3618,14 +3421,14 @@ function launcherEnvironment(root) {
|
|
|
3618
3421
|
return env;
|
|
3619
3422
|
}
|
|
3620
3423
|
function version(binary, cwd, env, signal) {
|
|
3621
|
-
return new Promise((
|
|
3424
|
+
return new Promise((resolve3, reject) => {
|
|
3622
3425
|
execFile2(binary, ["--version"], { cwd, env, signal, timeout: 5e3, killSignal: "SIGKILL", maxBuffer: 4096 }, (error, stdout, stderr) => {
|
|
3623
3426
|
if (error)
|
|
3624
3427
|
reject(new Error(`smolvm runtime unavailable: ${stderr.trim() || error.message}`));
|
|
3625
3428
|
else if (stdout.trim() !== "smolvm 1.14.6")
|
|
3626
3429
|
reject(new Error("smolvm 1.14.6 is required; other versions need lifecycle qualification"));
|
|
3627
3430
|
else
|
|
3628
|
-
|
|
3431
|
+
resolve3();
|
|
3629
3432
|
});
|
|
3630
3433
|
});
|
|
3631
3434
|
}
|
|
@@ -3691,8 +3494,8 @@ async function runSmolvm(options) {
|
|
|
3691
3494
|
const binary = options.binary ?? "smolvm";
|
|
3692
3495
|
await version(binary, root, env, controller.signal);
|
|
3693
3496
|
const archive = join8(root, "image.tar");
|
|
3694
|
-
const
|
|
3695
|
-
if (options.imageDigest &&
|
|
3497
|
+
const digest = await hashArchive(options.imageArchive, archive, controller.signal);
|
|
3498
|
+
if (options.imageDigest && digest !== options.imageDigest)
|
|
3696
3499
|
throw new Error("smolvm archive identity mismatch");
|
|
3697
3500
|
const args = [
|
|
3698
3501
|
"--pdeathsig",
|
|
@@ -3726,7 +3529,7 @@ async function runSmolvm(options) {
|
|
|
3726
3529
|
}
|
|
3727
3530
|
args.push("--", ...options.command);
|
|
3728
3531
|
controller.signal.throwIfAborted();
|
|
3729
|
-
await new Promise((
|
|
3532
|
+
await new Promise((resolve3) => {
|
|
3730
3533
|
const child = spawn4("setpriv", args, { cwd: root, env, stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
3731
3534
|
const stdout = [], stderr = [];
|
|
3732
3535
|
let stdoutBytes = 0, stderrBytes = 0;
|
|
@@ -3799,7 +3602,7 @@ async function runSmolvm(options) {
|
|
|
3799
3602
|
result.stderr = Buffer.concat(stderr, stderrBytes).toString("utf8");
|
|
3800
3603
|
if (!banner || signal)
|
|
3801
3604
|
result.error ??= `smolvm launcher failed${signal ? ` (${signal})` : " before guest execution"}`;
|
|
3802
|
-
|
|
3605
|
+
resolve3();
|
|
3803
3606
|
});
|
|
3804
3607
|
controller.signal.addEventListener("abort", abort, { once: true });
|
|
3805
3608
|
if (controller.signal.aborted)
|
|
@@ -3841,6 +3644,7 @@ async function runSmolvm(options) {
|
|
|
3841
3644
|
// packages/core/dist/runtime/smolvm-broker.js
|
|
3842
3645
|
var ADMISSION = "/run/0-workbench/admission.json";
|
|
3843
3646
|
var GUEST_ROOT = "/run/0-workbench/broker";
|
|
3647
|
+
var WORKBENCH_BROKER_WORKSPACE = "/tmp/0-workspace";
|
|
3844
3648
|
var DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
3845
3649
|
var REFERENCE = /^(?:sha256:[a-f0-9]{64}|[a-zA-Z0-9][a-zA-Z0-9_.:/-]*@sha256:[a-f0-9]{64})$/;
|
|
3846
3650
|
var POLL_MS = 15;
|
|
@@ -3860,7 +3664,7 @@ var DEFAULT_WORKBENCH_BROKER_LIMITS = Object.freeze({
|
|
|
3860
3664
|
maxInputBytes: 1024 * 1024
|
|
3861
3665
|
});
|
|
3862
3666
|
function hash2(data) {
|
|
3863
|
-
return `sha256:${
|
|
3667
|
+
return `sha256:${createHash4("sha256").update(data).digest("hex")}`;
|
|
3864
3668
|
}
|
|
3865
3669
|
function problem(error) {
|
|
3866
3670
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -3905,8 +3709,8 @@ function admission() {
|
|
|
3905
3709
|
if (marker.schemaVersion !== 1 || marker.profile !== "smolvm-workbench" || !broker || broker.protocol !== 1 || broker.root !== GUEST_ROOT || typeof marker.runtimeId !== "string" || !/^[a-f0-9]{64}$/.test(marker.runtimeId) || broker.runtimeId !== marker.runtimeId || !DIGEST.test(broker.imageDigest) || typeof broker.allowHttp !== "boolean" || !broker.approvedImages || typeof broker.approvedImages !== "object")
|
|
3906
3710
|
throw new Error("Workbench broker admission identity mismatch");
|
|
3907
3711
|
broker.limits = Object.freeze(checkedLimits(broker.limits));
|
|
3908
|
-
for (const [reference,
|
|
3909
|
-
if (!REFERENCE.test(reference) || !DIGEST.test(
|
|
3712
|
+
for (const [reference, digest] of Object.entries(broker.approvedImages))
|
|
3713
|
+
if (!REFERENCE.test(reference) || !DIGEST.test(digest))
|
|
3910
3714
|
throw new Error("Invalid admitted image catalog");
|
|
3911
3715
|
return immutableAdmission = broker;
|
|
3912
3716
|
} finally {
|
|
@@ -3935,10 +3739,10 @@ function resolveWorkbenchBrokerImage(reference) {
|
|
|
3935
3739
|
const grant = admission();
|
|
3936
3740
|
if (reference === void 0 || reference === grant.imageDigest)
|
|
3937
3741
|
return grant.imageDigest;
|
|
3938
|
-
const
|
|
3939
|
-
if (!
|
|
3742
|
+
const digest = Object.hasOwn(grant.approvedImages, reference) ? grant.approvedImages[reference] : void 0;
|
|
3743
|
+
if (!digest)
|
|
3940
3744
|
throw new Error(`Sandbox image is not explicitly approved: ${reference}`);
|
|
3941
|
-
return
|
|
3745
|
+
return digest;
|
|
3942
3746
|
}
|
|
3943
3747
|
async function regularBytes(path, maximum) {
|
|
3944
3748
|
const file = await open3(path, constants4.O_RDONLY | constants4.O_NOFOLLOW | constants4.O_NONBLOCK);
|
|
@@ -3987,8 +3791,8 @@ async function snapshot(root, limits) {
|
|
|
3987
3791
|
if ((entries += names.length) > limits.maxFiles * 4)
|
|
3988
3792
|
throw new Error("Broker workspace has too many filesystem entries");
|
|
3989
3793
|
for (const name of names.sort()) {
|
|
3990
|
-
const path = join9(directory, name),
|
|
3991
|
-
if (!safePath(
|
|
3794
|
+
const path = join9(directory, name), relativePath = relative(root, path).split(sep).join("/");
|
|
3795
|
+
if (!safePath(relativePath))
|
|
3992
3796
|
throw new Error("Invalid broker workspace path");
|
|
3993
3797
|
const info = await lstat3(path);
|
|
3994
3798
|
if (info.isDirectory()) {
|
|
@@ -4001,7 +3805,7 @@ async function snapshot(root, limits) {
|
|
|
4001
3805
|
throw new Error("Broker workspace exceeds its file/byte limits");
|
|
4002
3806
|
const content = await regularBytes(path, limits.maxWorkspaceBytes - bytes);
|
|
4003
3807
|
bytes += content.length;
|
|
4004
|
-
files.push({ path:
|
|
3808
|
+
files.push({ path: relativePath, digest: hash2(content), data: content.toString("base64"), mode: info.mode & 73 ? 448 : 384 });
|
|
4005
3809
|
}
|
|
4006
3810
|
}
|
|
4007
3811
|
await walk(root);
|
|
@@ -4083,25 +3887,25 @@ async function httpCommand(request, signal) {
|
|
|
4083
3887
|
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
4084
3888
|
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || !host || /^(?:localhost|.*\.localhost)$/i.test(host))
|
|
4085
3889
|
throw new Error("HTTP broker requires a public HTTP(S) URL without userinfo");
|
|
4086
|
-
const
|
|
3890
|
+
const argv = request.command;
|
|
4087
3891
|
const prefix = ["curl", "--disable", "--globoff", "--silent", "--show-error", "--request"];
|
|
4088
|
-
if (!prefix.every((arg, index2) =>
|
|
3892
|
+
if (!prefix.every((arg, index2) => argv[index2] === arg) || !/^[A-Za-z]{1,32}$/.test(argv[6] ?? ""))
|
|
4089
3893
|
throw new Error("HTTP broker permits only declarative curl requests");
|
|
4090
|
-
const fixed = ["--max-time",
|
|
4091
|
-
if (!fixed.every((arg, index2) =>
|
|
3894
|
+
const fixed = ["--max-time", argv[8], "--proto", "=http,https", "--proto-redir", "=http,https", "--max-redirs", "0", "--noproxy", "*", "--output", "-", "--write-out", "%{stderr}\n__ZERO_HTTP_STATUS__:%{http_code}\n"];
|
|
3895
|
+
if (!fixed.every((arg, index2) => argv[index2 + 7] === arg) || !/^[0-9]+(?:\.[0-9]+)?$/.test(argv[8] ?? "") || Number(argv[8]) <= 0 || Number(argv[8]) > request.timeoutMs / 1e3)
|
|
4092
3896
|
throw new Error("HTTP broker curl policy mismatch");
|
|
4093
3897
|
let index = 21, headerBytes = 0, headers = 0;
|
|
4094
|
-
while (
|
|
4095
|
-
const header =
|
|
3898
|
+
while (argv[index] === "--header") {
|
|
3899
|
+
const header = argv[index + 1];
|
|
4096
3900
|
if (typeof header !== "string" || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+:[^\r\n\0]*$/.test(header) || ++headers > 64 || (headerBytes += Buffer.byteLength(header)) > 65536)
|
|
4097
3901
|
throw new Error("Invalid bounded HTTP header");
|
|
4098
3902
|
index += 2;
|
|
4099
3903
|
}
|
|
4100
|
-
if (
|
|
3904
|
+
if (argv[index] === "--data-binary" && argv[index + 1] === "@-")
|
|
4101
3905
|
index += 2;
|
|
4102
|
-
if (
|
|
3906
|
+
if (argv[index] !== "--" || argv[index + 1] !== request.httpTarget || argv.length !== index + 2)
|
|
4103
3907
|
throw new Error("HTTP broker rejects additional curl flags, files, redirects or destinations");
|
|
4104
|
-
const addresses = isIP2(host) ? [{ address: host, family: isIP2(host) }] : await new Promise((
|
|
3908
|
+
const addresses = isIP2(host) ? [{ address: host, family: isIP2(host) }] : await new Promise((resolve3, reject) => {
|
|
4105
3909
|
let settled = false;
|
|
4106
3910
|
const finish = (error, result) => {
|
|
4107
3911
|
if (settled)
|
|
@@ -4112,7 +3916,7 @@ async function httpCommand(request, signal) {
|
|
|
4112
3916
|
if (error)
|
|
4113
3917
|
reject(error);
|
|
4114
3918
|
else
|
|
4115
|
-
|
|
3919
|
+
resolve3(result);
|
|
4116
3920
|
};
|
|
4117
3921
|
const timer = setTimeout(() => finish(new Error("HTTP broker DNS resolution exceeded its deadline")), Math.min(5e3, request.timeoutMs));
|
|
4118
3922
|
const abort = () => finish(new Error("HTTP broker DNS resolution cancelled"));
|
|
@@ -4125,18 +3929,21 @@ async function httpCommand(request, signal) {
|
|
|
4125
3929
|
if (!addresses.length || addresses.some(({ address: address2 }) => !isPublicRepositoryAddress(address2)))
|
|
4126
3930
|
throw new Error("HTTP broker refuses private, loopback, reserved or mixed DNS destinations");
|
|
4127
3931
|
if (isIP2(host))
|
|
4128
|
-
return [...
|
|
3932
|
+
return [...argv];
|
|
4129
3933
|
const address = addresses[0];
|
|
4130
3934
|
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
4131
3935
|
const pinned = address.family === 6 ? `[${address.address}]` : address.address;
|
|
4132
|
-
return [...
|
|
3936
|
+
return [...argv.slice(0, index), "--resolve", `${host}:${port}:${pinned}`, ...argv.slice(index)];
|
|
4133
3937
|
}
|
|
4134
3938
|
var SIBLING_TRANSPORT = String.raw`
|
|
4135
3939
|
const fs = require('node:fs'), p = require('node:path'), crypto = require('node:crypto'), cp = require('node:child_process');
|
|
4136
3940
|
const cfg = JSON.parse(fs.readFileSync('/control/program.json', 'utf8'));
|
|
4137
3941
|
const send = value => process.stdout.write(JSON.stringify(value) + '\n');
|
|
4138
3942
|
const digest = b => 'sha256:' + crypto.createHash('sha256').update(b).digest('hex');
|
|
4139
|
-
const workspace =
|
|
3943
|
+
const workspace = ${JSON.stringify(WORKBENCH_BROKER_WORKSPACE)};
|
|
3944
|
+
// A fresh guest must not contain this path, including a dangling symlink.
|
|
3945
|
+
fs.mkdirSync(workspace,{mode:0o700});
|
|
3946
|
+
const workspaceInfo = fs.lstatSync(workspace);
|
|
4140
3947
|
for (const file of cfg.files) {
|
|
4141
3948
|
const destination = p.join(workspace,file.path);
|
|
4142
3949
|
fs.mkdirSync(p.dirname(destination),{recursive:true,mode:0o700});
|
|
@@ -4155,6 +3962,8 @@ child.on('spawn',()=>send({type:'ready'}));
|
|
|
4155
3962
|
child.on('close',(code,signal)=>{
|
|
4156
3963
|
let bytes=0,files=0,entries=0;
|
|
4157
3964
|
try {
|
|
3965
|
+
const current = fs.lstatSync(workspace);
|
|
3966
|
+
if(!current.isDirectory()||current.dev!==workspaceInfo.dev||current.ino!==workspaceInfo.ino)throw Error('sandbox workspace was replaced');
|
|
4158
3967
|
const walk=dir=>{const names=fs.readdirSync(dir).sort();entries+=names.length;if(entries>cfg.maxFiles*4)throw Error('sandbox artifact entry limit');for(const name of names){const path=p.join(dir,name), rel=p.relative(workspace,path),st=fs.lstatSync(path);if(st.isDirectory()){walk(path);continue;}if(!st.isFile()||st.nlink!==1)throw Error('sandbox artifact is not a regular unlinked file');if(++files>cfg.maxFiles||bytes+st.size>cfg.maxWorkspaceBytes)throw Error('sandbox artifact limit');const fd=fs.openSync(path,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW|fs.constants.O_NONBLOCK);let b;try{const a=fs.fstatSync(fd);b=fs.readFileSync(fd);const z=fs.fstatSync(fd);if(!a.isFile()||a.nlink!==1||a.size!==z.size||a.mtimeMs!==z.mtimeMs||b.length!==a.size)throw Error('sandbox artifact changed');}finally{fs.closeSync(fd);}bytes+=b.length;send({type:'file',file:{path:rel,digest:digest(b),data:b.toString('base64'),mode:st.mode&0o111?448:384}});}};
|
|
4159
3968
|
walk(workspace); send({type:'done',exitCode:code,error:failed?'sandbox transport failed':signal?'sandbox process signalled '+signal:undefined});
|
|
4160
3969
|
} catch(e) { send({type:'done',exitCode:code,error:e.message}); }
|
|
@@ -4188,8 +3997,8 @@ async function startWorkbenchBroker(options) {
|
|
|
4188
3997
|
let polling;
|
|
4189
3998
|
try {
|
|
4190
3999
|
await mkdir4(guestRoot, { mode: 448 });
|
|
4191
|
-
async function approve(archive,
|
|
4192
|
-
if (!DIGEST.test(
|
|
4000
|
+
async function approve(archive, digest, index) {
|
|
4001
|
+
if (!DIGEST.test(digest) || !isAbsolute5(archive) || resolve2(archive) !== await realpath3(archive))
|
|
4193
4002
|
throw new Error("Invalid trusted broker image archive");
|
|
4194
4003
|
const file = join9(privateRoot, `image-${index}.tar`);
|
|
4195
4004
|
const info = await lstat3(archive);
|
|
@@ -4197,7 +4006,7 @@ async function startWorkbenchBroker(options) {
|
|
|
4197
4006
|
throw new Error("Invalid approved broker archive file");
|
|
4198
4007
|
await copyFile3(archive, file, constants4.COPYFILE_EXCL | constants4.COPYFILE_FICLONE);
|
|
4199
4008
|
await chmod2(file, 256);
|
|
4200
|
-
if (await resolveSmolvmImage(file) !==
|
|
4009
|
+
if (await resolveSmolvmImage(file) !== digest)
|
|
4201
4010
|
throw new Error("Approved broker archive copy identity mismatch");
|
|
4202
4011
|
return file;
|
|
4203
4012
|
}
|
|
@@ -4678,770 +4487,7 @@ async function runWorkbenchBrokerProgram(options, signal, channel) {
|
|
|
4678
4487
|
}
|
|
4679
4488
|
}
|
|
4680
4489
|
|
|
4681
|
-
// packages/core/dist/improvement/config.js
|
|
4682
|
-
import { existsSync as existsSync2, readFileSync as readFileSync5, realpathSync } from "node:fs";
|
|
4683
|
-
import { homedir as homedir3 } from "node:os";
|
|
4684
|
-
import { isAbsolute as isAbsolute6, resolve as resolve3, sep as sep2 } from "node:path";
|
|
4685
|
-
var relativePath = external_exports.string().min(1).max(512).refine((value) => !isAbsolute6(value) && !value.includes("\\") && !value.includes("\0") && value !== "." && value.split("/").every((part) => part !== ".." && part !== "" && part !== "."), "must be a normalized relative file or directory path (not the entire workspace)");
|
|
4686
|
-
var argv = external_exports.array(external_exports.string().max(8192).refine((value) => !value.includes("\0"))).min(1).max(128).refine((value) => value[0].length > 0, "executable must not be empty");
|
|
4687
|
-
var finitePositive = external_exports.number().finite().positive();
|
|
4688
|
-
var schema = external_exports.object({
|
|
4689
|
-
schemaVersion: external_exports.literal(1),
|
|
4690
|
-
sourceRoot: external_exports.string().min(1),
|
|
4691
|
-
storePath: external_exports.string().min(1),
|
|
4692
|
-
image: external_exports.string().min(1).max(512).regex(/^[a-zA-Z0-9][a-zA-Z0-9_./:@-]*$/),
|
|
4693
|
-
backend: external_exports.enum(["docker", "smolvm"]).optional(),
|
|
4694
|
-
imageArchive: external_exports.string().min(1).max(4096).refine((value) => !value.includes("\0")).optional(),
|
|
4695
|
-
sourcePaths: external_exports.array(relativePath).min(1).max(256),
|
|
4696
|
-
editablePaths: external_exports.array(relativePath).min(1).max(256),
|
|
4697
|
-
kind: external_exports.enum(["source", "skill", "router", "lens"]).default("source"),
|
|
4698
|
-
command: argv,
|
|
4699
|
-
buildCommand: argv.optional(),
|
|
4700
|
-
cases: external_exports.array(external_exports.object({
|
|
4701
|
-
id: external_exports.string().min(1).max(128),
|
|
4702
|
-
lane: external_exports.enum(["development", "held-out", "negative-control"]),
|
|
4703
|
-
input: external_exports.unknown(),
|
|
4704
|
-
expected: external_exports.unknown()
|
|
4705
|
-
}).strict()).min(3).max(1e3),
|
|
4706
|
-
repeats: external_exports.number().int().min(2).max(20).default(3),
|
|
4707
|
-
maxIterations: external_exports.number().int().min(1).max(100).default(3),
|
|
4708
|
-
maxModelTurns: external_exports.number().int().min(1).max(64).default(12),
|
|
4709
|
-
maxModelCostUsd: finitePositive.max(1e3).default(5),
|
|
4710
|
-
maxEvaluationCostUsd: finitePositive.max(1e3).default(5),
|
|
4711
|
-
computeUsdPerSecond: finitePositive.max(10),
|
|
4712
|
-
timeoutMs: external_exports.number().int().min(100).max(6e5).default(6e4),
|
|
4713
|
-
memoryMb: external_exports.number().int().min(32).max(16384).default(1024),
|
|
4714
|
-
cpus: finitePositive.max(16).default(1),
|
|
4715
|
-
maxOutputBytes: external_exports.number().int().min(256).max(16 * 1024 * 1024).default(64 * 1024),
|
|
4716
|
-
maxSourceBytes: external_exports.number().int().min(1024).max(512 * 1024 * 1024).default(64 * 1024 * 1024),
|
|
4717
|
-
maxChangedBytes: external_exports.number().int().min(128).max(4 * 1024 * 1024).default(256 * 1024),
|
|
4718
|
-
model: external_exports.string().min(1).optional(),
|
|
4719
|
-
objective: external_exports.string().min(1).max(16e3),
|
|
4720
|
-
allowModelSourceAccess: external_exports.boolean().default(false),
|
|
4721
|
-
autoPromote: external_exports.boolean().default(false),
|
|
4722
|
-
canaryTrials: external_exports.number().int().min(1).max(20).default(2),
|
|
4723
|
-
promotionPolicy: external_exports.object({
|
|
4724
|
-
minimumCases: external_exports.number().int().min(3).max(1e3).default(DEFAULT_IMPROVEMENT_PROMOTION_POLICY.minimumCases),
|
|
4725
|
-
minimumDevelopmentLift: finitePositive.max(1).default(DEFAULT_IMPROVEMENT_PROMOTION_POLICY.minimumDevelopmentLift),
|
|
4726
|
-
minimumHeldOutLift: finitePositive.max(1).default(DEFAULT_IMPROVEMENT_PROMOTION_POLICY.minimumHeldOutLift),
|
|
4727
|
-
maximumNegativeControlFpDelta: external_exports.literal(0).default(0),
|
|
4728
|
-
maximumCostMultiplier: finitePositive.max(10).default(DEFAULT_IMPROVEMENT_PROMOTION_POLICY.maximumCostMultiplier)
|
|
4729
|
-
}).strict().default({}),
|
|
4730
|
-
maxAlternativeParents: external_exports.number().int().min(0).max(10).optional()
|
|
4731
|
-
}).strict();
|
|
4732
|
-
function canonicalEvolutionJson(value) {
|
|
4733
|
-
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
4734
|
-
return JSON.stringify(value);
|
|
4735
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
4736
|
-
return JSON.stringify(value);
|
|
4737
|
-
if (Array.isArray(value))
|
|
4738
|
-
return `[${Array.from(value, canonicalEvolutionJson).join(",")}]`;
|
|
4739
|
-
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
|
|
4740
|
-
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalEvolutionJson(value[key])}`).join(",")}}`;
|
|
4741
|
-
}
|
|
4742
|
-
throw new Error("evolution cases must contain finite JSON values only");
|
|
4743
|
-
}
|
|
4744
|
-
function parseEvolutionConfig(raw, baseDir = process.cwd()) {
|
|
4745
|
-
const parsed = schema.parse(raw);
|
|
4746
|
-
const admitted = isAdmittedSmolvmWorkbench();
|
|
4747
|
-
if (admitted) {
|
|
4748
|
-
if (parsed.imageArchive)
|
|
4749
|
-
throw new Error("Admitted workbench images must use the host-approved catalog, not guest archive paths");
|
|
4750
|
-
parsed.backend = "smolvm";
|
|
4751
|
-
}
|
|
4752
|
-
if (parsed.backend === "smolvm") {
|
|
4753
|
-
if (!parsed.imageArchive && !admitted)
|
|
4754
|
-
throw new Error("smolvm requires a local imageArchive");
|
|
4755
|
-
if (!Number.isInteger(parsed.cpus))
|
|
4756
|
-
throw new Error("smolvm cpus must be an integer");
|
|
4757
|
-
if (parsed.imageArchive)
|
|
4758
|
-
parsed.imageArchive = resolve3(baseDir, parsed.imageArchive);
|
|
4759
|
-
} else if (parsed.imageArchive !== void 0) {
|
|
4760
|
-
throw new Error("imageArchive is only valid with backend smolvm");
|
|
4761
|
-
}
|
|
4762
|
-
if (parsed.cases.length * parsed.repeats * 4 * parsed.maxOutputBytes > 64 * 1024 * 1024) {
|
|
4763
|
-
throw new Error("evaluation stream retention exceeds 64 MiB; reduce cases, repeats or maxOutputBytes");
|
|
4764
|
-
}
|
|
4765
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
4766
|
-
const inputs = /* @__PURE__ */ new Map();
|
|
4767
|
-
for (const entry of parsed.cases) {
|
|
4768
|
-
if (seenIds.has(entry.id))
|
|
4769
|
-
throw new Error(`duplicate evolution case id: ${entry.id}`);
|
|
4770
|
-
seenIds.add(entry.id);
|
|
4771
|
-
const input = canonicalEvolutionJson(entry.input);
|
|
4772
|
-
canonicalEvolutionJson(entry.expected);
|
|
4773
|
-
if (inputs.has(input))
|
|
4774
|
-
throw new Error(`duplicate evaluation input: ${entry.id}; cases and lanes must be independent`);
|
|
4775
|
-
inputs.set(input, entry.lane);
|
|
4776
|
-
}
|
|
4777
|
-
for (const lane of ["development", "held-out", "negative-control"]) {
|
|
4778
|
-
if (parsed.cases.filter((entry) => entry.lane === lane).length < parsed.promotionPolicy.minimumCases) {
|
|
4779
|
-
throw new Error(`${lane} requires at least ${parsed.promotionPolicy.minimumCases} distinct cases`);
|
|
4780
|
-
}
|
|
4781
|
-
}
|
|
4782
|
-
for (const edit of parsed.editablePaths) {
|
|
4783
|
-
if (!parsed.sourcePaths.some((source) => edit === source || edit.startsWith(`${source}/`))) {
|
|
4784
|
-
throw new Error(`editable path ${edit} is not inside a selected source path`);
|
|
4785
|
-
}
|
|
4786
|
-
}
|
|
4787
|
-
const sourceRoot = resolve3(baseDir, parsed.sourceRoot === "~" ? homedir3() : parsed.sourceRoot.startsWith("~/") ? resolve3(homedir3(), parsed.sourceRoot.slice(2)) : parsed.sourceRoot);
|
|
4788
|
-
const storePath = resolve3(baseDir, parsed.storePath === "~" ? homedir3() : parsed.storePath.startsWith("~/") ? resolve3(homedir3(), parsed.storePath.slice(2)) : parsed.storePath);
|
|
4789
|
-
if (sourceRoot === storePath)
|
|
4790
|
-
throw new Error("evolution store cannot be the source workspace");
|
|
4791
|
-
for (const selected of parsed.sourcePaths) {
|
|
4792
|
-
const source = resolve3(sourceRoot, selected);
|
|
4793
|
-
if (storePath === source || storePath.startsWith(`${source}/`) || source.startsWith(`${storePath}/`)) {
|
|
4794
|
-
throw new Error("evolution store and selected source paths must not overlap");
|
|
4795
|
-
}
|
|
4796
|
-
}
|
|
4797
|
-
return JSON.parse(JSON.stringify({ ...parsed, sourceRoot, storePath }));
|
|
4798
|
-
}
|
|
4799
|
-
function loadEvolutionConfigFile(path) {
|
|
4800
|
-
const absPath = resolve3(path);
|
|
4801
|
-
if (!existsSync2(absPath))
|
|
4802
|
-
throw new Error(`config file not found: ${absPath}`);
|
|
4803
|
-
const configFile = realpathSync(absPath);
|
|
4804
|
-
const raw = JSON.parse(readFileSync5(absPath, "utf8"));
|
|
4805
|
-
const config = parseEvolutionConfig(raw, resolve3(path, ".."));
|
|
4806
|
-
for (const source of config.sourcePaths) {
|
|
4807
|
-
const sourcePath = resolve3(config.sourceRoot, source);
|
|
4808
|
-
const selected = existsSync2(sourcePath) ? realpathSync(sourcePath) : sourcePath;
|
|
4809
|
-
if (configFile === selected || configFile.startsWith(`${selected}${sep2}`)) {
|
|
4810
|
-
throw new Error("evolution config contains private answers and must be outside selected source paths");
|
|
4811
|
-
}
|
|
4812
|
-
}
|
|
4813
|
-
return config;
|
|
4814
|
-
}
|
|
4815
|
-
|
|
4816
|
-
// packages/core/dist/verification-spec/spec.js
|
|
4817
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
4818
|
-
import { promises as fs } from "node:fs";
|
|
4819
|
-
import { tmpdir as tmpdir3 } from "node:os";
|
|
4820
|
-
import { join as join10, resolve as resolve4, isAbsolute as isAbsolute7, normalize, sep as sep3 } from "node:path";
|
|
4821
|
-
import { promisify } from "node:util";
|
|
4822
|
-
var execFileAsync = promisify(execFile3);
|
|
4823
|
-
var MAX_GIT_DIFF_BYTES = 1e6;
|
|
4824
|
-
var GIT_CHECK_TIMEOUT_MS = 5e3;
|
|
4825
|
-
var FULL_GIT_OBJECT_ID_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i;
|
|
4826
|
-
var MAX_PATTERN_LENGTH = 512;
|
|
4827
|
-
var MAX_FILE_BYTES = 1e6;
|
|
4828
|
-
function resolveRepoPath(repoRoot, file) {
|
|
4829
|
-
if (typeof file !== "string" || file.length === 0)
|
|
4830
|
-
return null;
|
|
4831
|
-
if (isAbsolute7(file))
|
|
4832
|
-
return null;
|
|
4833
|
-
const root = resolve4(repoRoot);
|
|
4834
|
-
const candidate = resolve4(root, file);
|
|
4835
|
-
const normalized = normalize(candidate);
|
|
4836
|
-
if (normalized !== root && !normalized.startsWith(root + sep3)) {
|
|
4837
|
-
return null;
|
|
4838
|
-
}
|
|
4839
|
-
return normalized;
|
|
4840
|
-
}
|
|
4841
|
-
async function checkRepoBoundary(repoRoot, absPath) {
|
|
4842
|
-
try {
|
|
4843
|
-
await fs.lstat(absPath);
|
|
4844
|
-
} catch {
|
|
4845
|
-
return "missing";
|
|
4846
|
-
}
|
|
4847
|
-
try {
|
|
4848
|
-
const [realRoot, realTarget] = await Promise.all([
|
|
4849
|
-
fs.realpath(repoRoot),
|
|
4850
|
-
fs.realpath(absPath)
|
|
4851
|
-
]);
|
|
4852
|
-
if (realTarget === realRoot || realTarget.startsWith(realRoot + sep3)) {
|
|
4853
|
-
return "inside";
|
|
4854
|
-
}
|
|
4855
|
-
return "outside";
|
|
4856
|
-
} catch {
|
|
4857
|
-
return "outside";
|
|
4858
|
-
}
|
|
4859
|
-
}
|
|
4860
|
-
function safeRegex(pattern, flags) {
|
|
4861
|
-
if (typeof pattern !== "string")
|
|
4862
|
-
return null;
|
|
4863
|
-
if (pattern.length > MAX_PATTERN_LENGTH)
|
|
4864
|
-
return null;
|
|
4865
|
-
try {
|
|
4866
|
-
return new RegExp(pattern, flags);
|
|
4867
|
-
} catch {
|
|
4868
|
-
return null;
|
|
4869
|
-
}
|
|
4870
|
-
}
|
|
4871
|
-
async function readFileSafe(absPath) {
|
|
4872
|
-
try {
|
|
4873
|
-
const stat2 = await fs.stat(absPath);
|
|
4874
|
-
if (!stat2.isFile())
|
|
4875
|
-
return null;
|
|
4876
|
-
if (stat2.size > MAX_FILE_BYTES)
|
|
4877
|
-
return null;
|
|
4878
|
-
return await fs.readFile(absPath, "utf8");
|
|
4879
|
-
} catch {
|
|
4880
|
-
return null;
|
|
4881
|
-
}
|
|
4882
|
-
}
|
|
4883
|
-
async function evaluateGitDiffApplies(predicate, repoRoot) {
|
|
4884
|
-
if (predicate.diff.trim().length === 0) {
|
|
4885
|
-
return { predicate, passed: false, reason: "git diff is empty" };
|
|
4886
|
-
}
|
|
4887
|
-
if (Buffer.byteLength(predicate.diff, "utf8") > MAX_GIT_DIFF_BYTES) {
|
|
4888
|
-
return {
|
|
4889
|
-
predicate,
|
|
4890
|
-
passed: false,
|
|
4891
|
-
reason: `git diff exceeds ${MAX_GIT_DIFF_BYTES} bytes`
|
|
4892
|
-
};
|
|
4893
|
-
}
|
|
4894
|
-
if (!FULL_GIT_OBJECT_ID_RE.test(predicate.baseCommit)) {
|
|
4895
|
-
return { predicate, passed: false, reason: "invalid git base commit" };
|
|
4896
|
-
}
|
|
4897
|
-
let head;
|
|
4898
|
-
try {
|
|
4899
|
-
const result = await execFileAsync("git", ["rev-parse", "--verify", "HEAD"], {
|
|
4900
|
-
cwd: repoRoot,
|
|
4901
|
-
timeout: GIT_CHECK_TIMEOUT_MS,
|
|
4902
|
-
maxBuffer: 4096
|
|
4903
|
-
});
|
|
4904
|
-
head = result.stdout.trim();
|
|
4905
|
-
} catch {
|
|
4906
|
-
return {
|
|
4907
|
-
predicate,
|
|
4908
|
-
passed: false,
|
|
4909
|
-
reason: "git repository HEAD unavailable"
|
|
4910
|
-
};
|
|
4911
|
-
}
|
|
4912
|
-
if (head.toLowerCase() !== predicate.baseCommit.toLowerCase()) {
|
|
4913
|
-
return {
|
|
4914
|
-
predicate,
|
|
4915
|
-
passed: false,
|
|
4916
|
-
reason: "git HEAD does not match diff base commit"
|
|
4917
|
-
};
|
|
4918
|
-
}
|
|
4919
|
-
let tempDir;
|
|
4920
|
-
try {
|
|
4921
|
-
tempDir = await fs.mkdtemp(join10(tmpdir3(), "0-verify-diff-"));
|
|
4922
|
-
const patchPath = join10(tempDir, "evidence.patch");
|
|
4923
|
-
await fs.writeFile(patchPath, predicate.diff, { encoding: "utf8", mode: 384 });
|
|
4924
|
-
await execFileAsync("git", ["apply", "--check", "--whitespace=nowarn", "--", patchPath], {
|
|
4925
|
-
cwd: repoRoot,
|
|
4926
|
-
timeout: GIT_CHECK_TIMEOUT_MS,
|
|
4927
|
-
maxBuffer: 65536
|
|
4928
|
-
});
|
|
4929
|
-
return {
|
|
4930
|
-
predicate,
|
|
4931
|
-
passed: true,
|
|
4932
|
-
reason: "git diff applies cleanly"
|
|
4933
|
-
};
|
|
4934
|
-
} catch {
|
|
4935
|
-
return {
|
|
4936
|
-
predicate,
|
|
4937
|
-
passed: false,
|
|
4938
|
-
reason: "git diff does not apply cleanly"
|
|
4939
|
-
};
|
|
4940
|
-
} finally {
|
|
4941
|
-
if (tempDir) {
|
|
4942
|
-
try {
|
|
4943
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
4944
|
-
} catch {
|
|
4945
|
-
}
|
|
4946
|
-
}
|
|
4947
|
-
}
|
|
4948
|
-
}
|
|
4949
|
-
async function evaluateOne(predicate, repoRoot) {
|
|
4950
|
-
switch (predicate.kind) {
|
|
4951
|
-
case "file-exists": {
|
|
4952
|
-
const abs = resolveRepoPath(repoRoot, predicate.file);
|
|
4953
|
-
if (!abs) {
|
|
4954
|
-
return {
|
|
4955
|
-
predicate,
|
|
4956
|
-
passed: false,
|
|
4957
|
-
reason: `path escapes repo root or is invalid: ${predicate.file}`
|
|
4958
|
-
};
|
|
4959
|
-
}
|
|
4960
|
-
const outcome = await checkRepoBoundary(repoRoot, abs);
|
|
4961
|
-
if (outcome === "outside") {
|
|
4962
|
-
return {
|
|
4963
|
-
predicate,
|
|
4964
|
-
passed: false,
|
|
4965
|
-
reason: `path resolves outside repo root: ${predicate.file}`
|
|
4966
|
-
};
|
|
4967
|
-
}
|
|
4968
|
-
const ok = outcome === "inside";
|
|
4969
|
-
return {
|
|
4970
|
-
predicate,
|
|
4971
|
-
passed: ok,
|
|
4972
|
-
reason: ok ? "file exists" : `file not found: ${predicate.file}`
|
|
4973
|
-
};
|
|
4974
|
-
}
|
|
4975
|
-
case "file-contains": {
|
|
4976
|
-
const abs = resolveRepoPath(repoRoot, predicate.file);
|
|
4977
|
-
if (!abs) {
|
|
4978
|
-
return {
|
|
4979
|
-
predicate,
|
|
4980
|
-
passed: false,
|
|
4981
|
-
reason: `path escapes repo root or is invalid: ${predicate.file}`
|
|
4982
|
-
};
|
|
4983
|
-
}
|
|
4984
|
-
const outcome = await checkRepoBoundary(repoRoot, abs);
|
|
4985
|
-
if (outcome === "outside") {
|
|
4986
|
-
return {
|
|
4987
|
-
predicate,
|
|
4988
|
-
passed: false,
|
|
4989
|
-
reason: `path resolves outside repo root: ${predicate.file}`
|
|
4990
|
-
};
|
|
4991
|
-
}
|
|
4992
|
-
if (outcome === "missing") {
|
|
4993
|
-
return {
|
|
4994
|
-
predicate,
|
|
4995
|
-
passed: false,
|
|
4996
|
-
reason: `file not found or unreadable: ${predicate.file}`
|
|
4997
|
-
};
|
|
4998
|
-
}
|
|
4999
|
-
const content = await readFileSafe(abs);
|
|
5000
|
-
if (content === null) {
|
|
5001
|
-
return {
|
|
5002
|
-
predicate,
|
|
5003
|
-
passed: false,
|
|
5004
|
-
reason: `file not found or unreadable: ${predicate.file}`
|
|
5005
|
-
};
|
|
5006
|
-
}
|
|
5007
|
-
const re = safeRegex(predicate.pattern, predicate.flags);
|
|
5008
|
-
if (!re) {
|
|
5009
|
-
return {
|
|
5010
|
-
predicate,
|
|
5011
|
-
passed: false,
|
|
5012
|
-
reason: `invalid regex: /${predicate.pattern}/${predicate.flags ?? ""}`
|
|
5013
|
-
};
|
|
5014
|
-
}
|
|
5015
|
-
const matched = re.test(content);
|
|
5016
|
-
return {
|
|
5017
|
-
predicate,
|
|
5018
|
-
passed: matched,
|
|
5019
|
-
reason: matched ? `pattern matched in ${predicate.file}` : `pattern not found in ${predicate.file}`
|
|
5020
|
-
};
|
|
5021
|
-
}
|
|
5022
|
-
case "file-missing-pattern": {
|
|
5023
|
-
const abs = resolveRepoPath(repoRoot, predicate.file);
|
|
5024
|
-
if (!abs) {
|
|
5025
|
-
return {
|
|
5026
|
-
predicate,
|
|
5027
|
-
passed: false,
|
|
5028
|
-
reason: `path escapes repo root or is invalid: ${predicate.file}`
|
|
5029
|
-
};
|
|
5030
|
-
}
|
|
5031
|
-
const outcome = await checkRepoBoundary(repoRoot, abs);
|
|
5032
|
-
if (outcome === "outside") {
|
|
5033
|
-
return {
|
|
5034
|
-
predicate,
|
|
5035
|
-
passed: false,
|
|
5036
|
-
reason: `path resolves outside repo root: ${predicate.file}`
|
|
5037
|
-
};
|
|
5038
|
-
}
|
|
5039
|
-
if (outcome === "missing") {
|
|
5040
|
-
return {
|
|
5041
|
-
predicate,
|
|
5042
|
-
passed: false,
|
|
5043
|
-
reason: `file not found or unreadable: ${predicate.file}`
|
|
5044
|
-
};
|
|
5045
|
-
}
|
|
5046
|
-
const content = await readFileSafe(abs);
|
|
5047
|
-
if (content === null) {
|
|
5048
|
-
return {
|
|
5049
|
-
predicate,
|
|
5050
|
-
passed: false,
|
|
5051
|
-
reason: `file not found or unreadable: ${predicate.file}`
|
|
5052
|
-
};
|
|
5053
|
-
}
|
|
5054
|
-
const re = safeRegex(predicate.pattern, predicate.flags);
|
|
5055
|
-
if (!re) {
|
|
5056
|
-
return {
|
|
5057
|
-
predicate,
|
|
5058
|
-
passed: false,
|
|
5059
|
-
reason: `invalid regex: /${predicate.pattern}/${predicate.flags ?? ""}`
|
|
5060
|
-
};
|
|
5061
|
-
}
|
|
5062
|
-
const matched = re.test(content);
|
|
5063
|
-
return {
|
|
5064
|
-
predicate,
|
|
5065
|
-
passed: !matched,
|
|
5066
|
-
reason: matched ? `pattern unexpectedly present in ${predicate.file}` : `pattern absent in ${predicate.file}`
|
|
5067
|
-
};
|
|
5068
|
-
}
|
|
5069
|
-
case "git-diff-applies":
|
|
5070
|
-
return evaluateGitDiffApplies(predicate, repoRoot);
|
|
5071
|
-
case "ast-shape": {
|
|
5072
|
-
return {
|
|
5073
|
-
predicate,
|
|
5074
|
-
passed: false,
|
|
5075
|
-
reason: "ast-shape predicates are not yet implemented in the OSS verifier"
|
|
5076
|
-
};
|
|
5077
|
-
}
|
|
5078
|
-
default: {
|
|
5079
|
-
const _exhaustive = predicate;
|
|
5080
|
-
void _exhaustive;
|
|
5081
|
-
return {
|
|
5082
|
-
predicate,
|
|
5083
|
-
passed: false,
|
|
5084
|
-
reason: "unknown predicate kind"
|
|
5085
|
-
};
|
|
5086
|
-
}
|
|
5087
|
-
}
|
|
5088
|
-
}
|
|
5089
|
-
async function evaluateVerificationSpec(spec, repoRoot) {
|
|
5090
|
-
const results = [];
|
|
5091
|
-
for (const predicate of spec.code) {
|
|
5092
|
-
results.push(await evaluateOne(predicate, repoRoot));
|
|
5093
|
-
}
|
|
5094
|
-
const failedPredicates = results.filter((r) => !r.passed);
|
|
5095
|
-
const hasVulnerabilityPredicate = spec.code.some((predicate) => predicate.kind !== "git-diff-applies");
|
|
5096
|
-
if (!hasVulnerabilityPredicate && !spec.behavior) {
|
|
5097
|
-
return {
|
|
5098
|
-
passed: false,
|
|
5099
|
-
failedPredicates,
|
|
5100
|
-
reason: spec.code.length === 0 ? "no predicates" : "no vulnerability predicates"
|
|
5101
|
-
};
|
|
5102
|
-
}
|
|
5103
|
-
const codePassed = failedPredicates.length === 0 && hasVulnerabilityPredicate;
|
|
5104
|
-
if (spec.behavior) {
|
|
5105
|
-
return {
|
|
5106
|
-
passed: codePassed,
|
|
5107
|
-
failedPredicates,
|
|
5108
|
-
reason: "behavior eval not yet supported"
|
|
5109
|
-
};
|
|
5110
|
-
}
|
|
5111
|
-
return {
|
|
5112
|
-
passed: codePassed,
|
|
5113
|
-
failedPredicates
|
|
5114
|
-
};
|
|
5115
|
-
}
|
|
5116
|
-
|
|
5117
|
-
// packages/core/dist/improvement/artifacts.js
|
|
5118
|
-
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5119
|
-
import { closeSync as closeSync3, constants as constants5, fchmodSync, fstatSync as fstatSync2, fsyncSync as fsyncSync2, linkSync, lstatSync as lstatSync3, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5120
|
-
import { dirname as dirname4, isAbsolute as isAbsolute8, join as join11, parse, resolve as resolve5, sep as sep4 } from "node:path";
|
|
5121
|
-
var MAX_ARTIFACT_BYTES = 128 * 1024 * 1024;
|
|
5122
|
-
function hasCode(error, code) {
|
|
5123
|
-
return error instanceof Error && "code" in error && error.code === code;
|
|
5124
|
-
}
|
|
5125
|
-
function inspectDirectories(path, create) {
|
|
5126
|
-
if (!isAbsolute8(path))
|
|
5127
|
-
throw new Error("evolution storage paths must be absolute");
|
|
5128
|
-
const absolute = resolve5(path);
|
|
5129
|
-
const root = parse(absolute).root;
|
|
5130
|
-
let cursor = root;
|
|
5131
|
-
for (const component of absolute.slice(root.length).split(sep4).filter(Boolean)) {
|
|
5132
|
-
cursor = join11(cursor, component);
|
|
5133
|
-
if (create) {
|
|
5134
|
-
try {
|
|
5135
|
-
mkdirSync4(cursor, { mode: 448 });
|
|
5136
|
-
} catch (error) {
|
|
5137
|
-
if (!hasCode(error, "EEXIST"))
|
|
5138
|
-
throw error;
|
|
5139
|
-
}
|
|
5140
|
-
}
|
|
5141
|
-
const stat2 = lstatSync3(cursor);
|
|
5142
|
-
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
5143
|
-
throw new Error(`unsafe evolution directory: ${cursor}`);
|
|
5144
|
-
}
|
|
5145
|
-
const leaf = lstatSync3(absolute);
|
|
5146
|
-
if (typeof process.getuid === "function" && leaf.uid !== process.getuid() || (leaf.mode & 63) !== 0) {
|
|
5147
|
-
throw new Error(`evolution storage directory must be owner-only: ${absolute}`);
|
|
5148
|
-
}
|
|
5149
|
-
return absolute;
|
|
5150
|
-
}
|
|
5151
|
-
function ensureEvolutionDirectory(path) {
|
|
5152
|
-
return inspectDirectories(path, true);
|
|
5153
|
-
}
|
|
5154
|
-
function readEvolutionArtifact(path) {
|
|
5155
|
-
inspectDirectories(dirname4(path), false);
|
|
5156
|
-
const fd = openSync3(path, constants5.O_RDONLY | constants5.O_NOFOLLOW);
|
|
5157
|
-
try {
|
|
5158
|
-
const stat2 = fstatSync2(fd);
|
|
5159
|
-
if (!stat2.isFile() || stat2.size > MAX_ARTIFACT_BYTES || (stat2.mode & 63) !== 0 || typeof process.getuid === "function" && stat2.uid !== process.getuid()) {
|
|
5160
|
-
throw new Error(`unsafe evolution artifact: ${path}`);
|
|
5161
|
-
}
|
|
5162
|
-
const value = JSON.parse(readFileSync6(fd, "utf8"));
|
|
5163
|
-
canonicalEvolutionJson(value);
|
|
5164
|
-
return value;
|
|
5165
|
-
} finally {
|
|
5166
|
-
closeSync3(fd);
|
|
5167
|
-
}
|
|
5168
|
-
}
|
|
5169
|
-
function publishEvolutionArtifact(path, value) {
|
|
5170
|
-
const content = canonicalEvolutionJson(value);
|
|
5171
|
-
if (Buffer.byteLength(content) > MAX_ARTIFACT_BYTES)
|
|
5172
|
-
throw new Error("evolution artifact exceeds retention limit");
|
|
5173
|
-
const directory = ensureEvolutionDirectory(dirname4(path));
|
|
5174
|
-
const temporary = join11(directory, `.artifact-${randomUUID3()}.tmp`);
|
|
5175
|
-
const fd = openSync3(temporary, constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW, 384);
|
|
5176
|
-
try {
|
|
5177
|
-
try {
|
|
5178
|
-
writeFileSync3(fd, content, "utf8");
|
|
5179
|
-
fchmodSync(fd, 256);
|
|
5180
|
-
fsyncSync2(fd);
|
|
5181
|
-
} finally {
|
|
5182
|
-
closeSync3(fd);
|
|
5183
|
-
}
|
|
5184
|
-
try {
|
|
5185
|
-
linkSync(temporary, path);
|
|
5186
|
-
} catch (error) {
|
|
5187
|
-
if (!hasCode(error, "EEXIST"))
|
|
5188
|
-
throw error;
|
|
5189
|
-
if (canonicalEvolutionJson(readEvolutionArtifact(path)) !== content) {
|
|
5190
|
-
throw new Error(`immutable evolution artifact collision: ${path}`);
|
|
5191
|
-
}
|
|
5192
|
-
}
|
|
5193
|
-
const directoryFd = openSync3(directory, constants5.O_RDONLY | constants5.O_DIRECTORY | constants5.O_NOFOLLOW);
|
|
5194
|
-
try {
|
|
5195
|
-
fsyncSync2(directoryFd);
|
|
5196
|
-
} finally {
|
|
5197
|
-
closeSync3(directoryFd);
|
|
5198
|
-
}
|
|
5199
|
-
} finally {
|
|
5200
|
-
unlinkSync(temporary);
|
|
5201
|
-
}
|
|
5202
|
-
}
|
|
5203
|
-
|
|
5204
|
-
// packages/core/dist/agent/apply-patch.js
|
|
5205
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5206
|
-
import { dirname as dirname5 } from "node:path";
|
|
5207
|
-
var BEGIN = "*** Begin Patch";
|
|
5208
|
-
var END = "*** End Patch";
|
|
5209
|
-
function parsePatch(input) {
|
|
5210
|
-
const text = input.replace(/\r\n/g, "\n");
|
|
5211
|
-
const lines = text.split("\n");
|
|
5212
|
-
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
5213
|
-
lines.pop();
|
|
5214
|
-
}
|
|
5215
|
-
if (lines.length === 0 || lines[0].trim() !== BEGIN) {
|
|
5216
|
-
throw new Error(`apply_patch: envelope must start with "${BEGIN}" (got: ${lines[0] ?? "<empty>"})`);
|
|
5217
|
-
}
|
|
5218
|
-
if (lines[lines.length - 1].trim() !== END) {
|
|
5219
|
-
throw new Error(`apply_patch: envelope must end with "${END}"`);
|
|
5220
|
-
}
|
|
5221
|
-
const body = lines.slice(1, -1);
|
|
5222
|
-
const ops = [];
|
|
5223
|
-
let i = 0;
|
|
5224
|
-
while (i < body.length) {
|
|
5225
|
-
const line = body[i];
|
|
5226
|
-
const trimmed = line.trim();
|
|
5227
|
-
if (trimmed === "") {
|
|
5228
|
-
i += 1;
|
|
5229
|
-
continue;
|
|
5230
|
-
}
|
|
5231
|
-
const addMatch = /^\*\*\* Add File: (.+)$/.exec(trimmed);
|
|
5232
|
-
const replaceMatch = /^\*\*\* Replace File: (.+)$/.exec(trimmed);
|
|
5233
|
-
const updateMatch = /^\*\*\* Update File: (.+)$/.exec(trimmed);
|
|
5234
|
-
const deleteMatch = /^\*\*\* Delete File: (.+)$/.exec(trimmed);
|
|
5235
|
-
if (addMatch || replaceMatch) {
|
|
5236
|
-
const path = (addMatch?.[1] ?? replaceMatch[1]).trim();
|
|
5237
|
-
const overwrite = replaceMatch !== null;
|
|
5238
|
-
i += 1;
|
|
5239
|
-
const contentLines = [];
|
|
5240
|
-
while (i < body.length && !isOpHeader(body[i])) {
|
|
5241
|
-
const ln = body[i];
|
|
5242
|
-
if (ln.length === 0) {
|
|
5243
|
-
contentLines.push("");
|
|
5244
|
-
} else if (ln[0] === "+") {
|
|
5245
|
-
contentLines.push(ln.slice(1));
|
|
5246
|
-
} else {
|
|
5247
|
-
throw new Error(`apply_patch: Add/Replace body lines must start with "+" (got: ${JSON.stringify(ln)})`);
|
|
5248
|
-
}
|
|
5249
|
-
i += 1;
|
|
5250
|
-
}
|
|
5251
|
-
ops.push({
|
|
5252
|
-
kind: "add",
|
|
5253
|
-
path,
|
|
5254
|
-
contents: contentLines.join("\n"),
|
|
5255
|
-
overwrite
|
|
5256
|
-
});
|
|
5257
|
-
continue;
|
|
5258
|
-
}
|
|
5259
|
-
if (deleteMatch) {
|
|
5260
|
-
ops.push({ kind: "delete", path: deleteMatch[1].trim() });
|
|
5261
|
-
i += 1;
|
|
5262
|
-
continue;
|
|
5263
|
-
}
|
|
5264
|
-
if (updateMatch) {
|
|
5265
|
-
const path = updateMatch[1].trim();
|
|
5266
|
-
i += 1;
|
|
5267
|
-
const hunks = [];
|
|
5268
|
-
while (i < body.length && !isOpHeader(body[i])) {
|
|
5269
|
-
const hunkLine = body[i];
|
|
5270
|
-
if (!hunkLine.startsWith("@@")) {
|
|
5271
|
-
throw new Error(`apply_patch: Update File body must begin with "@@ <anchor>" hunk header (got: ${JSON.stringify(hunkLine)})`);
|
|
5272
|
-
}
|
|
5273
|
-
const anchor = hunkLine.slice(2).trim();
|
|
5274
|
-
i += 1;
|
|
5275
|
-
const hunkBody = [];
|
|
5276
|
-
while (i < body.length && !isOpHeader(body[i]) && !body[i].startsWith("@@")) {
|
|
5277
|
-
const ln = body[i];
|
|
5278
|
-
if (ln === "") {
|
|
5279
|
-
hunkBody.push({ kind: "context", text: "" });
|
|
5280
|
-
} else if (ln[0] === " ") {
|
|
5281
|
-
hunkBody.push({ kind: "context", text: ln.slice(1) });
|
|
5282
|
-
} else if (ln[0] === "+") {
|
|
5283
|
-
hunkBody.push({ kind: "add", text: ln.slice(1) });
|
|
5284
|
-
} else if (ln[0] === "-") {
|
|
5285
|
-
hunkBody.push({ kind: "del", text: ln.slice(1) });
|
|
5286
|
-
} else {
|
|
5287
|
-
throw new Error(`apply_patch: Update hunk body lines must start with " ", "+", or "-" (got: ${JSON.stringify(ln)})`);
|
|
5288
|
-
}
|
|
5289
|
-
i += 1;
|
|
5290
|
-
}
|
|
5291
|
-
hunks.push({ anchor, body: hunkBody });
|
|
5292
|
-
}
|
|
5293
|
-
if (hunks.length === 0) {
|
|
5294
|
-
throw new Error(`apply_patch: "Update File: ${path}" requires at least one "@@" hunk`);
|
|
5295
|
-
}
|
|
5296
|
-
ops.push({ kind: "update", path, hunks });
|
|
5297
|
-
continue;
|
|
5298
|
-
}
|
|
5299
|
-
throw new Error(`apply_patch: unknown directive (got: ${JSON.stringify(trimmed)})`);
|
|
5300
|
-
}
|
|
5301
|
-
if (ops.length === 0) {
|
|
5302
|
-
throw new Error("apply_patch: envelope contains no operations");
|
|
5303
|
-
}
|
|
5304
|
-
return ops;
|
|
5305
|
-
}
|
|
5306
|
-
function isOpHeader(line) {
|
|
5307
|
-
return line.startsWith("*** Add File:") || line.startsWith("*** Replace File:") || line.startsWith("*** Update File:") || line.startsWith("*** Delete File:");
|
|
5308
|
-
}
|
|
5309
|
-
function applyPatchOps(ops, pathResolver) {
|
|
5310
|
-
const applied = [];
|
|
5311
|
-
for (const op of ops) {
|
|
5312
|
-
const abs = pathResolver(op.path);
|
|
5313
|
-
if (op.kind === "add") {
|
|
5314
|
-
if (!op.overwrite && existsSync3(abs)) {
|
|
5315
|
-
throw new Error(`apply_patch: Add File "${op.path}" failed \u2014 file already exists. Use "*** Replace File: ${op.path}" to overwrite.`);
|
|
5316
|
-
}
|
|
5317
|
-
mkdirSync5(dirname5(abs), { recursive: true });
|
|
5318
|
-
writeFileSync4(abs, op.contents, "utf-8");
|
|
5319
|
-
applied.push({ kind: "add", path: op.path });
|
|
5320
|
-
continue;
|
|
5321
|
-
}
|
|
5322
|
-
if (op.kind === "delete") {
|
|
5323
|
-
if (!existsSync3(abs)) {
|
|
5324
|
-
throw new Error(`apply_patch: Delete File "${op.path}" failed \u2014 file does not exist`);
|
|
5325
|
-
}
|
|
5326
|
-
unlinkSync2(abs);
|
|
5327
|
-
applied.push({ kind: "delete", path: op.path });
|
|
5328
|
-
continue;
|
|
5329
|
-
}
|
|
5330
|
-
if (!existsSync3(abs)) {
|
|
5331
|
-
throw new Error(`apply_patch: Update File "${op.path}" failed \u2014 file does not exist`);
|
|
5332
|
-
}
|
|
5333
|
-
const original = readFileSync7(abs, "utf-8");
|
|
5334
|
-
const updated = applyUpdateHunks(original, op.path, op.hunks);
|
|
5335
|
-
writeFileSync4(abs, updated, "utf-8");
|
|
5336
|
-
applied.push({ kind: "update", path: op.path });
|
|
5337
|
-
}
|
|
5338
|
-
return { applied };
|
|
5339
|
-
}
|
|
5340
|
-
function applyUpdateHunks(source, logicalPath, hunks) {
|
|
5341
|
-
const hadTrailingNewline = source.endsWith("\n");
|
|
5342
|
-
const sourceBody = hadTrailingNewline ? source.slice(0, -1) : source;
|
|
5343
|
-
let lines = sourceBody.split("\n");
|
|
5344
|
-
for (const hunk of hunks) {
|
|
5345
|
-
const anchorIdx = locateAnchor(lines, hunk.anchor, logicalPath);
|
|
5346
|
-
const { newLines, consumed } = applyOneHunk(lines, anchorIdx, hunk, logicalPath);
|
|
5347
|
-
lines = [
|
|
5348
|
-
...lines.slice(0, anchorIdx),
|
|
5349
|
-
...newLines,
|
|
5350
|
-
...lines.slice(anchorIdx + consumed)
|
|
5351
|
-
];
|
|
5352
|
-
}
|
|
5353
|
-
return lines.join("\n") + (hadTrailingNewline ? "\n" : "");
|
|
5354
|
-
}
|
|
5355
|
-
function locateAnchor(lines, anchor, logicalPath) {
|
|
5356
|
-
if (anchor === "") {
|
|
5357
|
-
return 0;
|
|
5358
|
-
}
|
|
5359
|
-
const matches = [];
|
|
5360
|
-
for (let i = 0; i < lines.length; i++) {
|
|
5361
|
-
if (lines[i].includes(anchor)) {
|
|
5362
|
-
matches.push(i);
|
|
5363
|
-
}
|
|
5364
|
-
}
|
|
5365
|
-
if (matches.length === 0) {
|
|
5366
|
-
throw new Error(`apply_patch: anchor "${anchor}" not found in ${logicalPath}; refine the @@ anchor to a line that exists in the file`);
|
|
5367
|
-
}
|
|
5368
|
-
if (matches.length > 1) {
|
|
5369
|
-
throw new Error(`apply_patch: context "${anchor}" matches ${matches.length} locations in ${logicalPath}; refine the @@ anchor to be unique`);
|
|
5370
|
-
}
|
|
5371
|
-
return matches[0];
|
|
5372
|
-
}
|
|
5373
|
-
function applyOneHunk(lines, anchorIdx, hunk, logicalPath) {
|
|
5374
|
-
const newLines = [];
|
|
5375
|
-
let cursor = anchorIdx;
|
|
5376
|
-
for (const entry of hunk.body) {
|
|
5377
|
-
if (entry.kind === "context") {
|
|
5378
|
-
const observed = lines[cursor];
|
|
5379
|
-
if (observed === void 0 || observed !== entry.text) {
|
|
5380
|
-
throw new Error(`apply_patch: context mismatch in ${logicalPath} at line ${cursor + 1}: expected ${JSON.stringify(entry.text)}, got ${JSON.stringify(observed ?? "<eof>")}`);
|
|
5381
|
-
}
|
|
5382
|
-
newLines.push(entry.text);
|
|
5383
|
-
cursor += 1;
|
|
5384
|
-
continue;
|
|
5385
|
-
}
|
|
5386
|
-
if (entry.kind === "del") {
|
|
5387
|
-
const observed = lines[cursor];
|
|
5388
|
-
if (observed === void 0 || observed !== entry.text) {
|
|
5389
|
-
throw new Error(`apply_patch: removed line mismatch in ${logicalPath} at line ${cursor + 1}: expected ${JSON.stringify(entry.text)}, got ${JSON.stringify(observed ?? "<eof>")}`);
|
|
5390
|
-
}
|
|
5391
|
-
cursor += 1;
|
|
5392
|
-
continue;
|
|
5393
|
-
}
|
|
5394
|
-
newLines.push(entry.text);
|
|
5395
|
-
}
|
|
5396
|
-
return { newLines, consumed: cursor - anchorIdx };
|
|
5397
|
-
}
|
|
5398
|
-
|
|
5399
|
-
// packages/core/dist/agent/tools/scope-path.js
|
|
5400
|
-
import { lstatSync as lstatSync4, realpathSync as realpathSync2 } from "node:fs";
|
|
5401
|
-
import { basename, dirname as dirname6, isAbsolute as isAbsolute9, resolve as resolve6, sep as sep5 } from "node:path";
|
|
5402
|
-
function pathEntryExists(path) {
|
|
5403
|
-
try {
|
|
5404
|
-
lstatSync4(path);
|
|
5405
|
-
return true;
|
|
5406
|
-
} catch (error) {
|
|
5407
|
-
if (error.code === "ENOENT")
|
|
5408
|
-
return false;
|
|
5409
|
-
throw error;
|
|
5410
|
-
}
|
|
5411
|
-
}
|
|
5412
|
-
function canonicalizePath(path) {
|
|
5413
|
-
const missingSegments = [];
|
|
5414
|
-
let existingPath = path;
|
|
5415
|
-
while (!pathEntryExists(existingPath)) {
|
|
5416
|
-
const parent = dirname6(existingPath);
|
|
5417
|
-
if (parent === existingPath) {
|
|
5418
|
-
throw new Error(`Path does not have an existing parent: ${path}`);
|
|
5419
|
-
}
|
|
5420
|
-
missingSegments.unshift(basename(existingPath));
|
|
5421
|
-
existingPath = parent;
|
|
5422
|
-
}
|
|
5423
|
-
return resolve6(realpathSync2(existingPath), ...missingSegments);
|
|
5424
|
-
}
|
|
5425
|
-
function isScopedPath(scopePath, inputPath) {
|
|
5426
|
-
const root = realpathSync2(scopePath);
|
|
5427
|
-
const logicalCandidate = isAbsolute9(inputPath) ? resolve6(inputPath) : resolve6(root, inputPath);
|
|
5428
|
-
const candidate = canonicalizePath(logicalCandidate);
|
|
5429
|
-
return candidate === root || candidate.startsWith(root + sep5);
|
|
5430
|
-
}
|
|
5431
|
-
function resolveScopedPath(scopePath, inputPath) {
|
|
5432
|
-
if (isScopeEnforcementEnabled() && (!scopePath || !isScopedPath(scopePath, inputPath))) {
|
|
5433
|
-
throw new Error(`Path escapes the allowed scope: ${inputPath}`);
|
|
5434
|
-
}
|
|
5435
|
-
const root = scopePath ? realpathSync2(scopePath) : process.cwd();
|
|
5436
|
-
const logicalCandidate = isAbsolute9(inputPath) ? resolve6(inputPath) : resolve6(root, inputPath);
|
|
5437
|
-
return canonicalizePath(logicalCandidate);
|
|
5438
|
-
}
|
|
5439
|
-
|
|
5440
4490
|
export {
|
|
5441
|
-
DEFAULT_IMPROVEMENT_PROMOTION_POLICY,
|
|
5442
|
-
evaluateImprovementPromotion,
|
|
5443
|
-
appendImprovementLedgerEntry,
|
|
5444
|
-
verifyImprovementLedger,
|
|
5445
4491
|
superviseChild,
|
|
5446
4492
|
runScannerProcess,
|
|
5447
4493
|
buildSqlmapArgv,
|
|
@@ -5457,9 +4503,8 @@ export {
|
|
|
5457
4503
|
shellTokens,
|
|
5458
4504
|
ANALYTICS_LEVEL_ENV,
|
|
5459
4505
|
levelAtLeast,
|
|
4506
|
+
analyticsOptedOut,
|
|
5460
4507
|
resolveAnalyticsLevel,
|
|
5461
|
-
presentationEventSink,
|
|
5462
|
-
eventBus,
|
|
5463
4508
|
DEFAULT_CLOUD_HOST,
|
|
5464
4509
|
CloudAuthMissingError,
|
|
5465
4510
|
CloudAuthError,
|
|
@@ -5493,6 +4538,7 @@ export {
|
|
|
5493
4538
|
truncateMiddle,
|
|
5494
4539
|
formatTruncated,
|
|
5495
4540
|
parseRepositoryAcquisition,
|
|
4541
|
+
repositoryIdentityMatchesTarget,
|
|
5496
4542
|
repositoryAcquisitionAllowed,
|
|
5497
4543
|
runRepositoryAcquisition,
|
|
5498
4544
|
assertSmolvmWorkbenchPlatform,
|
|
@@ -5504,20 +4550,11 @@ export {
|
|
|
5504
4550
|
getSmolvmWorkbenchStatus,
|
|
5505
4551
|
resolveSmolvmImage,
|
|
5506
4552
|
runSmolvm,
|
|
4553
|
+
WORKBENCH_BROKER_WORKSPACE,
|
|
5507
4554
|
DEFAULT_WORKBENCH_BROKER_LIMITS,
|
|
5508
4555
|
isAdmittedSmolvmWorkbench,
|
|
5509
4556
|
getWorkbenchBrokerLimits,
|
|
5510
4557
|
resolveWorkbenchBrokerImage,
|
|
5511
4558
|
startWorkbenchBroker,
|
|
5512
|
-
runWorkbenchBrokerProgram
|
|
5513
|
-
canonicalEvolutionJson,
|
|
5514
|
-
parseEvolutionConfig,
|
|
5515
|
-
loadEvolutionConfigFile,
|
|
5516
|
-
ensureEvolutionDirectory,
|
|
5517
|
-
readEvolutionArtifact,
|
|
5518
|
-
publishEvolutionArtifact,
|
|
5519
|
-
evaluateVerificationSpec,
|
|
5520
|
-
parsePatch,
|
|
5521
|
-
applyPatchOps,
|
|
5522
|
-
resolveScopedPath
|
|
4559
|
+
runWorkbenchBrokerProgram
|
|
5523
4560
|
};
|