fallow 2.88.2 → 2.88.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fallow",
|
|
3
|
-
"version": "2.88.
|
|
3
|
+
"version": "2.88.3",
|
|
4
4
|
"description": "Deterministic codebase intelligence for TypeScript and JavaScript. Quality, risk, architecture, dependencies, duplication, and safe cleanup evidence for humans, CI, and agents. Optional runtime intelligence layer (Fallow Runtime) adds production execution evidence. Rust-native, sub-second, 96 framework plugins.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -83,13 +83,13 @@
|
|
|
83
83
|
"@tanstack/intent": "0.0.41"
|
|
84
84
|
},
|
|
85
85
|
"optionalDependencies": {
|
|
86
|
-
"@fallow-cli/darwin-arm64": "2.88.
|
|
87
|
-
"@fallow-cli/darwin-x64": "2.88.
|
|
88
|
-
"@fallow-cli/linux-x64-gnu": "2.88.
|
|
89
|
-
"@fallow-cli/linux-arm64-gnu": "2.88.
|
|
90
|
-
"@fallow-cli/linux-x64-musl": "2.88.
|
|
91
|
-
"@fallow-cli/linux-arm64-musl": "2.88.
|
|
92
|
-
"@fallow-cli/win32-arm64-msvc": "2.88.
|
|
93
|
-
"@fallow-cli/win32-x64-msvc": "2.88.
|
|
86
|
+
"@fallow-cli/darwin-arm64": "2.88.3",
|
|
87
|
+
"@fallow-cli/darwin-x64": "2.88.3",
|
|
88
|
+
"@fallow-cli/linux-x64-gnu": "2.88.3",
|
|
89
|
+
"@fallow-cli/linux-arm64-gnu": "2.88.3",
|
|
90
|
+
"@fallow-cli/linux-x64-musl": "2.88.3",
|
|
91
|
+
"@fallow-cli/linux-arm64-musl": "2.88.3",
|
|
92
|
+
"@fallow-cli/win32-arm64-msvc": "2.88.3",
|
|
93
|
+
"@fallow-cli/win32-x64-msvc": "2.88.3"
|
|
94
94
|
}
|
|
95
95
|
}
|
package/scripts/run-binary.js
CHANGED
|
@@ -14,6 +14,7 @@ const fs = require("node:fs");
|
|
|
14
14
|
|
|
15
15
|
const { getPlatformPackage } = require("./platform-package");
|
|
16
16
|
const { ensureVerified } = require("./lazy-verify");
|
|
17
|
+
const { isPreSigningVersion } = require("./verify-binary");
|
|
17
18
|
|
|
18
19
|
function resolvePlatformPackageName() {
|
|
19
20
|
if (process.platform !== "linux") {
|
|
@@ -29,13 +30,34 @@ function resolvePlatformPackageName() {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function isVersionQuery(argv) {
|
|
32
|
-
//
|
|
33
|
+
// The root command answers all three version flags (--version, -V, and the
|
|
34
|
+
// TS/JS-toolchain-style -v), so the verified-status line must be appended for
|
|
35
|
+
// every one of them, not just --version / -V.
|
|
33
36
|
const tail = argv.slice(2);
|
|
34
37
|
if (tail.length === 0) return false;
|
|
35
|
-
return tail[0] === "--version" || tail[0] === "-V";
|
|
38
|
+
return tail[0] === "--version" || tail[0] === "-V" || tail[0] === "-v";
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
// Signing status of the resolved CLI version, appended to the `verified:` line.
|
|
42
|
+
// Most informative on the `skipped` path: a fleet running with
|
|
43
|
+
// FALLOW_SKIP_BINARY_VERIFY can see whether the pinned version is even signable
|
|
44
|
+
// (pre-signing versions predate the 2.77.0 epoch and have no signature to
|
|
45
|
+
// verify). Best-effort: an unknown/unreadable version yields no annotation.
|
|
46
|
+
function describeSigning(version) {
|
|
47
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
return isPreSigningVersion(version)
|
|
51
|
+
? `; fallow ${version} unsigned (predates 2.77.0)`
|
|
52
|
+
: `; fallow ${version} signed`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function describeVerified(result, version) {
|
|
56
|
+
const status = describeVerifiedStatus(result);
|
|
57
|
+
return `${status}${describeSigning(version)}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function describeVerifiedStatus(result) {
|
|
39
61
|
if (result.skipped) {
|
|
40
62
|
return `verified: skipped (${result.reason})`;
|
|
41
63
|
}
|
|
@@ -79,9 +101,21 @@ function printVerifyError(verifyResult) {
|
|
|
79
101
|
);
|
|
80
102
|
}
|
|
81
103
|
|
|
82
|
-
function writeVerifiedLineIfVersionQuery(verifyResult) {
|
|
104
|
+
function writeVerifiedLineIfVersionQuery(verifyResult, version) {
|
|
83
105
|
if (isVersionQuery(process.argv)) {
|
|
84
|
-
process.stdout.write(`${describeVerified(verifyResult)}\n`);
|
|
106
|
+
process.stdout.write(`${describeVerified(verifyResult, version)}\n`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Read the resolved CLI version from the platform package manifest (its version
|
|
111
|
+
// is released in lockstep with the CLI). Best-effort: never throws, so a
|
|
112
|
+
// missing/garbled manifest just omits the signing annotation on --version.
|
|
113
|
+
function readResolvedVersion(manifestPath) {
|
|
114
|
+
try {
|
|
115
|
+
const version = JSON.parse(fs.readFileSync(manifestPath, "utf8")).version;
|
|
116
|
+
return typeof version === "string" ? version : undefined;
|
|
117
|
+
} catch {
|
|
118
|
+
return undefined;
|
|
85
119
|
}
|
|
86
120
|
}
|
|
87
121
|
|
|
@@ -105,6 +139,7 @@ function guardBrokenStdout() {
|
|
|
105
139
|
function runBinary(binaryBaseName) {
|
|
106
140
|
guardBrokenStdout();
|
|
107
141
|
const { pkg, manifestPath, platformPkgDir } = resolvePlatformPaths();
|
|
142
|
+
const resolvedVersion = readResolvedVersion(manifestPath);
|
|
108
143
|
|
|
109
144
|
const binaryName = process.platform === "win32" ? `${binaryBaseName}.exe` : binaryBaseName;
|
|
110
145
|
const binaryPath = path.join(platformPkgDir, binaryName);
|
|
@@ -126,11 +161,11 @@ function runBinary(binaryBaseName) {
|
|
|
126
161
|
if (e.status === undefined) throw e;
|
|
127
162
|
// Child has already written its --version line via inherited stdio;
|
|
128
163
|
// append the verified line here only on a clean exit.
|
|
129
|
-
if (e.status === 0) writeVerifiedLineIfVersionQuery(verifyResult);
|
|
164
|
+
if (e.status === 0) writeVerifiedLineIfVersionQuery(verifyResult, resolvedVersion);
|
|
130
165
|
process.exit(e.status);
|
|
131
166
|
}
|
|
132
167
|
|
|
133
|
-
writeVerifiedLineIfVersionQuery(verifyResult);
|
|
168
|
+
writeVerifiedLineIfVersionQuery(verifyResult, resolvedVersion);
|
|
134
169
|
}
|
|
135
170
|
|
|
136
171
|
module.exports = {
|
|
@@ -37,3 +37,40 @@ test("guardBrokenStdout rethrows non-EPIPE stdout errors (exit 1)", () => {
|
|
|
37
37
|
assert.match(res.stderr, /Error: write ENOSPC/, "the rethrown error reaches stderr");
|
|
38
38
|
assert.doesNotMatch(res.stderr, /is not a function/, "guard must be present, not absent");
|
|
39
39
|
});
|
|
40
|
+
|
|
41
|
+
test("isVersionQuery recognizes --version, -V, and -v as the first argument", () => {
|
|
42
|
+
const { isVersionQuery } = require(RUN_BINARY);
|
|
43
|
+
assert.equal(isVersionQuery(["node", "fallow", "--version"]), true);
|
|
44
|
+
assert.equal(isVersionQuery(["node", "fallow", "-V"]), true);
|
|
45
|
+
assert.equal(
|
|
46
|
+
isVersionQuery(["node", "fallow", "-v"]),
|
|
47
|
+
true,
|
|
48
|
+
"-v must append the verified line too",
|
|
49
|
+
);
|
|
50
|
+
assert.equal(isVersionQuery(["node", "fallow"]), false);
|
|
51
|
+
assert.equal(isVersionQuery(["node", "fallow", "dead-code"]), false);
|
|
52
|
+
assert.equal(
|
|
53
|
+
isVersionQuery(["node", "fallow", "dead-code", "-v"]),
|
|
54
|
+
false,
|
|
55
|
+
"-v only counts as the first arg",
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("describeVerified annotates the resolved version's signing status", () => {
|
|
60
|
+
const { describeVerified } = require(RUN_BINARY);
|
|
61
|
+
const ok = { ok: true, sentinelPath: "/c/s" };
|
|
62
|
+
// Signed-era version: appended as `signed`.
|
|
63
|
+
assert.match(
|
|
64
|
+
describeVerified(ok, "2.83.0"),
|
|
65
|
+
/verified: yes \(sentinel \/c\/s\); fallow 2\.83\.0 signed/,
|
|
66
|
+
);
|
|
67
|
+
// Pre-signing version: the fleet pre-flight signal, most useful on skip.
|
|
68
|
+
const skipped = { skipped: true, reason: "FALLOW_SKIP_BINARY_VERIFY is set" };
|
|
69
|
+
assert.match(
|
|
70
|
+
describeVerified(skipped, "2.76.0"),
|
|
71
|
+
/verified: skipped \(.*\); fallow 2\.76\.0 unsigned \(predates 2\.77\.0\)/,
|
|
72
|
+
);
|
|
73
|
+
// Unknown / unreadable version: no annotation, version line stays intact.
|
|
74
|
+
assert.equal(describeVerified(ok, undefined), "verified: yes (sentinel /c/s)");
|
|
75
|
+
assert.equal(describeVerified(ok, ""), "verified: yes (sentinel /c/s)");
|
|
76
|
+
});
|
package/scripts/verify-binary.js
CHANGED
|
@@ -81,6 +81,10 @@ function _verifyWithKey(binaryPath, rawPubKey) {
|
|
|
81
81
|
signature = fs.readFileSync(sigPath);
|
|
82
82
|
} catch (err) {
|
|
83
83
|
if (err && err.code === "ENOENT") {
|
|
84
|
+
// Low-level result: the version-aware guidance is attached one layer up
|
|
85
|
+
// in verifyOneBinary{,Sync}, which knows the resolved platform-package
|
|
86
|
+
// version and can distinguish a pre-signing version from a >=2.77.0
|
|
87
|
+
// package whose signature is missing (a tampering signal). Refs #944.
|
|
84
88
|
return { ok: false, code: "sig-missing", message: `signature not found at ${sigPath}` };
|
|
85
89
|
}
|
|
86
90
|
return {
|
|
@@ -389,16 +393,69 @@ function resolvePlatformPackageForVerify(opts) {
|
|
|
389
393
|
};
|
|
390
394
|
}
|
|
391
395
|
|
|
396
|
+
// Signed platform binaries ship from fallow 2.77.0 onward (the `.sig` staging
|
|
397
|
+
// step, the `files` entries, and this verifier all landed in #488, first
|
|
398
|
+
// released in 2.77.0). A resolved version below this epoch has no signature and
|
|
399
|
+
// never will (npm is immutable), so its missing-sig failure is expected and the
|
|
400
|
+
// fix is to upgrade the pin. A version at or above the epoch whose signature is
|
|
401
|
+
// absent is a different, alarming case (tampered or incomplete package).
|
|
402
|
+
const SIGNING_EPOCH = [2, 77, 0];
|
|
403
|
+
|
|
404
|
+
// True when `version` is a parseable semver strictly below the signing epoch.
|
|
405
|
+
// An unparsable / unknown version returns false so the caller uses the
|
|
406
|
+
// cautious (possible-tampering) message rather than telling the user to bump.
|
|
407
|
+
function isPreSigningVersion(version) {
|
|
408
|
+
if (typeof version !== "string") {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
412
|
+
if (!match) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
const parts = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
416
|
+
for (let i = 0; i < SIGNING_EPOCH.length; i += 1) {
|
|
417
|
+
if (parts[i] < SIGNING_EPOCH[i]) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
if (parts[i] > SIGNING_EPOCH[i]) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Attach version-aware remediation to a `sig-missing` low-level result. Other
|
|
428
|
+
// failure codes pass through untouched. The split distinguishes a pre-signing
|
|
429
|
+
// version (no signature exists and never will, so the fix is to upgrade the
|
|
430
|
+
// pin) from a >=2.77.0 package whose signature is unexpectedly absent (a
|
|
431
|
+
// tampering signal). Each message gives the CONSTRUCTIVE fix only; the bypass
|
|
432
|
+
// escape hatch (FALLOW_SKIP_BINARY_VERIFY) is owned by the caller's trailer and
|
|
433
|
+
// SECURITY.md, deliberately not surfaced here so a tampering victim is never
|
|
434
|
+
// nudged to bypass and the env is not normalized in CI logs.
|
|
435
|
+
function describeSigMissing(result, version) {
|
|
436
|
+
if (result.code !== "sig-missing") {
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
const message = isPreSigningVersion(version)
|
|
440
|
+
? `${result.message}. fallow ${version} predates signed binaries (signatures ship in 2.77.0 ` +
|
|
441
|
+
`and later), so this package has no signature to verify. Bump the \`fallow\` dependency in ` +
|
|
442
|
+
`your project's package.json to >=2.77.0 (for example \`npm install fallow@latest\`).`
|
|
443
|
+
: `${result.message}. fallow ${version} should be signed but its signature is missing; the ` +
|
|
444
|
+
`platform package may be tampered with or incomplete. Reinstall with \`npm install fallow@latest\` ` +
|
|
445
|
+
`and report it if it persists on a clean install.`;
|
|
446
|
+
return { ...result, message };
|
|
447
|
+
}
|
|
448
|
+
|
|
392
449
|
// Verify one binary against its sig + expected SHA-256. Used by both the
|
|
393
450
|
// sync and async verify-installed entry points; the digest provider may be
|
|
394
451
|
// sync (returns string) or async (returns Promise<string>), and the loop body
|
|
395
452
|
// awaits the value regardless. Keeps the outer functions a flat for-loop so
|
|
396
453
|
// cyclomatic + cognitive complexity stays low.
|
|
397
|
-
async function verifyOneBinary(target, dir, pkg, manifestPath, verifyFn, digestProvider) {
|
|
454
|
+
async function verifyOneBinary(target, dir, pkg, manifestPath, verifyFn, digestProvider, version) {
|
|
398
455
|
const binaryPath = path.join(dir, target.binary);
|
|
399
456
|
const sigResult = verifyFn(binaryPath);
|
|
400
457
|
if (!sigResult.ok) {
|
|
401
|
-
return { ...sigResult, binary: binaryPath, package: pkg };
|
|
458
|
+
return { ...describeSigMissing(sigResult, version), binary: binaryPath, package: pkg };
|
|
402
459
|
}
|
|
403
460
|
// Prefer the digest embedded in the platform package's package.json
|
|
404
461
|
// (written at release time by `npm-prep`). Falling back to the GitHub
|
|
@@ -478,7 +535,15 @@ async function verifyInstalled(options) {
|
|
|
478
535
|
const boundProvider = async (args) => digestProvider({ ...args, version });
|
|
479
536
|
|
|
480
537
|
for (const target of binaryTargetsForPlatform(platformId)) {
|
|
481
|
-
const result = await verifyOneBinary(
|
|
538
|
+
const result = await verifyOneBinary(
|
|
539
|
+
target,
|
|
540
|
+
dir,
|
|
541
|
+
pkg,
|
|
542
|
+
manifestPath,
|
|
543
|
+
verify,
|
|
544
|
+
boundProvider,
|
|
545
|
+
version,
|
|
546
|
+
);
|
|
482
547
|
if (!result.ok) return result;
|
|
483
548
|
}
|
|
484
549
|
return { ok: true, package: pkg, version };
|
|
@@ -518,7 +583,15 @@ function verifyInstalledSync(options) {
|
|
|
518
583
|
const { dir, manifestPath, pkg, version, platformId } = resolved;
|
|
519
584
|
|
|
520
585
|
for (const target of binaryTargetsForPlatform(platformId)) {
|
|
521
|
-
const result = verifyOneBinarySync(
|
|
586
|
+
const result = verifyOneBinarySync(
|
|
587
|
+
target,
|
|
588
|
+
dir,
|
|
589
|
+
pkg,
|
|
590
|
+
manifestPath,
|
|
591
|
+
verify,
|
|
592
|
+
digestProvider,
|
|
593
|
+
version,
|
|
594
|
+
);
|
|
522
595
|
if (!result.ok) return result;
|
|
523
596
|
}
|
|
524
597
|
return { ok: true, package: pkg, version };
|
|
@@ -529,11 +602,11 @@ function verifyInstalledSync(options) {
|
|
|
529
602
|
// actionable error pointing the user at `npm install fallow@latest` (since
|
|
530
603
|
// there is no network fallback in lazy mode), and the digestProvider is
|
|
531
604
|
// optional (tests inject one; production callers rely on the embedded digest).
|
|
532
|
-
function verifyOneBinarySync(target, dir, pkg, manifestPath, verifyFn, digestProvider) {
|
|
605
|
+
function verifyOneBinarySync(target, dir, pkg, manifestPath, verifyFn, digestProvider, version) {
|
|
533
606
|
const binaryPath = path.join(dir, target.binary);
|
|
534
607
|
const sigResult = verifyFn(binaryPath);
|
|
535
608
|
if (!sigResult.ok) {
|
|
536
|
-
return { ...sigResult, binary: binaryPath, package: pkg };
|
|
609
|
+
return { ...describeSigMissing(sigResult, version), binary: binaryPath, package: pkg };
|
|
537
610
|
}
|
|
538
611
|
let expectedDigest = manifestPath ? readEmbeddedDigest(manifestPath, target.binary) : null;
|
|
539
612
|
if (!expectedDigest && digestProvider) {
|
|
@@ -575,6 +648,8 @@ module.exports = {
|
|
|
575
648
|
verifyInstalled,
|
|
576
649
|
verifyInstalledSync,
|
|
577
650
|
_verifyWithKey,
|
|
651
|
+
isPreSigningVersion,
|
|
652
|
+
describeSigMissing,
|
|
578
653
|
normalizeDigest,
|
|
579
654
|
readEmbeddedDigest,
|
|
580
655
|
sha256Hex,
|
|
@@ -7,6 +7,8 @@ const path = require("node:path");
|
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
_verifyWithKey,
|
|
10
|
+
isPreSigningVersion,
|
|
11
|
+
describeSigMissing,
|
|
10
12
|
verifyBinaryAt,
|
|
11
13
|
verifyDigestAt,
|
|
12
14
|
verifyInstalled,
|
|
@@ -133,6 +135,56 @@ test("_verifyWithKey returns sig-missing when the signature file does not exist"
|
|
|
133
135
|
}
|
|
134
136
|
});
|
|
135
137
|
|
|
138
|
+
// The version-aware remediation lives in describeSigMissing (attached by
|
|
139
|
+
// verifyOneBinary{,Sync}), not in the low-level _verifyWithKey result. Refs #944.
|
|
140
|
+
test("isPreSigningVersion is true below 2.77.0 and false at/above it", () => {
|
|
141
|
+
assert.equal(isPreSigningVersion("2.76.0"), true);
|
|
142
|
+
assert.equal(isPreSigningVersion("2.73.0"), true);
|
|
143
|
+
assert.equal(isPreSigningVersion("1.9.0"), true);
|
|
144
|
+
assert.equal(isPreSigningVersion("2.77.0"), false);
|
|
145
|
+
assert.equal(isPreSigningVersion("2.83.0"), false);
|
|
146
|
+
assert.equal(isPreSigningVersion("2.88.2"), false);
|
|
147
|
+
// Unknown / unparsable versions default to false so the caller uses the
|
|
148
|
+
// cautious possible-tampering message rather than telling the user to bump.
|
|
149
|
+
assert.equal(isPreSigningVersion("unknown"), false);
|
|
150
|
+
assert.equal(isPreSigningVersion(undefined), false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("describeSigMissing gives upgrade guidance for a pre-signing version", () => {
|
|
154
|
+
const base = { ok: false, code: "sig-missing", message: "signature not found at /x/fallow.sig" };
|
|
155
|
+
const result = describeSigMissing(base, "2.76.0");
|
|
156
|
+
// The security-critical invariant: enriching the message must never flip the
|
|
157
|
+
// verdict. A sig-missing result stays a hard failure.
|
|
158
|
+
assert.equal(result.ok, false);
|
|
159
|
+
assert.equal(result.code, "sig-missing");
|
|
160
|
+
assert.match(result.message, /predates signed binaries/);
|
|
161
|
+
assert.match(result.message, /2\.77\.0/);
|
|
162
|
+
// Names WHERE the pin lives so the user can act.
|
|
163
|
+
assert.match(result.message, /package\.json/);
|
|
164
|
+
// The bypass escape hatch is owned by the caller's trailer + SECURITY.md, not
|
|
165
|
+
// surfaced inline, so it is not normalized in CI logs.
|
|
166
|
+
assert.doesNotMatch(result.message, new RegExp(SKIP_ENV));
|
|
167
|
+
// The original low-level detail is preserved.
|
|
168
|
+
assert.match(result.message, /signature not found/);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("describeSigMissing flags possible tampering for a signed-era version", () => {
|
|
172
|
+
const base = { ok: false, code: "sig-missing", message: "signature not found at /x/fallow.sig" };
|
|
173
|
+
const result = describeSigMissing(base, "2.83.0");
|
|
174
|
+
assert.equal(result.ok, false);
|
|
175
|
+
assert.equal(result.code, "sig-missing");
|
|
176
|
+
assert.match(result.message, /tampered with or incomplete/);
|
|
177
|
+
assert.match(result.message, /Reinstall/);
|
|
178
|
+
// Must NOT nudge a possible-tampering victim toward bypassing verification.
|
|
179
|
+
assert.doesNotMatch(result.message, new RegExp(SKIP_ENV));
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("describeSigMissing passes non-sig-missing results through untouched", () => {
|
|
183
|
+
const base = { ok: false, code: "digest-mismatch", message: "digest mismatch" };
|
|
184
|
+
const result = describeSigMissing(base, "2.76.0");
|
|
185
|
+
assert.equal(result.message, "digest mismatch");
|
|
186
|
+
});
|
|
187
|
+
|
|
136
188
|
test("_verifyWithKey returns binary-missing when the binary does not exist", () => {
|
|
137
189
|
const { rawPub } = makeKeypair();
|
|
138
190
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fallow-vbtest-"));
|
|
@@ -343,6 +395,32 @@ test("verifyInstalled with dirOverride reports sig-missing when a .sig is absent
|
|
|
343
395
|
assert.match(result.binary, /fallow-mcp/);
|
|
344
396
|
});
|
|
345
397
|
|
|
398
|
+
test("verifyInstalled threads the resolved version into the sig-missing message", async (t) => {
|
|
399
|
+
const { privateKey, rawPub } = makeKeypair();
|
|
400
|
+
const preDir = makePlatformDir(privateKey, { skipSigFor: "fallow-mcp" });
|
|
401
|
+
const eraDir = makePlatformDir(privateKey, { skipSigFor: "fallow-mcp" });
|
|
402
|
+
t.after(() => {
|
|
403
|
+
cleanup(preDir);
|
|
404
|
+
cleanup(eraDir);
|
|
405
|
+
});
|
|
406
|
+
const pre = await verifyInstalled({
|
|
407
|
+
dirOverride: preDir,
|
|
408
|
+
version: "2.76.0",
|
|
409
|
+
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
410
|
+
digestProvider: makeDigestProvider(preDir),
|
|
411
|
+
});
|
|
412
|
+
assert.equal(pre.code, "sig-missing");
|
|
413
|
+
assert.match(pre.message, /predates signed binaries/);
|
|
414
|
+
const era = await verifyInstalled({
|
|
415
|
+
dirOverride: eraDir,
|
|
416
|
+
version: "2.83.0",
|
|
417
|
+
verifyFn: (p) => _verifyWithKey(p, rawPub),
|
|
418
|
+
digestProvider: makeDigestProvider(eraDir),
|
|
419
|
+
});
|
|
420
|
+
assert.equal(era.code, "sig-missing");
|
|
421
|
+
assert.match(era.message, /tampered with or incomplete/);
|
|
422
|
+
});
|
|
423
|
+
|
|
346
424
|
test("verifyInstalled reports digest-mismatch when SHA-256 disagrees with the provider", async (t) => {
|
|
347
425
|
const { privateKey, rawPub } = makeKeypair();
|
|
348
426
|
const dir = makePlatformDir(privateKey);
|
|
@@ -486,7 +486,7 @@ fallow health --format json --quiet --trend
|
|
|
486
486
|
{
|
|
487
487
|
"kind": "health",
|
|
488
488
|
"schema_version": 7,
|
|
489
|
-
"version": "2.88.
|
|
489
|
+
"version": "2.88.3",
|
|
490
490
|
"elapsed_ms": 32,
|
|
491
491
|
"summary": {
|
|
492
492
|
"files_analyzed": 482,
|
|
@@ -876,7 +876,7 @@ fallow audit \
|
|
|
876
876
|
{
|
|
877
877
|
"kind": "audit",
|
|
878
878
|
"schema_version": 7,
|
|
879
|
-
"version": "2.88.
|
|
879
|
+
"version": "2.88.3",
|
|
880
880
|
"command": "audit",
|
|
881
881
|
"verdict": "fail",
|
|
882
882
|
"changed_files_count": 12,
|
|
@@ -949,7 +949,7 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
949
949
|
```json
|
|
950
950
|
{
|
|
951
951
|
"schema_version": 7,
|
|
952
|
-
"version": "2.88.
|
|
952
|
+
"version": "2.88.3",
|
|
953
953
|
"elapsed_ms": 116,
|
|
954
954
|
"feature_flags": [],
|
|
955
955
|
"total_flags": 0
|
|
@@ -1488,7 +1488,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1488
1488
|
{
|
|
1489
1489
|
"kind": "dead-code",
|
|
1490
1490
|
"schema_version": 7,
|
|
1491
|
-
"version": "2.88.
|
|
1491
|
+
"version": "2.88.3",
|
|
1492
1492
|
"elapsed_ms": 45,
|
|
1493
1493
|
"total_issues": 12,
|
|
1494
1494
|
"entry_points": {
|
|
@@ -1648,7 +1648,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
1648
1648
|
{
|
|
1649
1649
|
"kind": "dupes",
|
|
1650
1650
|
"schema_version": 7,
|
|
1651
|
-
"version": "2.88.
|
|
1651
|
+
"version": "2.88.3",
|
|
1652
1652
|
"elapsed_ms": 82,
|
|
1653
1653
|
"total_clones": 15,
|
|
1654
1654
|
"total_lines_duplicated": 230,
|
|
@@ -1692,11 +1692,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
1692
1692
|
{
|
|
1693
1693
|
"kind": "combined",
|
|
1694
1694
|
"schema_version": 7,
|
|
1695
|
-
"version": "2.88.
|
|
1695
|
+
"version": "2.88.3",
|
|
1696
1696
|
"elapsed_ms": 159,
|
|
1697
1697
|
"check": {
|
|
1698
1698
|
"schema_version": 7,
|
|
1699
|
-
"version": "2.88.
|
|
1699
|
+
"version": "2.88.3",
|
|
1700
1700
|
"elapsed_ms": 45,
|
|
1701
1701
|
"total_issues": 12,
|
|
1702
1702
|
"unused_files": [],
|