fallow 2.80.0 → 2.82.0
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/bin/fallow +2 -47
- package/bin/fallow-lsp +2 -47
- package/bin/fallow-mcp +2 -47
- package/package.json +13 -11
- package/schema.json +7 -2
- package/scripts/lazy-verify.js +351 -0
- package/scripts/lazy-verify.test.js +573 -0
- package/scripts/platform-package.js +8 -8
- package/scripts/platform-package.test.js +17 -17
- package/scripts/run-binary.js +122 -0
- package/scripts/sentinel-path.js +165 -0
- package/scripts/sentinel-path.test.js +236 -0
- package/scripts/verify-binary.js +318 -130
- package/scripts/verify-binary.test.js +309 -156
- package/skills/fallow/references/cli-reference.md +1 -1
- package/types/output-contract.d.ts +22 -5
- package/scripts/postinstall.js +0 -63
|
@@ -1,45 +1,46 @@
|
|
|
1
|
-
const test = require(
|
|
2
|
-
const assert = require(
|
|
3
|
-
const crypto = require(
|
|
4
|
-
const fs = require(
|
|
5
|
-
const os = require(
|
|
6
|
-
const path = require(
|
|
1
|
+
const test = require("node:test");
|
|
2
|
+
const assert = require("node:assert/strict");
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const os = require("node:os");
|
|
6
|
+
const path = require("node:path");
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
_verifyWithKey,
|
|
10
10
|
verifyBinaryAt,
|
|
11
11
|
verifyDigestAt,
|
|
12
12
|
verifyInstalled,
|
|
13
|
+
verifyInstalledSync,
|
|
13
14
|
sha256Hex,
|
|
14
15
|
normalizeDigest,
|
|
15
16
|
readEmbeddedDigest,
|
|
16
17
|
EMBEDDED_PUBLIC_KEY,
|
|
17
18
|
ED25519_SPKI_HEADER,
|
|
18
19
|
SKIP_ENV,
|
|
19
|
-
} = require(
|
|
20
|
-
const { getPlatformPackage } = require(
|
|
20
|
+
} = require("./verify-binary");
|
|
21
|
+
const { getPlatformPackage } = require("./platform-package");
|
|
21
22
|
|
|
22
|
-
function makeDigestProvider(
|
|
23
|
-
return ({
|
|
23
|
+
function makeDigestProvider(_dir) {
|
|
24
|
+
return ({ binaryPath }) => {
|
|
24
25
|
const data = fs.readFileSync(binaryPath);
|
|
25
|
-
return Promise.resolve(
|
|
26
|
+
return Promise.resolve("sha256:" + crypto.createHash("sha256").update(data).digest("hex"));
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function makeMismatchedDigestProvider() {
|
|
30
|
-
return () => Promise.resolve(
|
|
31
|
+
return () => Promise.resolve("sha256:" + "a".repeat(64));
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function makeKeypair() {
|
|
34
|
-
const { privateKey, publicKey } = crypto.generateKeyPairSync(
|
|
35
|
-
const spki = publicKey.export({ format:
|
|
35
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
|
|
36
|
+
const spki = publicKey.export({ format: "der", type: "spki" });
|
|
36
37
|
const rawPub = spki.subarray(spki.length - 32);
|
|
37
38
|
return { privateKey, rawPub };
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function makeFixture(binaryBytes, signFn) {
|
|
41
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
42
|
-
const binaryPath = path.join(dir,
|
|
42
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-vbtest-"));
|
|
43
|
+
const binaryPath = path.join(dir, "fallow");
|
|
43
44
|
fs.writeFileSync(binaryPath, binaryBytes);
|
|
44
45
|
if (signFn) {
|
|
45
46
|
fs.writeFileSync(`${binaryPath}.sig`, signFn(binaryBytes));
|
|
@@ -51,23 +52,21 @@ function cleanup(dir) {
|
|
|
51
52
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
test(
|
|
55
|
+
test("embedded public key is 32 bytes and SPKI header is 12 bytes", () => {
|
|
55
56
|
assert.equal(EMBEDDED_PUBLIC_KEY.length, 32);
|
|
56
57
|
assert.equal(ED25519_SPKI_HEADER.length, 12);
|
|
57
58
|
});
|
|
58
59
|
|
|
59
|
-
test(
|
|
60
|
+
test("embedded public key reconstructs a valid Ed25519 SPKI key", () => {
|
|
60
61
|
const spki = Buffer.concat([ED25519_SPKI_HEADER, EMBEDDED_PUBLIC_KEY]);
|
|
61
|
-
const key = crypto.createPublicKey({ key: spki, format:
|
|
62
|
-
assert.equal(key.asymmetricKeyType,
|
|
62
|
+
const key = crypto.createPublicKey({ key: spki, format: "der", type: "spki" });
|
|
63
|
+
assert.equal(key.asymmetricKeyType, "ed25519");
|
|
63
64
|
});
|
|
64
65
|
|
|
65
|
-
test(
|
|
66
|
+
test("_verifyWithKey returns ok for a valid signature", () => {
|
|
66
67
|
const { privateKey, rawPub } = makeKeypair();
|
|
67
|
-
const content = Buffer.from(
|
|
68
|
-
const { dir, binaryPath } = makeFixture(content, (data) =>
|
|
69
|
-
crypto.sign(null, data, privateKey),
|
|
70
|
-
);
|
|
68
|
+
const content = Buffer.from("hello world");
|
|
69
|
+
const { dir, binaryPath } = makeFixture(content, (data) => crypto.sign(null, data, privateKey));
|
|
71
70
|
try {
|
|
72
71
|
const result = _verifyWithKey(binaryPath, rawPub);
|
|
73
72
|
assert.deepEqual(result, { ok: true });
|
|
@@ -76,119 +75,113 @@ test('_verifyWithKey returns ok for a valid signature', () => {
|
|
|
76
75
|
}
|
|
77
76
|
});
|
|
78
77
|
|
|
79
|
-
test(
|
|
78
|
+
test("_verifyWithKey returns sig-invalid when the signature is corrupted", () => {
|
|
80
79
|
const { privateKey, rawPub } = makeKeypair();
|
|
81
|
-
const content = Buffer.from(
|
|
82
|
-
const { dir, binaryPath } = makeFixture(content, (data) =>
|
|
83
|
-
crypto.sign(null, data, privateKey),
|
|
84
|
-
);
|
|
80
|
+
const content = Buffer.from("hello world");
|
|
81
|
+
const { dir, binaryPath } = makeFixture(content, (data) => crypto.sign(null, data, privateKey));
|
|
85
82
|
try {
|
|
86
83
|
const sig = fs.readFileSync(`${binaryPath}.sig`);
|
|
87
84
|
sig[0] ^= 0xff;
|
|
88
85
|
fs.writeFileSync(`${binaryPath}.sig`, sig);
|
|
89
86
|
const result = _verifyWithKey(binaryPath, rawPub);
|
|
90
87
|
assert.equal(result.ok, false);
|
|
91
|
-
assert.equal(result.code,
|
|
88
|
+
assert.equal(result.code, "sig-invalid");
|
|
92
89
|
} finally {
|
|
93
90
|
cleanup(dir);
|
|
94
91
|
}
|
|
95
92
|
});
|
|
96
93
|
|
|
97
|
-
test(
|
|
94
|
+
test("_verifyWithKey returns sig-invalid for the wrong key", () => {
|
|
98
95
|
const { rawPub } = makeKeypair();
|
|
99
96
|
const wrongKey = makeKeypair();
|
|
100
|
-
const content = Buffer.from(
|
|
97
|
+
const content = Buffer.from("hello world");
|
|
101
98
|
const { dir, binaryPath } = makeFixture(content, (data) =>
|
|
102
99
|
crypto.sign(null, data, wrongKey.privateKey),
|
|
103
100
|
);
|
|
104
101
|
try {
|
|
105
102
|
const result = _verifyWithKey(binaryPath, rawPub);
|
|
106
103
|
assert.equal(result.ok, false);
|
|
107
|
-
assert.equal(result.code,
|
|
104
|
+
assert.equal(result.code, "sig-invalid");
|
|
108
105
|
} finally {
|
|
109
106
|
cleanup(dir);
|
|
110
107
|
}
|
|
111
108
|
});
|
|
112
109
|
|
|
113
|
-
test(
|
|
110
|
+
test("_verifyWithKey returns sig-invalid when the binary bytes are tampered", () => {
|
|
114
111
|
const { privateKey, rawPub } = makeKeypair();
|
|
115
|
-
const original = Buffer.from(
|
|
116
|
-
const { dir, binaryPath } = makeFixture(original, (data) =>
|
|
117
|
-
crypto.sign(null, data, privateKey),
|
|
118
|
-
);
|
|
112
|
+
const original = Buffer.from("hello world");
|
|
113
|
+
const { dir, binaryPath } = makeFixture(original, (data) => crypto.sign(null, data, privateKey));
|
|
119
114
|
try {
|
|
120
|
-
fs.writeFileSync(binaryPath, Buffer.from(
|
|
115
|
+
fs.writeFileSync(binaryPath, Buffer.from("tampered"));
|
|
121
116
|
const result = _verifyWithKey(binaryPath, rawPub);
|
|
122
117
|
assert.equal(result.ok, false);
|
|
123
|
-
assert.equal(result.code,
|
|
118
|
+
assert.equal(result.code, "sig-invalid");
|
|
124
119
|
} finally {
|
|
125
120
|
cleanup(dir);
|
|
126
121
|
}
|
|
127
122
|
});
|
|
128
123
|
|
|
129
|
-
test(
|
|
124
|
+
test("_verifyWithKey returns sig-missing when the signature file does not exist", () => {
|
|
130
125
|
const { rawPub } = makeKeypair();
|
|
131
|
-
const { dir, binaryPath } = makeFixture(Buffer.from(
|
|
126
|
+
const { dir, binaryPath } = makeFixture(Buffer.from("hello world"));
|
|
132
127
|
try {
|
|
133
128
|
const result = _verifyWithKey(binaryPath, rawPub);
|
|
134
129
|
assert.equal(result.ok, false);
|
|
135
|
-
assert.equal(result.code,
|
|
130
|
+
assert.equal(result.code, "sig-missing");
|
|
136
131
|
} finally {
|
|
137
132
|
cleanup(dir);
|
|
138
133
|
}
|
|
139
134
|
});
|
|
140
135
|
|
|
141
|
-
test(
|
|
136
|
+
test("_verifyWithKey returns binary-missing when the binary does not exist", () => {
|
|
142
137
|
const { rawPub } = makeKeypair();
|
|
143
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
138
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-vbtest-"));
|
|
144
139
|
try {
|
|
145
|
-
const result = _verifyWithKey(path.join(dir,
|
|
140
|
+
const result = _verifyWithKey(path.join(dir, "nonexistent"), rawPub);
|
|
146
141
|
assert.equal(result.ok, false);
|
|
147
|
-
assert.equal(result.code,
|
|
142
|
+
assert.equal(result.code, "binary-missing");
|
|
148
143
|
} finally {
|
|
149
144
|
cleanup(dir);
|
|
150
145
|
}
|
|
151
146
|
});
|
|
152
147
|
|
|
153
|
-
test(
|
|
148
|
+
test("_verifyWithKey returns sig-invalid when the signature length is wrong", () => {
|
|
154
149
|
const { rawPub } = makeKeypair();
|
|
155
|
-
const { dir, binaryPath } = makeFixture(Buffer.from(
|
|
150
|
+
const { dir, binaryPath } = makeFixture(Buffer.from("hello"));
|
|
156
151
|
try {
|
|
157
|
-
fs.writeFileSync(`${binaryPath}.sig`, Buffer.from(
|
|
152
|
+
fs.writeFileSync(`${binaryPath}.sig`, Buffer.from("short"));
|
|
158
153
|
const result = _verifyWithKey(binaryPath, rawPub);
|
|
159
154
|
assert.equal(result.ok, false);
|
|
160
|
-
assert.equal(result.code,
|
|
155
|
+
assert.equal(result.code, "sig-invalid");
|
|
161
156
|
} finally {
|
|
162
157
|
cleanup(dir);
|
|
163
158
|
}
|
|
164
159
|
});
|
|
165
160
|
|
|
166
|
-
test(
|
|
167
|
-
const { dir, binaryPath } = makeFixture(Buffer.from(
|
|
161
|
+
test("_verifyWithKey throws when given a non-32-byte raw public key", () => {
|
|
162
|
+
const { dir, binaryPath } = makeFixture(Buffer.from("hello world"));
|
|
168
163
|
try {
|
|
169
|
-
const result = _verifyWithKey(binaryPath, Buffer.from(
|
|
164
|
+
const result = _verifyWithKey(binaryPath, Buffer.from("too short"));
|
|
170
165
|
assert.equal(result.ok, false);
|
|
171
|
-
assert.equal(result.code,
|
|
166
|
+
assert.equal(result.code, "sig-missing");
|
|
172
167
|
} finally {
|
|
173
168
|
cleanup(dir);
|
|
174
169
|
}
|
|
175
170
|
});
|
|
176
171
|
|
|
177
|
-
test(
|
|
172
|
+
test("verifyBinaryAt uses the embedded production public key", () => {
|
|
178
173
|
// The embedded key cannot sign our test data because we do not have the
|
|
179
174
|
// private key, so we only assert that verifyBinaryAt returns sig-invalid
|
|
180
175
|
// for a random signature against the production key, not the underlying
|
|
181
176
|
// crypto throwing. This locks in that the public API uses the embedded
|
|
182
177
|
// key path.
|
|
183
178
|
const { privateKey } = makeKeypair();
|
|
184
|
-
const content = Buffer.from(
|
|
185
|
-
const { dir, binaryPath } = makeFixture(content, (data) =>
|
|
186
|
-
crypto.sign(null, data, privateKey),
|
|
187
|
-
);
|
|
179
|
+
const content = Buffer.from("hello world");
|
|
180
|
+
const { dir, binaryPath } = makeFixture(content, (data) => crypto.sign(null, data, privateKey));
|
|
188
181
|
try {
|
|
189
182
|
const result = verifyBinaryAt(binaryPath);
|
|
190
183
|
assert.equal(result.ok, false);
|
|
191
|
-
assert.equal(result.code,
|
|
184
|
+
assert.equal(result.code, "sig-invalid");
|
|
192
185
|
} finally {
|
|
193
186
|
cleanup(dir);
|
|
194
187
|
}
|
|
@@ -196,16 +189,16 @@ test('verifyBinaryAt uses the embedded production public key', () => {
|
|
|
196
189
|
|
|
197
190
|
function makePlatformDir(privateKey, options) {
|
|
198
191
|
const opts = options || {};
|
|
199
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
200
|
-
const ext = process.platform ===
|
|
201
|
-
for (const base of [
|
|
192
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-vbtest-"));
|
|
193
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
194
|
+
for (const base of ["fallow", "fallow-lsp", "fallow-mcp"]) {
|
|
202
195
|
const binaryPath = path.join(dir, `${base}${ext}`);
|
|
203
196
|
const content = Buffer.from(`mock ${base} contents`);
|
|
204
197
|
fs.writeFileSync(binaryPath, content);
|
|
205
198
|
if (opts.skipSigFor === base) {
|
|
206
199
|
continue;
|
|
207
200
|
}
|
|
208
|
-
const data = opts.corruptBinaryFor === base ? Buffer.from(
|
|
201
|
+
const data = opts.corruptBinaryFor === base ? Buffer.from("tampered") : content;
|
|
209
202
|
const sig = crypto.sign(null, data, privateKey);
|
|
210
203
|
if (opts.corruptSigFor === base) {
|
|
211
204
|
sig[0] ^= 0xff;
|
|
@@ -216,58 +209,61 @@ function makePlatformDir(privateKey, options) {
|
|
|
216
209
|
}
|
|
217
210
|
|
|
218
211
|
function currentPlatformPackage() {
|
|
219
|
-
if (process.platform !==
|
|
212
|
+
if (process.platform !== "linux") {
|
|
220
213
|
return getPlatformPackage(process.platform, process.arch);
|
|
221
214
|
}
|
|
222
215
|
let libcFamily;
|
|
223
216
|
try {
|
|
224
|
-
libcFamily = require(
|
|
217
|
+
libcFamily = require("detect-libc").familySync();
|
|
225
218
|
} catch {
|
|
226
219
|
libcFamily = undefined;
|
|
227
220
|
}
|
|
228
221
|
return getPlatformPackage(process.platform, process.arch, libcFamily);
|
|
229
222
|
}
|
|
230
223
|
|
|
231
|
-
test(
|
|
232
|
-
const sample =
|
|
233
|
-
assert.equal(normalizeDigest(
|
|
224
|
+
test("normalizeDigest accepts sha256: prefix and bare hex", () => {
|
|
225
|
+
const sample = "a".repeat(64);
|
|
226
|
+
assert.equal(normalizeDigest("sha256:" + sample), sample);
|
|
234
227
|
assert.equal(normalizeDigest(sample), sample);
|
|
235
|
-
assert.equal(normalizeDigest(
|
|
228
|
+
assert.equal(normalizeDigest("SHA256:" + sample.toUpperCase()), sample);
|
|
236
229
|
});
|
|
237
230
|
|
|
238
|
-
test(
|
|
231
|
+
test("normalizeDigest rejects malformed digests", () => {
|
|
239
232
|
assert.equal(normalizeDigest(null), null);
|
|
240
|
-
assert.equal(normalizeDigest(
|
|
241
|
-
assert.equal(normalizeDigest(
|
|
242
|
-
assert.equal(normalizeDigest(
|
|
233
|
+
assert.equal(normalizeDigest(""), null);
|
|
234
|
+
assert.equal(normalizeDigest("not-hex"), null);
|
|
235
|
+
assert.equal(normalizeDigest("a".repeat(63)), null);
|
|
243
236
|
});
|
|
244
237
|
|
|
245
|
-
test(
|
|
246
|
-
const { dir, binaryPath } = makeFixture(Buffer.from(
|
|
238
|
+
test("sha256Hex returns 64-char hex over file bytes", () => {
|
|
239
|
+
const { dir, binaryPath } = makeFixture(Buffer.from("hello world"));
|
|
247
240
|
try {
|
|
248
241
|
const result = sha256Hex(binaryPath);
|
|
249
242
|
assert.equal(result.ok, true);
|
|
250
|
-
assert.equal(
|
|
243
|
+
assert.equal(
|
|
244
|
+
result.digest,
|
|
245
|
+
crypto.createHash("sha256").update(Buffer.from("hello world")).digest("hex"),
|
|
246
|
+
);
|
|
251
247
|
} finally {
|
|
252
248
|
cleanup(dir);
|
|
253
249
|
}
|
|
254
250
|
});
|
|
255
251
|
|
|
256
|
-
test(
|
|
257
|
-
const { dir, binaryPath } = makeFixture(Buffer.from(
|
|
252
|
+
test("verifyDigestAt accepts matching digest and rejects mismatched", () => {
|
|
253
|
+
const { dir, binaryPath } = makeFixture(Buffer.from("hello world"));
|
|
258
254
|
try {
|
|
259
|
-
const correct = crypto.createHash(
|
|
260
|
-
assert.deepEqual(verifyDigestAt(binaryPath,
|
|
261
|
-
const wrong =
|
|
255
|
+
const correct = crypto.createHash("sha256").update(Buffer.from("hello world")).digest("hex");
|
|
256
|
+
assert.deepEqual(verifyDigestAt(binaryPath, "sha256:" + correct), { ok: true });
|
|
257
|
+
const wrong = "b".repeat(64);
|
|
262
258
|
const bad = verifyDigestAt(binaryPath, wrong);
|
|
263
259
|
assert.equal(bad.ok, false);
|
|
264
|
-
assert.equal(bad.code,
|
|
260
|
+
assert.equal(bad.code, "digest-mismatch");
|
|
265
261
|
} finally {
|
|
266
262
|
cleanup(dir);
|
|
267
263
|
}
|
|
268
264
|
});
|
|
269
265
|
|
|
270
|
-
test(
|
|
266
|
+
test("verifyInstalled with dirOverride returns ok when every binary verifies", async (t) => {
|
|
271
267
|
const { privateKey, rawPub } = makeKeypair();
|
|
272
268
|
const dir = makePlatformDir(privateKey);
|
|
273
269
|
t.after(() => cleanup(dir));
|
|
@@ -277,28 +273,31 @@ test('verifyInstalled with dirOverride returns ok when every binary verifies', a
|
|
|
277
273
|
digestProvider: makeDigestProvider(dir),
|
|
278
274
|
});
|
|
279
275
|
assert.equal(result.ok, true);
|
|
280
|
-
assert.equal(result.package,
|
|
276
|
+
assert.equal(result.package, "<override>");
|
|
281
277
|
});
|
|
282
278
|
|
|
283
|
-
test(
|
|
279
|
+
test("verifyInstalled resolves a global npm install from the fallow package directory", async (t) => {
|
|
284
280
|
const pkg = currentPlatformPackage();
|
|
285
281
|
if (!pkg) {
|
|
286
|
-
t.skip(
|
|
282
|
+
t.skip("unsupported platform");
|
|
287
283
|
return;
|
|
288
284
|
}
|
|
289
285
|
|
|
290
286
|
const { privateKey, rawPub } = makeKeypair();
|
|
291
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
287
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-vbtest-global-"));
|
|
292
288
|
t.after(() => cleanup(root));
|
|
293
289
|
|
|
294
|
-
const resolveFrom = path.join(root,
|
|
295
|
-
const platformDir = path.join(root,
|
|
290
|
+
const resolveFrom = path.join(root, "node_modules", "fallow");
|
|
291
|
+
const platformDir = path.join(root, "node_modules", ...pkg.split("/"));
|
|
296
292
|
fs.mkdirSync(resolveFrom, { recursive: true });
|
|
297
293
|
fs.mkdirSync(platformDir, { recursive: true });
|
|
298
|
-
fs.writeFileSync(
|
|
294
|
+
fs.writeFileSync(
|
|
295
|
+
path.join(platformDir, "package.json"),
|
|
296
|
+
JSON.stringify({ name: pkg, version: "9.9.9" }),
|
|
297
|
+
);
|
|
299
298
|
|
|
300
|
-
const ext = process.platform ===
|
|
301
|
-
for (const base of [
|
|
299
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
300
|
+
for (const base of ["fallow", "fallow-lsp", "fallow-mcp"]) {
|
|
302
301
|
const binaryPath = path.join(platformDir, `${base}${ext}`);
|
|
303
302
|
const content = Buffer.from(`global install ${base}`);
|
|
304
303
|
fs.writeFileSync(binaryPath, content);
|
|
@@ -308,16 +307,17 @@ test('verifyInstalled resolves a global npm install from the fallow package dire
|
|
|
308
307
|
const result = await verifyInstalled({
|
|
309
308
|
resolveFrom,
|
|
310
309
|
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
311
|
-
digestProvider: ({ binaryPath }) =>
|
|
310
|
+
digestProvider: ({ binaryPath }) =>
|
|
311
|
+
crypto.createHash("sha256").update(fs.readFileSync(binaryPath)).digest("hex"),
|
|
312
312
|
});
|
|
313
313
|
assert.equal(result.ok, true);
|
|
314
314
|
assert.equal(result.package, pkg);
|
|
315
|
-
assert.equal(result.version,
|
|
315
|
+
assert.equal(result.version, "9.9.9");
|
|
316
316
|
});
|
|
317
317
|
|
|
318
|
-
test(
|
|
318
|
+
test("verifyInstalled with dirOverride fails fast on the first bad signature", async (t) => {
|
|
319
319
|
const { privateKey, rawPub } = makeKeypair();
|
|
320
|
-
const dir = makePlatformDir(privateKey, { corruptSigFor:
|
|
320
|
+
const dir = makePlatformDir(privateKey, { corruptSigFor: "fallow-lsp" });
|
|
321
321
|
t.after(() => cleanup(dir));
|
|
322
322
|
const result = await verifyInstalled({
|
|
323
323
|
dirOverride: dir,
|
|
@@ -325,13 +325,13 @@ test('verifyInstalled with dirOverride fails fast on the first bad signature', a
|
|
|
325
325
|
digestProvider: makeDigestProvider(dir),
|
|
326
326
|
});
|
|
327
327
|
assert.equal(result.ok, false);
|
|
328
|
-
assert.equal(result.code,
|
|
328
|
+
assert.equal(result.code, "sig-invalid");
|
|
329
329
|
assert.match(result.binary, /fallow-lsp/);
|
|
330
330
|
});
|
|
331
331
|
|
|
332
|
-
test(
|
|
332
|
+
test("verifyInstalled with dirOverride reports sig-missing when a .sig is absent", async (t) => {
|
|
333
333
|
const { privateKey, rawPub } = makeKeypair();
|
|
334
|
-
const dir = makePlatformDir(privateKey, { skipSigFor:
|
|
334
|
+
const dir = makePlatformDir(privateKey, { skipSigFor: "fallow-mcp" });
|
|
335
335
|
t.after(() => cleanup(dir));
|
|
336
336
|
const result = await verifyInstalled({
|
|
337
337
|
dirOverride: dir,
|
|
@@ -339,11 +339,11 @@ test('verifyInstalled with dirOverride reports sig-missing when a .sig is absent
|
|
|
339
339
|
digestProvider: makeDigestProvider(dir),
|
|
340
340
|
});
|
|
341
341
|
assert.equal(result.ok, false);
|
|
342
|
-
assert.equal(result.code,
|
|
342
|
+
assert.equal(result.code, "sig-missing");
|
|
343
343
|
assert.match(result.binary, /fallow-mcp/);
|
|
344
344
|
});
|
|
345
345
|
|
|
346
|
-
test(
|
|
346
|
+
test("verifyInstalled reports digest-mismatch when SHA-256 disagrees with the provider", async (t) => {
|
|
347
347
|
const { privateKey, rawPub } = makeKeypair();
|
|
348
348
|
const dir = makePlatformDir(privateKey);
|
|
349
349
|
t.after(() => cleanup(dir));
|
|
@@ -353,91 +353,92 @@ test('verifyInstalled reports digest-mismatch when SHA-256 disagrees with the pr
|
|
|
353
353
|
digestProvider: makeMismatchedDigestProvider(),
|
|
354
354
|
});
|
|
355
355
|
assert.equal(result.ok, false);
|
|
356
|
-
assert.equal(result.code,
|
|
356
|
+
assert.equal(result.code, "digest-mismatch");
|
|
357
357
|
});
|
|
358
358
|
|
|
359
|
-
test(
|
|
359
|
+
test("verifyInstalled reports digest-unavailable when the provider rejects", async (t) => {
|
|
360
360
|
const { privateKey, rawPub } = makeKeypair();
|
|
361
361
|
const dir = makePlatformDir(privateKey);
|
|
362
362
|
t.after(() => cleanup(dir));
|
|
363
363
|
const result = await verifyInstalled({
|
|
364
364
|
dirOverride: dir,
|
|
365
365
|
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
366
|
-
digestProvider: () => Promise.reject(new Error(
|
|
366
|
+
digestProvider: () => Promise.reject(new Error("network down")),
|
|
367
367
|
});
|
|
368
368
|
assert.equal(result.ok, false);
|
|
369
|
-
assert.equal(result.code,
|
|
369
|
+
assert.equal(result.code, "digest-unavailable");
|
|
370
370
|
assert.match(result.message, /network down/);
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
-
test(
|
|
373
|
+
test("verifyInstalled honors FALLOW_SKIP_BINARY_VERIFY", async (t) => {
|
|
374
374
|
const previous = process.env[SKIP_ENV];
|
|
375
|
-
process.env[SKIP_ENV] =
|
|
375
|
+
process.env[SKIP_ENV] = "1";
|
|
376
376
|
t.after(() => {
|
|
377
377
|
if (previous === undefined) delete process.env[SKIP_ENV];
|
|
378
378
|
else process.env[SKIP_ENV] = previous;
|
|
379
379
|
});
|
|
380
|
-
const result = await verifyInstalled({ dirOverride:
|
|
380
|
+
const result = await verifyInstalled({ dirOverride: "/does/not/exist" });
|
|
381
381
|
assert.equal(result.ok, true);
|
|
382
382
|
assert.equal(result.skipped, true);
|
|
383
383
|
});
|
|
384
384
|
|
|
385
385
|
function computeDigestsForDir(dir) {
|
|
386
|
-
const ext = process.platform ===
|
|
386
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
387
387
|
const out = {};
|
|
388
|
-
for (const base of [
|
|
388
|
+
for (const base of ["fallow", "fallow-lsp", "fallow-mcp"]) {
|
|
389
389
|
const fileName = `${base}${ext}`;
|
|
390
390
|
const full = path.join(dir, fileName);
|
|
391
|
-
out[fileName] =
|
|
391
|
+
out[fileName] =
|
|
392
|
+
"sha256:" + crypto.createHash("sha256").update(fs.readFileSync(full)).digest("hex");
|
|
392
393
|
}
|
|
393
394
|
return out;
|
|
394
395
|
}
|
|
395
396
|
|
|
396
397
|
function writeManifest(dir, body) {
|
|
397
|
-
fs.writeFileSync(path.join(dir,
|
|
398
|
+
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify(body));
|
|
398
399
|
}
|
|
399
400
|
|
|
400
|
-
test(
|
|
401
|
-
assert.equal(readEmbeddedDigest(
|
|
401
|
+
test("readEmbeddedDigest returns null when manifest is missing", () => {
|
|
402
|
+
assert.equal(readEmbeddedDigest("/does/not/exist/package.json", "fallow"), null);
|
|
402
403
|
});
|
|
403
404
|
|
|
404
|
-
test(
|
|
405
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
405
|
+
test("readEmbeddedDigest returns null when fallowDigests field is absent", (t) => {
|
|
406
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-vbtest-emb-"));
|
|
406
407
|
t.after(() => cleanup(dir));
|
|
407
|
-
writeManifest(dir, { name:
|
|
408
|
-
assert.equal(readEmbeddedDigest(path.join(dir,
|
|
408
|
+
writeManifest(dir, { name: "@fallow-cli/x", version: "1.0.0" });
|
|
409
|
+
assert.equal(readEmbeddedDigest(path.join(dir, "package.json"), "fallow"), null);
|
|
409
410
|
});
|
|
410
411
|
|
|
411
|
-
test(
|
|
412
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
412
|
+
test("readEmbeddedDigest returns null when the per-binary entry is malformed", (t) => {
|
|
413
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-vbtest-emb-"));
|
|
413
414
|
t.after(() => cleanup(dir));
|
|
414
415
|
writeManifest(dir, {
|
|
415
|
-
name:
|
|
416
|
-
version:
|
|
417
|
-
fallowDigests: { fallow:
|
|
416
|
+
name: "@fallow-cli/x",
|
|
417
|
+
version: "1.0.0",
|
|
418
|
+
fallowDigests: { fallow: "not-a-real-digest" },
|
|
418
419
|
});
|
|
419
|
-
assert.equal(readEmbeddedDigest(path.join(dir,
|
|
420
|
+
assert.equal(readEmbeddedDigest(path.join(dir, "package.json"), "fallow"), null);
|
|
420
421
|
});
|
|
421
422
|
|
|
422
|
-
test(
|
|
423
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
423
|
+
test("readEmbeddedDigest returns normalized hex for a valid sha256: prefixed entry", (t) => {
|
|
424
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-vbtest-emb-"));
|
|
424
425
|
t.after(() => cleanup(dir));
|
|
425
|
-
const hex =
|
|
426
|
+
const hex = "a".repeat(64);
|
|
426
427
|
writeManifest(dir, {
|
|
427
|
-
name:
|
|
428
|
-
version:
|
|
429
|
-
fallowDigests: { fallow:
|
|
428
|
+
name: "@fallow-cli/x",
|
|
429
|
+
version: "1.0.0",
|
|
430
|
+
fallowDigests: { fallow: "sha256:" + hex },
|
|
430
431
|
});
|
|
431
|
-
assert.equal(readEmbeddedDigest(path.join(dir,
|
|
432
|
+
assert.equal(readEmbeddedDigest(path.join(dir, "package.json"), "fallow"), hex);
|
|
432
433
|
});
|
|
433
434
|
|
|
434
|
-
test(
|
|
435
|
+
test("verifyInstalled uses the embedded digest without calling the provider", async (t) => {
|
|
435
436
|
const { privateKey, rawPub } = makeKeypair();
|
|
436
437
|
const dir = makePlatformDir(privateKey);
|
|
437
438
|
t.after(() => cleanup(dir));
|
|
438
439
|
writeManifest(dir, {
|
|
439
|
-
name:
|
|
440
|
-
version:
|
|
440
|
+
name: "@fallow-cli/x",
|
|
441
|
+
version: "1.0.0",
|
|
441
442
|
fallowDigests: computeDigestsForDir(dir),
|
|
442
443
|
});
|
|
443
444
|
let providerCalls = 0;
|
|
@@ -446,14 +447,14 @@ test('verifyInstalled uses the embedded digest without calling the provider', as
|
|
|
446
447
|
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
447
448
|
digestProvider: () => {
|
|
448
449
|
providerCalls += 1;
|
|
449
|
-
return Promise.reject(new Error(
|
|
450
|
+
return Promise.reject(new Error("provider should not be called"));
|
|
450
451
|
},
|
|
451
452
|
});
|
|
452
453
|
assert.equal(result.ok, true, JSON.stringify(result));
|
|
453
454
|
assert.equal(providerCalls, 0);
|
|
454
455
|
});
|
|
455
456
|
|
|
456
|
-
test(
|
|
457
|
+
test("verifyInstalled falls back to the provider when the embedded digest is missing", async (t) => {
|
|
457
458
|
const { privateKey, rawPub } = makeKeypair();
|
|
458
459
|
const dir = makePlatformDir(privateKey);
|
|
459
460
|
t.after(() => cleanup(dir));
|
|
@@ -464,21 +465,23 @@ test('verifyInstalled falls back to the provider when the embedded digest is mis
|
|
|
464
465
|
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
465
466
|
digestProvider: ({ binaryPath }) => {
|
|
466
467
|
providerCalls += 1;
|
|
467
|
-
return Promise.resolve(
|
|
468
|
+
return Promise.resolve(
|
|
469
|
+
"sha256:" + crypto.createHash("sha256").update(fs.readFileSync(binaryPath)).digest("hex"),
|
|
470
|
+
);
|
|
468
471
|
},
|
|
469
472
|
});
|
|
470
473
|
assert.equal(result.ok, true);
|
|
471
474
|
assert.equal(providerCalls, 3);
|
|
472
475
|
});
|
|
473
476
|
|
|
474
|
-
test(
|
|
477
|
+
test("verifyInstalled falls back to the provider when fallowDigests is partial / malformed", async (t) => {
|
|
475
478
|
const { privateKey, rawPub } = makeKeypair();
|
|
476
479
|
const dir = makePlatformDir(privateKey);
|
|
477
480
|
t.after(() => cleanup(dir));
|
|
478
481
|
writeManifest(dir, {
|
|
479
|
-
name:
|
|
480
|
-
version:
|
|
481
|
-
fallowDigests: { fallow:
|
|
482
|
+
name: "@fallow-cli/x",
|
|
483
|
+
version: "1.0.0",
|
|
484
|
+
fallowDigests: { fallow: "not-a-real-digest" },
|
|
482
485
|
});
|
|
483
486
|
let providerCalls = 0;
|
|
484
487
|
const result = await verifyInstalled({
|
|
@@ -486,7 +489,9 @@ test('verifyInstalled falls back to the provider when fallowDigests is partial /
|
|
|
486
489
|
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
487
490
|
digestProvider: ({ binaryPath }) => {
|
|
488
491
|
providerCalls += 1;
|
|
489
|
-
return Promise.resolve(
|
|
492
|
+
return Promise.resolve(
|
|
493
|
+
"sha256:" + crypto.createHash("sha256").update(fs.readFileSync(binaryPath)).digest("hex"),
|
|
494
|
+
);
|
|
490
495
|
},
|
|
491
496
|
});
|
|
492
497
|
assert.equal(result.ok, true);
|
|
@@ -494,32 +499,32 @@ test('verifyInstalled falls back to the provider when fallowDigests is partial /
|
|
|
494
499
|
assert.equal(providerCalls, 3);
|
|
495
500
|
});
|
|
496
501
|
|
|
497
|
-
test(
|
|
502
|
+
test("verifyInstalled returns digest-mismatch when the embedded digest disagrees with the binary", async (t) => {
|
|
498
503
|
const { privateKey, rawPub } = makeKeypair();
|
|
499
504
|
const dir = makePlatformDir(privateKey);
|
|
500
505
|
t.after(() => cleanup(dir));
|
|
501
|
-
const ext = process.platform ===
|
|
506
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
502
507
|
writeManifest(dir, {
|
|
503
|
-
name:
|
|
504
|
-
version:
|
|
508
|
+
name: "@fallow-cli/x",
|
|
509
|
+
version: "1.0.0",
|
|
505
510
|
fallowDigests: {
|
|
506
|
-
[`fallow${ext}`]:
|
|
507
|
-
[`fallow-lsp${ext}`]:
|
|
508
|
-
[`fallow-mcp${ext}`]:
|
|
511
|
+
[`fallow${ext}`]: "sha256:" + "a".repeat(64),
|
|
512
|
+
[`fallow-lsp${ext}`]: "sha256:" + "a".repeat(64),
|
|
513
|
+
[`fallow-mcp${ext}`]: "sha256:" + "a".repeat(64),
|
|
509
514
|
},
|
|
510
515
|
});
|
|
511
516
|
const result = await verifyInstalled({
|
|
512
517
|
dirOverride: dir,
|
|
513
518
|
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
514
|
-
digestProvider: () => Promise.reject(new Error(
|
|
519
|
+
digestProvider: () => Promise.reject(new Error("should not be reached")),
|
|
515
520
|
});
|
|
516
521
|
assert.equal(result.ok, false);
|
|
517
|
-
assert.equal(result.code,
|
|
522
|
+
assert.equal(result.code, "digest-mismatch");
|
|
518
523
|
});
|
|
519
524
|
|
|
520
|
-
test(
|
|
525
|
+
test("verifyInstalled ignores skip env when allowSkipEnv is false", async (t) => {
|
|
521
526
|
const previous = process.env[SKIP_ENV];
|
|
522
|
-
process.env[SKIP_ENV] =
|
|
527
|
+
process.env[SKIP_ENV] = "1";
|
|
523
528
|
t.after(() => {
|
|
524
529
|
if (previous === undefined) delete process.env[SKIP_ENV];
|
|
525
530
|
else process.env[SKIP_ENV] = previous;
|
|
@@ -536,3 +541,151 @@ test('verifyInstalled ignores skip env when allowSkipEnv is false', async (t) =>
|
|
|
536
541
|
assert.equal(result.ok, true);
|
|
537
542
|
assert.notEqual(result.skipped, true);
|
|
538
543
|
});
|
|
544
|
+
|
|
545
|
+
// ---- verifyInstalledSync (lazy first-run path) ----------------------------
|
|
546
|
+
|
|
547
|
+
function makeSyncDigestProvider(_dir) {
|
|
548
|
+
return ({ binaryPath }) =>
|
|
549
|
+
"sha256:" + crypto.createHash("sha256").update(fs.readFileSync(binaryPath)).digest("hex");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
test("verifyInstalledSync with embedded digests returns ok end-to-end", (t) => {
|
|
553
|
+
const { privateKey, rawPub } = makeKeypair();
|
|
554
|
+
const dir = makePlatformDir(privateKey);
|
|
555
|
+
t.after(() => cleanup(dir));
|
|
556
|
+
writeManifest(dir, {
|
|
557
|
+
name: "@fallow-cli/x",
|
|
558
|
+
version: "9.9.9",
|
|
559
|
+
fallowDigests: computeDigestsForDir(dir),
|
|
560
|
+
});
|
|
561
|
+
const result = verifyInstalledSync({
|
|
562
|
+
dirOverride: dir,
|
|
563
|
+
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
564
|
+
});
|
|
565
|
+
assert.equal(result.ok, true);
|
|
566
|
+
assert.equal(result.package, "<override>");
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
test("verifyInstalledSync fails fast on first bad signature", (t) => {
|
|
570
|
+
const { privateKey, rawPub } = makeKeypair();
|
|
571
|
+
const dir = makePlatformDir(privateKey, { corruptSigFor: "fallow-lsp" });
|
|
572
|
+
t.after(() => cleanup(dir));
|
|
573
|
+
writeManifest(dir, {
|
|
574
|
+
name: "@fallow-cli/x",
|
|
575
|
+
version: "9.9.9",
|
|
576
|
+
fallowDigests: computeDigestsForDir(dir),
|
|
577
|
+
});
|
|
578
|
+
const result = verifyInstalledSync({
|
|
579
|
+
dirOverride: dir,
|
|
580
|
+
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
581
|
+
});
|
|
582
|
+
assert.equal(result.ok, false);
|
|
583
|
+
assert.equal(result.code, "sig-invalid");
|
|
584
|
+
assert.match(result.binary, /fallow-lsp/);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test("verifyInstalledSync reports digest-mismatch when bytes diverge from embedded digest", (t) => {
|
|
588
|
+
const { privateKey, rawPub } = makeKeypair();
|
|
589
|
+
const dir = makePlatformDir(privateKey);
|
|
590
|
+
t.after(() => cleanup(dir));
|
|
591
|
+
writeManifest(dir, {
|
|
592
|
+
name: "@fallow-cli/x",
|
|
593
|
+
version: "9.9.9",
|
|
594
|
+
fallowDigests: {
|
|
595
|
+
fallow: "sha256:" + "a".repeat(64),
|
|
596
|
+
"fallow-lsp": "sha256:" + "a".repeat(64),
|
|
597
|
+
"fallow-mcp": "sha256:" + "a".repeat(64),
|
|
598
|
+
"fallow.exe": "sha256:" + "a".repeat(64),
|
|
599
|
+
"fallow-lsp.exe": "sha256:" + "a".repeat(64),
|
|
600
|
+
"fallow-mcp.exe": "sha256:" + "a".repeat(64),
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
const result = verifyInstalledSync({
|
|
604
|
+
dirOverride: dir,
|
|
605
|
+
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
606
|
+
});
|
|
607
|
+
assert.equal(result.ok, false);
|
|
608
|
+
assert.equal(result.code, "digest-mismatch");
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test("verifyInstalledSync reports digest-unavailable when no embedded digest and no provider", (t) => {
|
|
612
|
+
const { privateKey, rawPub } = makeKeypair();
|
|
613
|
+
const dir = makePlatformDir(privateKey);
|
|
614
|
+
t.after(() => cleanup(dir));
|
|
615
|
+
writeManifest(dir, { name: "@fallow-cli/x", version: "9.9.9" });
|
|
616
|
+
const result = verifyInstalledSync({
|
|
617
|
+
dirOverride: dir,
|
|
618
|
+
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
619
|
+
});
|
|
620
|
+
assert.equal(result.ok, false);
|
|
621
|
+
assert.equal(result.code, "digest-unavailable");
|
|
622
|
+
assert.match(result.message, /predates fallow 2\.78\.1/);
|
|
623
|
+
assert.match(result.message, new RegExp(SKIP_ENV));
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
test("verifyInstalledSync accepts a sync digestProvider for test isolation", (t) => {
|
|
627
|
+
const { privateKey, rawPub } = makeKeypair();
|
|
628
|
+
const dir = makePlatformDir(privateKey);
|
|
629
|
+
t.after(() => cleanup(dir));
|
|
630
|
+
// No fallowDigests on manifest; the sync provider supplies them.
|
|
631
|
+
writeManifest(dir, { name: "@fallow-cli/x", version: "9.9.9" });
|
|
632
|
+
const result = verifyInstalledSync({
|
|
633
|
+
dirOverride: dir,
|
|
634
|
+
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
635
|
+
digestProvider: makeSyncDigestProvider(dir),
|
|
636
|
+
});
|
|
637
|
+
assert.equal(result.ok, true);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
test("verifyInstalledSync surfaces provider errors as digest-unavailable", (t) => {
|
|
641
|
+
const { privateKey, rawPub } = makeKeypair();
|
|
642
|
+
const dir = makePlatformDir(privateKey);
|
|
643
|
+
t.after(() => cleanup(dir));
|
|
644
|
+
writeManifest(dir, { name: "@fallow-cli/x", version: "9.9.9" });
|
|
645
|
+
const result = verifyInstalledSync({
|
|
646
|
+
dirOverride: dir,
|
|
647
|
+
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
648
|
+
digestProvider: () => {
|
|
649
|
+
throw new Error("disk on fire");
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
assert.equal(result.ok, false);
|
|
653
|
+
assert.equal(result.code, "digest-unavailable");
|
|
654
|
+
assert.match(result.message, /disk on fire/);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("verifyInstalledSync honors FALLOW_SKIP_BINARY_VERIFY", (t) => {
|
|
658
|
+
const previous = process.env[SKIP_ENV];
|
|
659
|
+
process.env[SKIP_ENV] = "1";
|
|
660
|
+
t.after(() => {
|
|
661
|
+
if (previous === undefined) delete process.env[SKIP_ENV];
|
|
662
|
+
else process.env[SKIP_ENV] = previous;
|
|
663
|
+
});
|
|
664
|
+
const result = verifyInstalledSync({ dirOverride: "/does/not/exist" });
|
|
665
|
+
assert.equal(result.ok, true);
|
|
666
|
+
assert.equal(result.skipped, true);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
test("verifyInstalledSync ignores skip env when allowSkipEnv is false", (t) => {
|
|
670
|
+
const previous = process.env[SKIP_ENV];
|
|
671
|
+
process.env[SKIP_ENV] = "1";
|
|
672
|
+
t.after(() => {
|
|
673
|
+
if (previous === undefined) delete process.env[SKIP_ENV];
|
|
674
|
+
else process.env[SKIP_ENV] = previous;
|
|
675
|
+
});
|
|
676
|
+
const { privateKey, rawPub } = makeKeypair();
|
|
677
|
+
const dir = makePlatformDir(privateKey);
|
|
678
|
+
t.after(() => cleanup(dir));
|
|
679
|
+
writeManifest(dir, {
|
|
680
|
+
name: "@fallow-cli/x",
|
|
681
|
+
version: "9.9.9",
|
|
682
|
+
fallowDigests: computeDigestsForDir(dir),
|
|
683
|
+
});
|
|
684
|
+
const result = verifyInstalledSync({
|
|
685
|
+
dirOverride: dir,
|
|
686
|
+
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
687
|
+
allowSkipEnv: false,
|
|
688
|
+
});
|
|
689
|
+
assert.equal(result.ok, true);
|
|
690
|
+
assert.notEqual(result.skipped, true);
|
|
691
|
+
});
|