agentlas 0.7.0 → 0.9.2
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/CHANGELOG.md +199 -0
- package/README.md +161 -18
- package/bin/agentlas.cjs +8 -8
- package/engine/agentlas-core-harness.cjs +212 -0
- package/engine/agentlas-desktop-loadout.cjs +527 -0
- package/engine/agentlas-doctor.cjs +1 -1
- package/engine/agentlas-experience-exchange.cjs +835 -85
- package/engine/agentlas-experience-intake.cjs +444 -0
- package/engine/agentlas-experience-mcp.cjs +580 -18
- package/engine/agentlas-i18n.cjs +10 -10
- package/engine/agentlas-input.cjs +5 -4
- package/engine/agentlas-mcp-env.cjs +219 -0
- package/engine/agentlas-mcp-wrapper.cjs +51 -0
- package/engine/agentlas-memory-governance.cjs +1029 -0
- package/engine/agentlas-native-host.cjs +129 -39
- package/engine/agentlas-parity.cjs +339 -154
- package/engine/agentlas-repl.cjs +306 -31
- package/engine/agentlas-workforce.cjs +2991 -0
- package/engine/agentlas-workload-routing.cjs +523 -0
- package/engine/agentlas.cjs +1619 -234
- package/engine/bootstrap-schema.sql +1 -1
- package/engine/experience-taxonomy-v1.json +49 -0
- package/package.json +8 -4
- package/scripts/gen-bootstrap-schema.sh +0 -23
- package/test/bootstrap-race.cjs +0 -47
- package/test/capture-runtime-guard.cjs +0 -122
- package/test/cloud-asset-restore.cjs +0 -423
- package/test/cloud-cas-client.cjs +0 -333
- package/test/cloud-owner-restore.cjs +0 -183
- package/test/cloud-runtime-paths.cjs +0 -40
- package/test/cloud-save-publish.cjs +0 -487
- package/test/credential-env-regression.cjs +0 -52
- package/test/engine-hardening-regression.cjs +0 -74
- package/test/experience-exchange-contract.cjs +0 -569
- package/test/experience-mcp-contract.cjs +0 -391
- package/test/fixtures/portable-experience-bundle-v1-golden.json +0 -124
- package/test/login-loopback-security.cjs +0 -115
- package/test/mcp-config-isolation.cjs +0 -36
- package/test/permission-mapping.cjs +0 -180
- package/test/route-regression.cjs +0 -357
- package/test/run-api-regression.cjs +0 -322
- package/test/runtime-env-protection.cjs +0 -89
- package/test/semver-precedence.cjs +0 -39
- package/test/smoke.sh +0 -93
- package/test/sqlite-driver-probe.cjs +0 -22
- package/test/terminal-ui-regression.cjs +0 -477
- package/test/timeout-regression.cjs +0 -218
- package/test/tool-workspace-boundary.cjs +0 -165
- package/test/update-safety.cjs +0 -376
package/test/update-safety.cjs
DELETED
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const assert = require("node:assert/strict");
|
|
5
|
-
const crypto = require("node:crypto");
|
|
6
|
-
const fs = require("node:fs");
|
|
7
|
-
const os = require("node:os");
|
|
8
|
-
const path = require("node:path");
|
|
9
|
-
const {
|
|
10
|
-
updateTimeoutConfig,
|
|
11
|
-
fetchUpdateMetadata,
|
|
12
|
-
validateDesktopUpdateArtifact,
|
|
13
|
-
downloadUpdateFile,
|
|
14
|
-
verifyMacAppBundle,
|
|
15
|
-
replaceMacAppBundle,
|
|
16
|
-
} = require("../engine/agentlas.cjs");
|
|
17
|
-
|
|
18
|
-
function streamedResponse(parts, delayMs = 0, options = {}) {
|
|
19
|
-
let timer = null;
|
|
20
|
-
let index = 0;
|
|
21
|
-
const body = new ReadableStream({
|
|
22
|
-
start(controller) {
|
|
23
|
-
const push = () => {
|
|
24
|
-
if (index >= parts.length) {
|
|
25
|
-
if (!options.stall) controller.close();
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
controller.enqueue(Buffer.from(parts[index++]));
|
|
29
|
-
timer = setTimeout(push, delayMs);
|
|
30
|
-
};
|
|
31
|
-
timer = setTimeout(push, options.immediate ? 0 : delayMs);
|
|
32
|
-
},
|
|
33
|
-
cancel() {
|
|
34
|
-
if (timer) clearTimeout(timer);
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
const headers = new Headers(options.headers || {});
|
|
38
|
-
return {
|
|
39
|
-
ok: options.status == null || (options.status >= 200 && options.status < 300),
|
|
40
|
-
status: options.status || 200,
|
|
41
|
-
headers,
|
|
42
|
-
body,
|
|
43
|
-
arrayBuffer() {
|
|
44
|
-
throw new Error("arrayBuffer must never be used for updater transfers");
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function sha256(value) {
|
|
50
|
-
return crypto.createHash("sha256").update(value).digest("hex");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function artifactFor(value, overrides = {}) {
|
|
54
|
-
return {
|
|
55
|
-
url: "https://downloads.example.test/Agentlas.dmg",
|
|
56
|
-
fileName: "Agentlas.dmg",
|
|
57
|
-
sizeBytes: value.length,
|
|
58
|
-
sha256: sha256(value),
|
|
59
|
-
...overrides,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function assertNoPartials(dir) {
|
|
64
|
-
assert.deepEqual(fs.readdirSync(dir).filter((name) => name.includes(".partial.")), []);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function testMetadataPolicy() {
|
|
68
|
-
assert.deepEqual(
|
|
69
|
-
updateTimeoutConfig({
|
|
70
|
-
AGENTLAS_UPDATE_METADATA_CONNECT_TIMEOUT_MS: "NaN",
|
|
71
|
-
AGENTLAS_UPDATE_METADATA_IDLE_TIMEOUT_MS: "Infinity",
|
|
72
|
-
AGENTLAS_UPDATE_METADATA_TOTAL_TIMEOUT_MS: "bad",
|
|
73
|
-
}, "metadata"),
|
|
74
|
-
{ connectMs: 15_000, idleMs: 15_000, totalMs: 30_000 },
|
|
75
|
-
);
|
|
76
|
-
assert.deepEqual(
|
|
77
|
-
updateTimeoutConfig({
|
|
78
|
-
AGENTLAS_UPDATE_DOWNLOAD_CONNECT_TIMEOUT_MS: "-1",
|
|
79
|
-
AGENTLAS_UPDATE_DOWNLOAD_IDLE_TIMEOUT_MS: "0",
|
|
80
|
-
AGENTLAS_UPDATE_DOWNLOAD_TOTAL_TIMEOUT_MS: "999999999999",
|
|
81
|
-
}, "download"),
|
|
82
|
-
{ connectMs: 1_000, idleMs: 1_000, totalMs: 3_600_000 },
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const metadata = JSON.stringify({ version: "1.2.3-rc.1", artifacts: [] });
|
|
86
|
-
const parsed = await fetchUpdateMetadata("https://agentlas.example.test/latest", {
|
|
87
|
-
fetch: async () => streamedResponse([metadata.slice(0, 8), metadata.slice(8)], 12),
|
|
88
|
-
timeoutConfig: { connectMs: 40, idleMs: 30, totalMs: 150 },
|
|
89
|
-
});
|
|
90
|
-
assert.equal(parsed.version, "1.2.3-rc.1");
|
|
91
|
-
|
|
92
|
-
await assert.rejects(
|
|
93
|
-
fetchUpdateMetadata("https://agentlas.example.test/latest", {
|
|
94
|
-
fetch: () => new Promise(() => {}),
|
|
95
|
-
timeoutConfig: { connectMs: 20, idleMs: 40, totalMs: 80 },
|
|
96
|
-
}),
|
|
97
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_CONNECT_TIMEOUT",
|
|
98
|
-
);
|
|
99
|
-
await assert.rejects(
|
|
100
|
-
fetchUpdateMetadata("https://agentlas.example.test/latest", {
|
|
101
|
-
fetch: async () => streamedResponse(["{"], 0, { immediate: true, stall: true }),
|
|
102
|
-
timeoutConfig: { connectMs: 30, idleMs: 20, totalMs: 100 },
|
|
103
|
-
}),
|
|
104
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_IDLE_TIMEOUT",
|
|
105
|
-
);
|
|
106
|
-
await assert.rejects(
|
|
107
|
-
fetchUpdateMetadata("https://agentlas.example.test/latest", {
|
|
108
|
-
fetch: async () => streamedResponse(Array(20).fill(" "), 8, { immediate: true }),
|
|
109
|
-
timeoutConfig: { connectMs: 20, idleMs: 20, totalMs: 35 },
|
|
110
|
-
}),
|
|
111
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_TOTAL_TIMEOUT",
|
|
112
|
-
);
|
|
113
|
-
await assert.rejects(
|
|
114
|
-
fetchUpdateMetadata("https://agentlas.example.test/latest", {
|
|
115
|
-
fetch: async () => streamedResponse(["x".repeat(33)], 0, { immediate: true }),
|
|
116
|
-
maxBytes: 32,
|
|
117
|
-
timeoutConfig: { connectMs: 30, idleMs: 30, totalMs: 100 },
|
|
118
|
-
}),
|
|
119
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_TOO_LARGE",
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function testArtifactAndDownloadPolicy() {
|
|
124
|
-
const payload = Buffer.from("streamed-dmg-payload");
|
|
125
|
-
assert.throws(
|
|
126
|
-
() => validateDesktopUpdateArtifact(artifactFor(payload, { sha256: undefined })),
|
|
127
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_MISSING_DIGEST",
|
|
128
|
-
);
|
|
129
|
-
assert.throws(
|
|
130
|
-
() => validateDesktopUpdateArtifact(artifactFor(payload, { sizeBytes: undefined })),
|
|
131
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_MISSING_SIZE",
|
|
132
|
-
);
|
|
133
|
-
assert.throws(
|
|
134
|
-
() => validateDesktopUpdateArtifact(artifactFor(payload, { fileName: "../Agentlas.dmg" })),
|
|
135
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_INVALID_FILENAME",
|
|
136
|
-
);
|
|
137
|
-
assert.throws(
|
|
138
|
-
() => validateDesktopUpdateArtifact(artifactFor(payload, { url: "http://downloads.example.test/Agentlas.dmg" })),
|
|
139
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_INSECURE_URL",
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "agentlas-update-test."));
|
|
143
|
-
try {
|
|
144
|
-
let unsafeFetchCalled = false;
|
|
145
|
-
const unsafeDestination = path.join(root, "missing-integrity.dmg");
|
|
146
|
-
await assert.rejects(
|
|
147
|
-
downloadUpdateFile("https://downloads.example.test/Agentlas.dmg", unsafeDestination, artifactFor(payload, { sha256: undefined }), {
|
|
148
|
-
fetch: async () => {
|
|
149
|
-
unsafeFetchCalled = true;
|
|
150
|
-
return streamedResponse([payload], 0, { immediate: true });
|
|
151
|
-
},
|
|
152
|
-
maxBytes: 1024,
|
|
153
|
-
}),
|
|
154
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_MISSING_DIGEST",
|
|
155
|
-
);
|
|
156
|
-
assert.equal(unsafeFetchCalled, false, "missing integrity metadata must fail before any network request");
|
|
157
|
-
assert.equal(fs.existsSync(unsafeDestination), false);
|
|
158
|
-
|
|
159
|
-
const destination = path.join(root, "ok.dmg");
|
|
160
|
-
const result = await downloadUpdateFile("https://downloads.example.test/Agentlas.dmg", destination, artifactFor(payload), {
|
|
161
|
-
fetch: async () => streamedResponse([payload.subarray(0, 5), payload.subarray(5, 11), payload.subarray(11)], 12, {
|
|
162
|
-
headers: { "content-length": String(payload.length) },
|
|
163
|
-
}),
|
|
164
|
-
maxBytes: 1024,
|
|
165
|
-
timeoutConfig: { connectMs: 40, idleMs: 30, totalMs: 200 },
|
|
166
|
-
});
|
|
167
|
-
assert.equal(result.bytes, payload.length);
|
|
168
|
-
assert.deepEqual(fs.readFileSync(destination), payload);
|
|
169
|
-
assertNoPartials(root);
|
|
170
|
-
|
|
171
|
-
const connectionDestination = path.join(root, "connect-timeout.dmg");
|
|
172
|
-
await assert.rejects(
|
|
173
|
-
downloadUpdateFile("https://downloads.example.test/Agentlas.dmg", connectionDestination, artifactFor(payload), {
|
|
174
|
-
fetch: () => new Promise(() => {}),
|
|
175
|
-
maxBytes: 1024,
|
|
176
|
-
timeoutConfig: { connectMs: 20, idleMs: 40, totalMs: 90 },
|
|
177
|
-
}),
|
|
178
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_CONNECT_TIMEOUT",
|
|
179
|
-
);
|
|
180
|
-
assert.equal(fs.existsSync(connectionDestination), false);
|
|
181
|
-
assertNoPartials(root);
|
|
182
|
-
|
|
183
|
-
const idleDestination = path.join(root, "idle-timeout.dmg");
|
|
184
|
-
await assert.rejects(
|
|
185
|
-
downloadUpdateFile("https://downloads.example.test/Agentlas.dmg", idleDestination, artifactFor(payload), {
|
|
186
|
-
fetch: async () => streamedResponse([payload.subarray(0, 2)], 0, { immediate: true, stall: true }),
|
|
187
|
-
maxBytes: 1024,
|
|
188
|
-
timeoutConfig: { connectMs: 30, idleMs: 20, totalMs: 100 },
|
|
189
|
-
}),
|
|
190
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_IDLE_TIMEOUT",
|
|
191
|
-
);
|
|
192
|
-
assert.equal(fs.existsSync(idleDestination), false);
|
|
193
|
-
assertNoPartials(root);
|
|
194
|
-
|
|
195
|
-
const digestDestination = path.join(root, "bad-digest.dmg");
|
|
196
|
-
await assert.rejects(
|
|
197
|
-
downloadUpdateFile("https://downloads.example.test/Agentlas.dmg", digestDestination, artifactFor(payload, { sha256: "0".repeat(64) }), {
|
|
198
|
-
fetch: async () => streamedResponse([payload], 0, { immediate: true }),
|
|
199
|
-
maxBytes: 1024,
|
|
200
|
-
timeoutConfig: { connectMs: 30, idleMs: 30, totalMs: 100 },
|
|
201
|
-
}),
|
|
202
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_DIGEST_MISMATCH",
|
|
203
|
-
);
|
|
204
|
-
assert.equal(fs.existsSync(digestDestination), false);
|
|
205
|
-
assertNoPartials(root);
|
|
206
|
-
|
|
207
|
-
const sizeDestination = path.join(root, "bad-size.dmg");
|
|
208
|
-
await assert.rejects(
|
|
209
|
-
downloadUpdateFile("https://downloads.example.test/Agentlas.dmg", sizeDestination, artifactFor(payload, { sizeBytes: payload.length - 1 }), {
|
|
210
|
-
fetch: async () => streamedResponse([payload], 0, { immediate: true }),
|
|
211
|
-
maxBytes: 1024,
|
|
212
|
-
timeoutConfig: { connectMs: 30, idleMs: 30, totalMs: 100 },
|
|
213
|
-
}),
|
|
214
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_SIZE_MISMATCH",
|
|
215
|
-
);
|
|
216
|
-
assert.equal(fs.existsSync(sizeDestination), false);
|
|
217
|
-
assertNoPartials(root);
|
|
218
|
-
|
|
219
|
-
const maxDestination = path.join(root, "too-large.dmg");
|
|
220
|
-
await assert.rejects(
|
|
221
|
-
downloadUpdateFile("https://downloads.example.test/Agentlas.dmg", maxDestination, artifactFor(payload), {
|
|
222
|
-
fetch: async () => streamedResponse([payload], 0, { immediate: true }),
|
|
223
|
-
maxBytes: payload.length - 1,
|
|
224
|
-
timeoutConfig: { connectMs: 30, idleMs: 30, totalMs: 100 },
|
|
225
|
-
}),
|
|
226
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_TOO_LARGE",
|
|
227
|
-
);
|
|
228
|
-
assert.equal(fs.existsSync(maxDestination), false);
|
|
229
|
-
assertNoPartials(root);
|
|
230
|
-
} finally {
|
|
231
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function writeApp(appPath, marker) {
|
|
236
|
-
fs.mkdirSync(appPath, { recursive: true });
|
|
237
|
-
fs.writeFileSync(path.join(appPath, "marker.txt"), marker);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function readMarker(appPath) {
|
|
241
|
-
return fs.readFileSync(path.join(appPath, "marker.txt"), "utf8");
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function appFixture() {
|
|
245
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "agentlas-app-transaction."));
|
|
246
|
-
const sourceApp = path.join(root, "Source.app");
|
|
247
|
-
const targetApp = path.join(root, "Agentlas.app");
|
|
248
|
-
const backupPath = path.join(root, ".Agentlas.backup.test.app");
|
|
249
|
-
const stagingPath = path.join(root, ".Agentlas.installing.test.app");
|
|
250
|
-
writeApp(sourceApp, "new");
|
|
251
|
-
writeApp(targetApp, "original");
|
|
252
|
-
return { root, sourceApp, targetApp, backupPath, stagingPath };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function mockCommands(fixture, options = {}) {
|
|
256
|
-
const commands = { mv: "mock-mv", rm: "mock-rm", ditto: "mock-ditto", codesign: "mock-codesign", spctl: "mock-spctl" };
|
|
257
|
-
const calls = [];
|
|
258
|
-
const runCommand = async (command, args) => {
|
|
259
|
-
calls.push({ command, args: [...args] });
|
|
260
|
-
const pathArgs = args.filter((value) => !String(value).startsWith("-"));
|
|
261
|
-
if (!pathArgs.every((value) => path.resolve(value).startsWith(fixture.root)) && command !== commands.codesign && command !== commands.spctl) {
|
|
262
|
-
throw new Error(`test command escaped fixture: ${args.join(" ")}`);
|
|
263
|
-
}
|
|
264
|
-
if (command === commands.ditto) {
|
|
265
|
-
if (options.failDitto && args[0] === fixture.sourceApp) throw new Error("mock ditto failure");
|
|
266
|
-
fs.cpSync(args[0], args[1], { recursive: true, errorOnExist: true });
|
|
267
|
-
} else if (command === commands.mv) {
|
|
268
|
-
if (options.failRestore && args[0] === fixture.backupPath && args[1] === fixture.targetApp) throw new Error("mock restore failure");
|
|
269
|
-
fs.renameSync(args[0], args[1]);
|
|
270
|
-
} else if (command === commands.rm) {
|
|
271
|
-
fs.rmSync(args[1], { recursive: true, force: true });
|
|
272
|
-
}
|
|
273
|
-
return { code: 0, stdout: "", stderr: "" };
|
|
274
|
-
};
|
|
275
|
-
return { commands, calls, runCommand };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async function runReplacementCase(options = {}) {
|
|
279
|
-
const fixture = appFixture();
|
|
280
|
-
const mock = mockCommands(fixture, options);
|
|
281
|
-
const phases = [];
|
|
282
|
-
const verifyApp = async (appPath, context) => {
|
|
283
|
-
phases.push(context.phase);
|
|
284
|
-
assert.equal(fs.existsSync(path.join(appPath, "marker.txt")), true, `missing app marker during ${context.phase}`);
|
|
285
|
-
if (options.failPhase === context.phase) throw new Error(`mock ${context.phase} verification failure`);
|
|
286
|
-
return { identifier: "com.agentlas.desktop", teamIdentifier: options.teamByPhase?.[context.phase] || "AGENTLAS123" };
|
|
287
|
-
};
|
|
288
|
-
const promise = replaceMacAppBundle({ ...fixture, ...mock, verifyApp });
|
|
289
|
-
return { fixture, mock, phases, promise };
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
async function testReplacementTransaction() {
|
|
293
|
-
{
|
|
294
|
-
const run = await runReplacementCase();
|
|
295
|
-
try {
|
|
296
|
-
const result = await run.promise;
|
|
297
|
-
assert.equal(readMarker(run.fixture.targetApp), "new");
|
|
298
|
-
assert.equal(fs.existsSync(run.fixture.backupPath), false);
|
|
299
|
-
assert.equal(fs.existsSync(run.fixture.stagingPath), false);
|
|
300
|
-
assert.equal(result.backupRetained, false);
|
|
301
|
-
assert.deepEqual(run.phases, ["source", "original", "backup", "staging", "installed"]);
|
|
302
|
-
} finally {
|
|
303
|
-
fs.rmSync(run.fixture.root, { recursive: true, force: true });
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
for (const failure of [{ failDitto: true }, { failPhase: "staging" }, { failPhase: "installed" }, { teamByPhase: { staging: "EVIL123" } }]) {
|
|
308
|
-
const run = await runReplacementCase(failure);
|
|
309
|
-
try {
|
|
310
|
-
await assert.rejects(run.promise, (error) => error && error.code === "AGENTLAS_UPDATE_REPLACEMENT_FAILED_ROLLED_BACK");
|
|
311
|
-
assert.equal(readMarker(run.fixture.targetApp), "original", "the exact original app must be restored");
|
|
312
|
-
assert.equal(fs.existsSync(run.fixture.backupPath), false);
|
|
313
|
-
assert.equal(fs.existsSync(run.fixture.stagingPath), false);
|
|
314
|
-
assert.equal(run.phases.at(-1), "restored", "rollback must verify the restored app");
|
|
315
|
-
} finally {
|
|
316
|
-
fs.rmSync(run.fixture.root, { recursive: true, force: true });
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
{
|
|
321
|
-
const run = await runReplacementCase({ failDitto: true, failRestore: true });
|
|
322
|
-
try {
|
|
323
|
-
await assert.rejects(run.promise, (error) => {
|
|
324
|
-
assert.equal(error.code, "AGENTLAS_UPDATE_ROLLBACK_FAILED");
|
|
325
|
-
assert.equal(error.backupPath, run.fixture.backupPath);
|
|
326
|
-
return true;
|
|
327
|
-
});
|
|
328
|
-
assert.equal(readMarker(run.fixture.backupPath), "original", "failed rollback must retain the original backup");
|
|
329
|
-
} finally {
|
|
330
|
-
fs.rmSync(run.fixture.root, { recursive: true, force: true });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async function testCodeSigningChecks() {
|
|
336
|
-
const calls = [];
|
|
337
|
-
const identity = await verifyMacAppBundle("/tmp/fixture/Agentlas.app", {
|
|
338
|
-
commands: { codesign: "codesign", spctl: "spctl" },
|
|
339
|
-
runCommand: async (command, args, options) => {
|
|
340
|
-
calls.push({ command, args, options });
|
|
341
|
-
if (command === "codesign" && args[0] === "-d") {
|
|
342
|
-
return { code: 0, stdout: "", stderr: "Identifier=com.agentlas.desktop\nTeamIdentifier=AGENTLAS123\n" };
|
|
343
|
-
}
|
|
344
|
-
return { code: 0, stdout: "", stderr: "" };
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
assert.deepEqual(identity, { identifier: "com.agentlas.desktop", teamIdentifier: "AGENTLAS123" });
|
|
348
|
-
assert.deepEqual(calls, [
|
|
349
|
-
{ command: "codesign", args: ["--verify", "--deep", "--strict", "--verbose=2", "/tmp/fixture/Agentlas.app"], options: undefined },
|
|
350
|
-
{ command: "codesign", args: ["-d", "--verbose=4", "/tmp/fixture/Agentlas.app"], options: { capture: true } },
|
|
351
|
-
{ command: "spctl", args: ["-a", "-t", "exec", "-vv", "/tmp/fixture/Agentlas.app"], options: undefined },
|
|
352
|
-
]);
|
|
353
|
-
|
|
354
|
-
await assert.rejects(
|
|
355
|
-
verifyMacAppBundle("/tmp/fixture/Evil.app", {
|
|
356
|
-
commands: { codesign: "codesign", spctl: "spctl" },
|
|
357
|
-
runCommand: async (command, args) => command === "codesign" && args[0] === "-d"
|
|
358
|
-
? { code: 0, stdout: "", stderr: "Identifier=com.example.evil\nTeamIdentifier=EVIL123\n" }
|
|
359
|
-
: { code: 0, stdout: "", stderr: "" },
|
|
360
|
-
}),
|
|
361
|
-
(error) => error && error.code === "AGENTLAS_UPDATE_SIGNER_MISMATCH",
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
async function main() {
|
|
366
|
-
await testMetadataPolicy();
|
|
367
|
-
await testArtifactAndDownloadPolicy();
|
|
368
|
-
await testReplacementTransaction();
|
|
369
|
-
await testCodeSigningChecks();
|
|
370
|
-
console.log("update-safety: PASS");
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
main().catch((error) => {
|
|
374
|
-
console.error(error);
|
|
375
|
-
process.exitCode = 1;
|
|
376
|
-
});
|