@blamejs/exceptd-skills 0.19.33 → 0.19.34
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 +10 -0
- package/bin/exceptd.js +896 -2824
- package/data/_indexes/_meta.json +2 -2
- package/lib/auto-discovery.js +56 -286
- package/lib/canonical-eq.js +7 -40
- package/lib/citation-resolve.js +22 -70
- package/lib/collectors/ai-api.js +20 -54
- package/lib/collectors/cicd-pipeline-compromise.js +40 -108
- package/lib/collectors/citation-hygiene.js +72 -210
- package/lib/collectors/containers.js +41 -130
- package/lib/collectors/cred-stores.js +31 -115
- package/lib/collectors/crypto-codebase.js +55 -138
- package/lib/collectors/crypto.js +24 -54
- package/lib/collectors/hardening.js +20 -78
- package/lib/collectors/kernel.js +16 -46
- package/lib/collectors/library-author.js +57 -206
- package/lib/collectors/mcp.js +24 -70
- package/lib/collectors/runtime.js +24 -86
- package/lib/collectors/sbom.js +34 -106
- package/lib/collectors/scan-excludes.js +31 -138
- package/lib/collectors/secrets.js +62 -178
- package/lib/cross-ref-api.js +39 -123
- package/lib/currency-severity.js +8 -27
- package/lib/cve-batch.js +13 -21
- package/lib/cve-cli.js +13 -20
- package/lib/cve-curation.js +72 -239
- package/lib/cve-regression-watcher.js +29 -152
- package/lib/cvss.js +13 -54
- package/lib/doctor-bucketing.js +3 -19
- package/lib/exit-codes.js +10 -42
- package/lib/flag-suggest.js +7 -25
- package/lib/framework-gap.js +35 -114
- package/lib/gap-detectors.js +37 -159
- package/lib/id-validation.js +9 -30
- package/lib/job-queue.js +13 -36
- package/lib/lint-skills.js +64 -232
- package/lib/playbook-runner.js +693 -2095
- package/lib/prefetch.js +100 -376
- package/lib/refresh-external.js +199 -627
- package/lib/refresh-network.js +75 -307
- package/lib/rfc-cli.js +23 -68
- package/lib/scoring.js +77 -145
- package/lib/sign.js +43 -229
- package/lib/source-advisories.js +43 -194
- package/lib/source-ghsa.js +37 -120
- package/lib/source-osv.js +94 -266
- package/lib/ttp-mapper.js +14 -24
- package/lib/upstream-check-cli.js +10 -28
- package/lib/upstream-check.js +19 -44
- package/lib/validate-catalog-meta.js +17 -61
- package/lib/validate-cve-catalog.js +43 -119
- package/lib/validate-indexes.js +25 -76
- package/lib/validate-package.js +16 -62
- package/lib/validate-playbooks.js +69 -275
- package/lib/validate-vendor.js +16 -49
- package/lib/verify.js +56 -286
- package/lib/version-pins.js +5 -34
- package/lib/worker-pool.js +11 -30
- package/lib/xml-tokenizer.js +47 -152
- package/manifest.json +53 -53
- package/orchestrator/dispatcher.js +17 -68
- package/orchestrator/event-bus.js +11 -74
- package/orchestrator/index.js +138 -412
- package/orchestrator/pipeline.js +28 -85
- package/orchestrator/scanner.js +34 -138
- package/orchestrator/scheduler.js +20 -84
- package/package.json +1 -1
- package/sbom.cdx.json +241 -241
- package/scripts/audit-catalog-gaps.js +9 -62
- package/scripts/audit-cross-skill.js +5 -31
- package/scripts/audit-perf.js +6 -16
- package/scripts/backfill-theater-test.js +7 -64
- package/scripts/bootstrap.js +12 -44
- package/scripts/build-indexes.js +40 -154
- package/scripts/builders/activity-feed.js +4 -14
- package/scripts/builders/catalog-summaries.js +3 -10
- package/scripts/builders/currency.js +7 -20
- package/scripts/builders/cwe-chains.js +7 -30
- package/scripts/builders/did-ladders.js +6 -13
- package/scripts/builders/frequency.js +5 -19
- package/scripts/builders/jurisdiction-clocks.js +6 -25
- package/scripts/builders/recipes.js +6 -14
- package/scripts/builders/section-offsets.js +13 -51
- package/scripts/builders/stale-content.js +7 -28
- package/scripts/builders/summary-cards.js +8 -29
- package/scripts/builders/theater-fingerprints.js +12 -27
- package/scripts/builders/token-budget.js +4 -31
- package/scripts/check-agents-md-collectors.js +11 -54
- package/scripts/check-catalog-gap-budget.js +15 -32
- package/scripts/check-changelog-extract.js +18 -48
- package/scripts/check-codebase-patterns-currency.js +6 -22
- package/scripts/check-codebase-patterns.js +50 -143
- package/scripts/check-epss-consistency.js +9 -64
- package/scripts/check-framework-gap-coverage.js +13 -31
- package/scripts/check-manifest-snapshot.js +13 -73
- package/scripts/check-sbom-currency.js +44 -142
- package/scripts/check-test-count.js +15 -52
- package/scripts/check-test-coverage.js +66 -197
- package/scripts/check-test-subjects.js +21 -62
- package/scripts/check-ttp-references.js +14 -38
- package/scripts/check-ttp-upstream.js +8 -40
- package/scripts/check-version-bump.js +9 -61
- package/scripts/check-version-tags.js +20 -121
- package/scripts/predeploy.js +38 -184
- package/scripts/refresh-manifest-snapshot.js +16 -38
- package/scripts/refresh-mitre-atlas.js +3 -8
- package/scripts/refresh-mitre-attack.js +1 -8
- package/scripts/refresh-mitre-d3fend.js +3 -9
- package/scripts/refresh-mitre-ics-attack.js +3 -8
- package/scripts/refresh-reverse-refs.js +27 -94
- package/scripts/refresh-rfc-index.js +2 -10
- package/scripts/refresh-sbom.js +31 -161
- package/scripts/refresh-upstream-catalogs.js +40 -137
- package/scripts/release.js +69 -232
- package/scripts/run-e2e-scenarios.js +24 -71
- package/scripts/sync-manifest-metadata.js +10 -34
- package/scripts/sync-package-description.js +8 -17
- package/scripts/validate-vendor-online.js +13 -44
- package/scripts/verify-shipped-tarball.js +35 -140
|
@@ -2,41 +2,11 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Packs with `npm pack`, extracts the tarball, and verifies the EXTRACTED tree
|
|
6
|
+
* rather than the source working tree: a keys/public.pem swapped between sign
|
|
7
|
+
* and pack surfaces nowhere else, and every fresh install then fails.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
* then run lib/verify.js against the EXTRACTED tree (not the source
|
|
9
|
-
* working tree). This catches the class of bug where:
|
|
10
|
-
*
|
|
11
|
-
* - CI's verify step against the source tree passes (38/38)
|
|
12
|
-
* - The tarball that npm publish actually uploads has different
|
|
13
|
-
* content (e.g. keys/public.pem swapped) and verify-on-tarball fails
|
|
14
|
-
*
|
|
15
|
-
* Every release v0.11.x through v0.12.2 shipped a tarball whose
|
|
16
|
-
* keys/public.pem did not match the Ed25519 signatures in manifest.json.
|
|
17
|
-
* Operators installing from npm saw 0/38 verify on every fresh install.
|
|
18
|
-
* The bug was invisible because CI's verify ran against the SOURCE tree,
|
|
19
|
-
* not the shipped tarball. This gate closes that gap.
|
|
20
|
-
*
|
|
21
|
-
* * F9 — After the first-pass extraction (using the source-tree parseTar),
|
|
22
|
-
* re-parse the tarball using the parseTar shipped INSIDE the
|
|
23
|
-
* extracted tree itself. If the two parses disagree, fail with a
|
|
24
|
-
* structured error. Catches the class where the shipped parser
|
|
25
|
-
* silently rejects entries the source parser accepts (or vice
|
|
26
|
-
* versa), which would mean operators run a different extractor
|
|
27
|
-
* than CI exercised.
|
|
28
|
-
* F15 — Invoke `npm pack --offline` so the gate cannot be blocked by
|
|
29
|
-
* registry reachability problems during predeploy.
|
|
30
|
-
* F4 — Cross-check the extracted public.pem against
|
|
31
|
-
* keys/EXPECTED_FINGERPRINT (warn-and-continue when missing, fail
|
|
32
|
-
* when present-but-mismatched and KEYS_ROTATED != 1).
|
|
33
|
-
*
|
|
34
|
-
* Exit codes:
|
|
35
|
-
* 0 verify passed against the packed tarball
|
|
36
|
-
* 1 verify failed against the packed tarball (the bug class above)
|
|
37
|
-
* 2 pack or extract failed (infrastructure error)
|
|
38
|
-
*
|
|
39
|
-
* Zero npm deps. Node 24 stdlib only.
|
|
9
|
+
* Exit: 0 verified, 1 verify failed, 2 pack or extract failed.
|
|
40
10
|
*/
|
|
41
11
|
|
|
42
12
|
const fs = require("fs");
|
|
@@ -44,26 +14,17 @@ const path = require("path");
|
|
|
44
14
|
const os = require("os");
|
|
45
15
|
const { spawnSync } = require("child_process");
|
|
46
16
|
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// a bug in the normalize() implementation in lib/ doesn't simultaneously
|
|
51
|
-
// disable both the source-tree-verify path AND the shipped-tarball-verify
|
|
52
|
-
// gate (we want at least one independent check). ANY change to normalize()
|
|
53
|
-
// in any of these four files must be mirrored in all of them.
|
|
17
|
+
// Mirrors the normalize() contract in lib/sign.js, lib/verify.js and
|
|
18
|
+
// lib/refresh-network.js. Duplicated deliberately: one bug must not disable the
|
|
19
|
+
// source-tree and shipped-tarball checks together. Change all four together.
|
|
54
20
|
function normalizeSkillBytes(buf) {
|
|
55
21
|
let s = Buffer.isBuffer(buf) ? buf.toString("utf8") : String(buf);
|
|
56
22
|
if (s.length > 0 && s.charCodeAt(0) === 0xFEFF) s = s.slice(1);
|
|
57
23
|
return Buffer.from(s.replace(/\r\n/g, "\n"), "utf8");
|
|
58
24
|
}
|
|
59
25
|
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// normalizeSkillBytes: a bug in lib/verify.js's verifier should not also
|
|
63
|
-
// disable this gate — at least one independent check must remain. The
|
|
64
|
-
// canonical-bytes computation MUST stay in lockstep with lib/sign.js +
|
|
65
|
-
// lib/verify.js + lib/refresh-network.js — enforced by
|
|
66
|
-
// tests/normalize-contract.test.js.
|
|
26
|
+
// Duplicated for the same reason as normalizeSkillBytes; the canonical-bytes
|
|
27
|
+
// computation stays in lockstep, enforced by tests/normalize-contract.test.js.
|
|
67
28
|
function canonicalizeForTarball(value) {
|
|
68
29
|
if (Array.isArray(value)) return value.map(canonicalizeForTarball);
|
|
69
30
|
if (value && typeof value === "object") {
|
|
@@ -108,20 +69,9 @@ function verifyExtractedManifestSignature(manifest, publicKeyPem) {
|
|
|
108
69
|
return ok ? { status: "valid" } : { status: "invalid", reason: "Ed25519 manifest signature did not verify against extracted public.pem" };
|
|
109
70
|
}
|
|
110
71
|
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
// branch: a manifest skill entry MAY legally omit `signature` (it is not
|
|
115
|
-
// in the schema's skillEntry.required set — a freshly-added skill, or one
|
|
116
|
-
// skipped during a partial sign-all, leaves it absent while the manifest
|
|
117
|
-
// envelope is still validly re-signed over that field-absent state). Such
|
|
118
|
-
// an entry must be a STRUCTURED miss, not Buffer.from(undefined,'base64')
|
|
119
|
-
// throwing a TypeError that the script body's catch re-throws as an
|
|
120
|
-
// uncaught stack trace.
|
|
121
|
-
// { status: 'missing' } — no Ed25519 signature in manifest
|
|
122
|
-
// { status: 'pass' } — signature verified
|
|
123
|
-
// { status: 'fail' } — signature did not verify
|
|
124
|
-
// { status: 'fail', reason: str } — malformed base64 / verify threw
|
|
72
|
+
// One skill entry's outcome: { status: 'missing' | 'pass' | 'fail' }, plus a
|
|
73
|
+
// `reason` when the base64 is malformed or verify throws. A manifest entry may
|
|
74
|
+
// legally omit `signature`, so that case is a structured miss, not a throw.
|
|
125
75
|
function verifySkillSignatureOutcome(s, normalizedContent, pubKey) {
|
|
126
76
|
const cryptoMod = require("crypto");
|
|
127
77
|
if (typeof s.signature !== "string" || s.signature.length === 0) {
|
|
@@ -142,9 +92,6 @@ function verifySkillSignatureOutcome(s, normalizedContent, pubKey) {
|
|
|
142
92
|
return ok ? { status: "pass" } : { status: "fail" };
|
|
143
93
|
}
|
|
144
94
|
|
|
145
|
-
// Exported so tests/normalize-contract.test.js can assert byte-identical
|
|
146
|
-
// normalize() behavior across all four implementations, and so the
|
|
147
|
-
// missing-signature guard above is unit-testable.
|
|
148
95
|
module.exports = {
|
|
149
96
|
normalizeSkillBytes,
|
|
150
97
|
verifyExtractedManifestSignature,
|
|
@@ -155,10 +102,8 @@ module.exports = {
|
|
|
155
102
|
const ROOT = path.resolve(__dirname, "..");
|
|
156
103
|
|
|
157
104
|
function emit(msg) { process.stdout.write(`[verify-shipped-tarball] ${msg}\n`); }
|
|
158
|
-
// Sentinel thrown by fail() so the
|
|
159
|
-
//
|
|
160
|
-
// npm-pack temp dir on every run (predeploy gate + `npm test`). Abort by
|
|
161
|
-
// throwing instead and set the exit code via process.exitCode.
|
|
105
|
+
// Sentinel thrown by fail() so the body's finally still runs its temp-dir
|
|
106
|
+
// cleanup — process.exit() would preempt it and leak the npm-pack temp dir.
|
|
162
107
|
const ABORT = Symbol("verify-shipped-tarball:abort");
|
|
163
108
|
function fail(msg, code = 1) {
|
|
164
109
|
process.stderr.write(`[verify-shipped-tarball] FAIL: ${msg}\n`);
|
|
@@ -166,22 +111,15 @@ function fail(msg, code = 1) {
|
|
|
166
111
|
throw ABORT;
|
|
167
112
|
}
|
|
168
113
|
|
|
169
|
-
//
|
|
170
|
-
// `require()` this file to load the exported helpers (notably
|
|
171
|
-
// normalizeSkillBytes for the byte-stability contract test) without
|
|
172
|
-
// invoking npm pack as a side effect of import.
|
|
114
|
+
// Gated on require.main so a test can require() the helpers without npm pack running.
|
|
173
115
|
if (require.main !== module) {
|
|
174
|
-
// Loaded as a library (e.g. by tests/normalize-contract.test.js).
|
|
175
|
-
// Skip the script body; consumers use the module.exports surface above.
|
|
176
116
|
return;
|
|
177
117
|
}
|
|
178
118
|
|
|
179
119
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "verify-shipped-"));
|
|
180
120
|
try {
|
|
181
121
|
emit(`packing into ${tmpRoot} ...`);
|
|
182
|
-
//
|
|
183
|
-
// reachability; `npm pack` does not need the network for a local
|
|
184
|
-
// package and forcing offline mode hard-locks the assumption.
|
|
122
|
+
// --offline: predeploy must run without registry reachability.
|
|
185
123
|
const pack = spawnSync("npm", ["pack", "--offline", "--pack-destination", tmpRoot], {
|
|
186
124
|
cwd: ROOT,
|
|
187
125
|
encoding: "utf8",
|
|
@@ -192,9 +130,7 @@ try {
|
|
|
192
130
|
}
|
|
193
131
|
const tarballName = pack.stdout.trim().split(/\r?\n/).filter(Boolean).pop();
|
|
194
132
|
const tarballPath = path.join(tmpRoot, tarballName);
|
|
195
|
-
//
|
|
196
|
-
// existence check, size report, and read all observe the same inode — no
|
|
197
|
-
// existsSync→statSync→readFileSync TOCTOU on the path.
|
|
133
|
+
// One descriptor for fstat + read, so both observe the same inode — no TOCTOU.
|
|
198
134
|
let tgz;
|
|
199
135
|
try {
|
|
200
136
|
const fd = fs.openSync(tarballPath, "r");
|
|
@@ -229,12 +165,8 @@ try {
|
|
|
229
165
|
}
|
|
230
166
|
emit(`extracted to ${pkgRoot}`);
|
|
231
167
|
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
// gate trips: this means CI exercised a different parser than operators
|
|
235
|
-
// will. Defense against drift between source and shipped tarball when
|
|
236
|
-
// someone edits lib/refresh-network.js without re-vendoring or vice
|
|
237
|
-
// versa.
|
|
168
|
+
// Re-parse with the extracted tree's OWN parseTar: a divergence means CI
|
|
169
|
+
// exercised a different extractor than operators will run.
|
|
238
170
|
const shippedParserPath = path.join(pkgRoot, "lib", "refresh-network.js");
|
|
239
171
|
if (!fs.existsSync(shippedParserPath)) {
|
|
240
172
|
fail(`extracted tree missing lib/refresh-network.js (cannot run F9 cross-parse check)`, 2);
|
|
@@ -249,7 +181,6 @@ try {
|
|
|
249
181
|
fail(`extracted lib/refresh-network.js does not export parseTar`, 2);
|
|
250
182
|
}
|
|
251
183
|
const shippedEntries = parseTarShipped(tarBuf);
|
|
252
|
-
// Compare counts first — fast bailout.
|
|
253
184
|
const divergences = [];
|
|
254
185
|
if (shippedEntries.length !== entries.length) {
|
|
255
186
|
divergences.push(
|
|
@@ -257,8 +188,7 @@ try {
|
|
|
257
188
|
`shipped parser produced ${shippedEntries.length}`
|
|
258
189
|
);
|
|
259
190
|
} else {
|
|
260
|
-
//
|
|
261
|
-
// compare is correct. Compare name + byte length + body bytes.
|
|
191
|
+
// Tarball entry order is deterministic, so a positional compare is valid.
|
|
262
192
|
for (let i = 0; i < entries.length; i++) {
|
|
263
193
|
const a = entries[i];
|
|
264
194
|
const b = shippedEntries[i];
|
|
@@ -288,9 +218,7 @@ try {
|
|
|
288
218
|
}
|
|
289
219
|
emit(`F9: source-tree and shipped parseTar agree on ${entries.length} entries`);
|
|
290
220
|
|
|
291
|
-
//
|
|
292
|
-
// having to spawn a separate process whose cwd resolution differs across
|
|
293
|
-
// platforms.
|
|
221
|
+
// Inline rather than spawned: cwd resolution differs across platforms.
|
|
294
222
|
const crypto = require("crypto");
|
|
295
223
|
const manifestPath = path.join(pkgRoot, "manifest.json");
|
|
296
224
|
const pubKeyPath = path.join(pkgRoot, "keys", "public.pem");
|
|
@@ -301,16 +229,9 @@ try {
|
|
|
301
229
|
const pubPem = fs.readFileSync(pubKeyPath, "utf8");
|
|
302
230
|
const pubKey = crypto.createPublicKey(pubPem);
|
|
303
231
|
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
// other manifest envelope metadata. A tarball whose body bytes are
|
|
308
|
-
// signed but whose manifest envelope was rewritten (re-routing a skill
|
|
309
|
-
// path, renaming a skill, changing atlas refs) would pass per-skill
|
|
310
|
-
// verification but fail this gate. v0.12.17+ shipped tarballs always
|
|
311
|
-
// include manifest_signature, so a missing signature here is also a
|
|
312
|
-
// refusal — stricter than the post-install warn-and-continue path,
|
|
313
|
-
// which tolerates legacy v0.12.16-and-earlier installs.
|
|
232
|
+
// Per-skill signatures cover only the skill body bytes, not skill.name or
|
|
233
|
+
// skill.path — a rewritten manifest envelope still passes per-skill verify.
|
|
234
|
+
// A missing envelope signature is a refusal here, not a warning.
|
|
314
235
|
const manifestSigStatus = verifyExtractedManifestSignature(manifest, pubPem);
|
|
315
236
|
if (manifestSigStatus.status !== "valid") {
|
|
316
237
|
fail(
|
|
@@ -324,8 +245,7 @@ try {
|
|
|
324
245
|
.update(pubKey.export({ type: "spki", format: "der" }))
|
|
325
246
|
.digest("base64");
|
|
326
247
|
|
|
327
|
-
//
|
|
328
|
-
// shows the divergence explicitly.
|
|
248
|
+
// The same fingerprint for the SOURCE-tree key, so the log names the drift.
|
|
329
249
|
const sourcePubPem = fs.readFileSync(path.join(ROOT, "keys", "public.pem"), "utf8");
|
|
330
250
|
const sourcePubKey = crypto.createPublicKey(sourcePubPem);
|
|
331
251
|
const sourcePubFp = crypto.createHash("sha256")
|
|
@@ -339,25 +259,17 @@ try {
|
|
|
339
259
|
emit(`*** Something between sign and pack is swapping the key. Verify will fail below. ***`);
|
|
340
260
|
}
|
|
341
261
|
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
// that's the file operators will actually receive on `npm install`.
|
|
345
|
-
// Warn when absent, fail when present-but-mismatched (unless KEYS_ROTATED).
|
|
262
|
+
// The pin is read from the EXTRACTED tree — the file operators receive on
|
|
263
|
+
// install. Absent warns; mismatched fails unless KEYS_ROTATED=1.
|
|
346
264
|
const expectedFpPath = path.join(pkgRoot, "keys", "EXPECTED_FINGERPRINT");
|
|
347
265
|
if (fs.existsSync(expectedFpPath)) {
|
|
348
|
-
//
|
|
349
|
-
// file (Notepad with files.encoding=utf8bom in the source tree) is
|
|
350
|
-
// tolerated identically across every verify site. The helper strips
|
|
351
|
-
// leading U+FEFF + ignores comment lines (`#`).
|
|
266
|
+
// The shared loader strips a leading U+FEFF, so a BOM-prefixed pin is tolerated.
|
|
352
267
|
const { loadExpectedFingerprintFirstLine } = require(path.join(ROOT, "lib", "verify.js"));
|
|
353
268
|
const firstLine = loadExpectedFingerprintFirstLine(expectedFpPath) || "";
|
|
354
269
|
const liveFpLine = `SHA256:${pubFp}`;
|
|
355
270
|
if (firstLine !== liveFpLine) {
|
|
356
271
|
if (process.env.KEYS_ROTATED === "1") {
|
|
357
|
-
//
|
|
358
|
-
// well so any caller listening on `process.on('warning')` can
|
|
359
|
-
// observe the key-rotation acceptance. Matches the three peer
|
|
360
|
-
// sites (bin/exceptd.js, lib/refresh-network.js, lib/verify.js).
|
|
272
|
+
// Also on the structured warning channel, so a listener observes it.
|
|
361
273
|
process.emitWarning(
|
|
362
274
|
`extracted public.pem fingerprint ${liveFpLine} differs from pin ${firstLine}; KEYS_ROTATED=1 accepted. ` +
|
|
363
275
|
`Update keys/EXPECTED_FINGERPRINT to lock the new pin.`,
|
|
@@ -389,23 +301,12 @@ try {
|
|
|
389
301
|
failures.push(`${s.name}: file not found at ${s.path}`);
|
|
390
302
|
continue;
|
|
391
303
|
}
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
// the byte-stability contract in lib/verify.js's normalize() header.
|
|
396
|
-
// Without the same normalization here, this gate (which was added
|
|
397
|
-
// specifically to catch the v0.11.x signature regression class!) would
|
|
398
|
-
// itself report 0/38 on any tree where line-ending normalization
|
|
399
|
-
// touched the source between sign and pack — a Windows contributor
|
|
400
|
-
// with `core.autocrlf=true`, or a tool like Prettier between sign and
|
|
401
|
-
// pack. This is the recurring CRLF/line-ending-bypass class.
|
|
304
|
+
// Normalize before verify: lib/sign.js signs BOM-stripped, LF-normalized
|
|
305
|
+
// bytes, so feeding raw bytes here reports 0/N wherever line endings changed
|
|
306
|
+
// between sign and pack (`core.autocrlf=true`).
|
|
402
307
|
const rawContent = fs.readFileSync(skillPath);
|
|
403
308
|
const normalizedContent = normalizeSkillBytes(rawContent);
|
|
404
309
|
const outcome = verifySkillSignatureOutcome(s, normalizedContent, pubKey);
|
|
405
|
-
// A skill entry that legally omits `signature` (not in the schema's
|
|
406
|
-
// required set) must be a STRUCTURED miss — not Buffer.from(undefined,
|
|
407
|
-
// 'base64') throwing a TypeError that the body's catch re-throws as an
|
|
408
|
-
// uncaught stack trace. Mirrors lib/verify.js verifySkill()'s missing_sig.
|
|
409
310
|
if (outcome.status === "missing") {
|
|
410
311
|
miss++;
|
|
411
312
|
failures.push(`${s.name}: no Ed25519 signature in manifest`);
|
|
@@ -415,13 +316,8 @@ try {
|
|
|
415
316
|
if (ok) pass++;
|
|
416
317
|
else {
|
|
417
318
|
fail_count++;
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
// v0.12.16: forensic logging uses rawContent (pre-normalization
|
|
421
|
-
// bytes) so an operator inspecting failures sees the actual on-disk
|
|
422
|
-
// shape, but tarSha is computed over the NORMALIZED bytes that
|
|
423
|
-
// were actually fed to crypto.verify — making the comparison to
|
|
424
|
-
// sign-time bytes meaningful.
|
|
319
|
+
// Sizes come from rawContent so an operator sees the on-disk shape, but
|
|
320
|
+
// the hashes cover the NORMALIZED bytes actually fed to crypto.verify.
|
|
425
321
|
const tarSha = crypto.createHash("sha256").update(normalizedContent).digest("hex").slice(0, 16);
|
|
426
322
|
let srcSha = "<missing>", srcSize = 0, srcContent;
|
|
427
323
|
if (fs.existsSync(sourceSkillPath)) {
|
|
@@ -447,8 +343,7 @@ try {
|
|
|
447
343
|
process.exitCode = 1;
|
|
448
344
|
}
|
|
449
345
|
} catch (e) {
|
|
450
|
-
// ABORT is the fail() sentinel
|
|
451
|
-
// other error is unexpected: let finally run, then re-propagate it.
|
|
346
|
+
// ABORT is the fail() sentinel; any other error re-propagates after finally.
|
|
452
347
|
if (e !== ABORT) throw e;
|
|
453
348
|
} finally {
|
|
454
349
|
// Best-effort cleanup; leave on failure for diagnostics.
|