@blamejs/exceptd-skills 0.19.33 → 0.19.35

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.
Files changed (119) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/bin/exceptd.js +895 -2828
  3. package/data/_indexes/_meta.json +2 -2
  4. package/lib/auto-discovery.js +56 -286
  5. package/lib/canonical-eq.js +7 -40
  6. package/lib/citation-resolve.js +22 -70
  7. package/lib/collectors/ai-api.js +170 -76
  8. package/lib/collectors/cicd-pipeline-compromise.js +113 -136
  9. package/lib/collectors/citation-hygiene.js +72 -210
  10. package/lib/collectors/containers.js +41 -130
  11. package/lib/collectors/cred-stores.js +31 -115
  12. package/lib/collectors/crypto-codebase.js +55 -138
  13. package/lib/collectors/crypto.js +24 -54
  14. package/lib/collectors/hardening.js +20 -78
  15. package/lib/collectors/kernel.js +16 -46
  16. package/lib/collectors/library-author.js +198 -211
  17. package/lib/collectors/mcp.js +24 -70
  18. package/lib/collectors/runtime.js +24 -86
  19. package/lib/collectors/sbom.js +130 -118
  20. package/lib/collectors/scan-excludes.js +33 -139
  21. package/lib/collectors/secrets.js +62 -178
  22. package/lib/cross-ref-api.js +39 -123
  23. package/lib/currency-severity.js +8 -27
  24. package/lib/cve-batch.js +13 -21
  25. package/lib/cve-cli.js +13 -20
  26. package/lib/cve-curation.js +72 -239
  27. package/lib/cve-regression-watcher.js +29 -155
  28. package/lib/cvss.js +13 -54
  29. package/lib/doctor-bucketing.js +3 -19
  30. package/lib/exit-codes.js +10 -42
  31. package/lib/flag-suggest.js +7 -25
  32. package/lib/framework-gap.js +39 -113
  33. package/lib/gap-detectors.js +37 -159
  34. package/lib/id-validation.js +9 -30
  35. package/lib/job-queue.js +13 -36
  36. package/lib/lint-skills.js +88 -236
  37. package/lib/playbook-runner.js +759 -2107
  38. package/lib/prefetch.js +101 -376
  39. package/lib/refresh-external.js +199 -633
  40. package/lib/refresh-network.js +78 -311
  41. package/lib/rfc-cli.js +23 -68
  42. package/lib/scoring.js +85 -146
  43. package/lib/sign.js +43 -229
  44. package/lib/source-advisories.js +43 -194
  45. package/lib/source-ghsa.js +37 -120
  46. package/lib/source-osv.js +94 -266
  47. package/lib/ttp-mapper.js +28 -27
  48. package/lib/upstream-check-cli.js +36 -29
  49. package/lib/upstream-check.js +19 -44
  50. package/lib/validate-catalog-meta.js +17 -61
  51. package/lib/validate-cve-catalog.js +52 -121
  52. package/lib/validate-indexes.js +25 -76
  53. package/lib/validate-package.js +16 -62
  54. package/lib/validate-playbooks.js +78 -286
  55. package/lib/validate-vendor.js +16 -49
  56. package/lib/verify.js +56 -286
  57. package/lib/version-pins.js +5 -34
  58. package/lib/worker-pool.js +11 -30
  59. package/lib/xml-tokenizer.js +47 -152
  60. package/manifest.json +53 -53
  61. package/orchestrator/dispatcher.js +17 -68
  62. package/orchestrator/event-bus.js +11 -74
  63. package/orchestrator/index.js +138 -413
  64. package/orchestrator/pipeline.js +28 -85
  65. package/orchestrator/scanner.js +34 -138
  66. package/orchestrator/scheduler.js +20 -84
  67. package/package.json +1 -1
  68. package/sbom.cdx.json +242 -242
  69. package/scripts/audit-catalog-gaps.js +9 -62
  70. package/scripts/audit-cross-skill.js +5 -31
  71. package/scripts/audit-perf.js +29 -28
  72. package/scripts/backfill-theater-test.js +7 -64
  73. package/scripts/bootstrap.js +12 -44
  74. package/scripts/build-indexes.js +40 -154
  75. package/scripts/builders/activity-feed.js +4 -14
  76. package/scripts/builders/catalog-summaries.js +3 -10
  77. package/scripts/builders/currency.js +7 -20
  78. package/scripts/builders/cwe-chains.js +7 -30
  79. package/scripts/builders/did-ladders.js +6 -13
  80. package/scripts/builders/frequency.js +5 -19
  81. package/scripts/builders/jurisdiction-clocks.js +6 -25
  82. package/scripts/builders/recipes.js +6 -14
  83. package/scripts/builders/section-offsets.js +13 -51
  84. package/scripts/builders/stale-content.js +7 -28
  85. package/scripts/builders/summary-cards.js +8 -29
  86. package/scripts/builders/theater-fingerprints.js +21 -31
  87. package/scripts/builders/token-budget.js +4 -31
  88. package/scripts/check-agents-md-collectors.js +26 -57
  89. package/scripts/check-catalog-gap-budget.js +15 -32
  90. package/scripts/check-changelog-extract.js +18 -48
  91. package/scripts/check-codebase-patterns-currency.js +6 -22
  92. package/scripts/check-codebase-patterns.js +63 -143
  93. package/scripts/check-epss-consistency.js +9 -64
  94. package/scripts/check-framework-gap-coverage.js +13 -31
  95. package/scripts/check-manifest-snapshot.js +62 -81
  96. package/scripts/check-sbom-currency.js +44 -142
  97. package/scripts/check-test-count.js +15 -52
  98. package/scripts/check-test-coverage.js +83 -198
  99. package/scripts/check-test-subjects.js +21 -62
  100. package/scripts/check-ttp-references.js +14 -38
  101. package/scripts/check-ttp-upstream.js +8 -40
  102. package/scripts/check-version-bump.js +9 -61
  103. package/scripts/check-version-tags.js +20 -121
  104. package/scripts/predeploy.js +38 -184
  105. package/scripts/refresh-manifest-snapshot.js +16 -38
  106. package/scripts/refresh-mitre-atlas.js +7 -8
  107. package/scripts/refresh-mitre-attack.js +1 -8
  108. package/scripts/refresh-mitre-d3fend.js +3 -9
  109. package/scripts/refresh-mitre-ics-attack.js +7 -8
  110. package/scripts/refresh-reverse-refs.js +27 -94
  111. package/scripts/refresh-rfc-index.js +7 -10
  112. package/scripts/refresh-sbom.js +31 -161
  113. package/scripts/refresh-upstream-catalogs.js +63 -148
  114. package/scripts/release.js +69 -234
  115. package/scripts/run-e2e-scenarios.js +26 -73
  116. package/scripts/sync-manifest-metadata.js +10 -34
  117. package/scripts/sync-package-description.js +8 -17
  118. package/scripts/validate-vendor-online.js +13 -44
  119. package/scripts/verify-shipped-tarball.js +35 -141
@@ -2,41 +2,11 @@
2
2
  "use strict";
3
3
 
4
4
  /**
5
- * scripts/verify-shipped-tarball.js
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
- * Pack the package with `npm pack`, extract the tarball to a temp dir,
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
- // v0.12.16: mirror the byte-stability normalize() contract
48
- // from lib/sign.js + lib/verify.js + lib/refresh-network.js. Duplicated
49
- // (not require'd) to keep this script's dep surface minimal and to ensure
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
- // In-line manifest-signature verifier for the extracted tarball. Kept
61
- // here (rather than imported) for the same defense-in-depth reasoning as
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") {
@@ -78,7 +39,6 @@ function canonicalizeForTarball(value) {
78
39
  function canonicalManifestBytesForTarball(manifest) {
79
40
  const clone = Object.assign({}, manifest);
80
41
  delete clone.manifest_signature;
81
- const cryptoMod = require("crypto"); // eslint-disable-line no-unused-vars
82
42
  const json = JSON.stringify(canonicalizeForTarball(clone), null, 2);
83
43
  return normalizeSkillBytes(Buffer.from(json, "utf8"));
84
44
  }
@@ -108,20 +68,9 @@ function verifyExtractedManifestSignature(manifest, publicKeyPem) {
108
68
  return ok ? { status: "valid" } : { status: "invalid", reason: "Ed25519 manifest signature did not verify against extracted public.pem" };
109
69
  }
110
70
 
111
- // Decide one skill entry's verification outcome. Pulled out as a pure
112
- // function so the missing-signature guard is unit-testable without
113
- // spawning `npm pack`. Mirrors lib/verify.js verifySkill()'s missing_sig
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
71
+ // One skill entry's outcome: { status: 'missing' | 'pass' | 'fail' }, plus a
72
+ // `reason` when the base64 is malformed or verify throws. A manifest entry may
73
+ // legally omit `signature`, so that case is a structured miss, not a throw.
125
74
  function verifySkillSignatureOutcome(s, normalizedContent, pubKey) {
126
75
  const cryptoMod = require("crypto");
127
76
  if (typeof s.signature !== "string" || s.signature.length === 0) {
@@ -142,9 +91,6 @@ function verifySkillSignatureOutcome(s, normalizedContent, pubKey) {
142
91
  return ok ? { status: "pass" } : { status: "fail" };
143
92
  }
144
93
 
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
94
  module.exports = {
149
95
  normalizeSkillBytes,
150
96
  verifyExtractedManifestSignature,
@@ -155,10 +101,8 @@ module.exports = {
155
101
  const ROOT = path.resolve(__dirname, "..");
156
102
 
157
103
  function emit(msg) { process.stdout.write(`[verify-shipped-tarball] ${msg}\n`); }
158
- // Sentinel thrown by fail() so the script body's try/finally still runs its
159
- // temp-dir cleanup. process.exit() would preempt the finally, leaking the
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.
104
+ // Sentinel thrown by fail() so the body's finally still runs its temp-dir
105
+ // cleanup — process.exit() would preempt it and leak the npm-pack temp dir.
162
106
  const ABORT = Symbol("verify-shipped-tarball:abort");
163
107
  function fail(msg, code = 1) {
164
108
  process.stderr.write(`[verify-shipped-tarball] FAIL: ${msg}\n`);
@@ -166,22 +110,15 @@ function fail(msg, code = 1) {
166
110
  throw ABORT;
167
111
  }
168
112
 
169
- // Gate the script body behind require.main === module so tests can
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.
113
+ // Gated on require.main so a test can require() the helpers without npm pack running.
173
114
  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
115
  return;
177
116
  }
178
117
 
179
118
  const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "verify-shipped-"));
180
119
  try {
181
120
  emit(`packing into ${tmpRoot} ...`);
182
- // F15 — pass --offline. Predeploy must run without registry
183
- // reachability; `npm pack` does not need the network for a local
184
- // package and forcing offline mode hard-locks the assumption.
121
+ // --offline: predeploy must run without registry reachability.
185
122
  const pack = spawnSync("npm", ["pack", "--offline", "--pack-destination", tmpRoot], {
186
123
  cwd: ROOT,
187
124
  encoding: "utf8",
@@ -192,9 +129,7 @@ try {
192
129
  }
193
130
  const tarballName = pack.stdout.trim().split(/\r?\n/).filter(Boolean).pop();
194
131
  const tarballPath = path.join(tmpRoot, tarballName);
195
- // Open the packed tarball once and fstat + read via the descriptor so the
196
- // existence check, size report, and read all observe the same inode — no
197
- // existsSync→statSync→readFileSync TOCTOU on the path.
132
+ // One descriptor for fstat + read, so both observe the same inode — no TOCTOU.
198
133
  let tgz;
199
134
  try {
200
135
  const fd = fs.openSync(tarballPath, "r");
@@ -229,12 +164,8 @@ try {
229
164
  }
230
165
  emit(`extracted to ${pkgRoot}`);
231
166
 
232
- // load the extracted tree's OWN parseTar and re-parse the
233
- // tarball. If the two parsers diverge on entry list or content, the
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.
167
+ // Re-parse with the extracted tree's OWN parseTar: a divergence means CI
168
+ // exercised a different extractor than operators will run.
238
169
  const shippedParserPath = path.join(pkgRoot, "lib", "refresh-network.js");
239
170
  if (!fs.existsSync(shippedParserPath)) {
240
171
  fail(`extracted tree missing lib/refresh-network.js (cannot run F9 cross-parse check)`, 2);
@@ -249,7 +180,6 @@ try {
249
180
  fail(`extracted lib/refresh-network.js does not export parseTar`, 2);
250
181
  }
251
182
  const shippedEntries = parseTarShipped(tarBuf);
252
- // Compare counts first — fast bailout.
253
183
  const divergences = [];
254
184
  if (shippedEntries.length !== entries.length) {
255
185
  divergences.push(
@@ -257,8 +187,7 @@ try {
257
187
  `shipped parser produced ${shippedEntries.length}`
258
188
  );
259
189
  } else {
260
- // Walk in parallel; tarball entry order is deterministic so positional
261
- // compare is correct. Compare name + byte length + body bytes.
190
+ // Tarball entry order is deterministic, so a positional compare is valid.
262
191
  for (let i = 0; i < entries.length; i++) {
263
192
  const a = entries[i];
264
193
  const b = shippedEntries[i];
@@ -288,9 +217,7 @@ try {
288
217
  }
289
218
  emit(`F9: source-tree and shipped parseTar agree on ${entries.length} entries`);
290
219
 
291
- // Run the verifier inline against the extracted package tree. This avoids
292
- // having to spawn a separate process whose cwd resolution differs across
293
- // platforms.
220
+ // Inline rather than spawned: cwd resolution differs across platforms.
294
221
  const crypto = require("crypto");
295
222
  const manifestPath = path.join(pkgRoot, "manifest.json");
296
223
  const pubKeyPath = path.join(pkgRoot, "keys", "public.pem");
@@ -301,16 +228,9 @@ try {
301
228
  const pubPem = fs.readFileSync(pubKeyPath, "utf8");
302
229
  const pubKey = crypto.createPublicKey(pubPem);
303
230
 
304
- // Verify the top-level manifest_signature on the EXTRACTED
305
- // manifest.json. Per-skill signatures only sign the skill body bytes —
306
- // they do not sign skill.name / skill.path / skill.atlas_refs or any
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.
231
+ // Per-skill signatures cover only the skill body bytes, not skill.name or
232
+ // skill.path — a rewritten manifest envelope still passes per-skill verify.
233
+ // A missing envelope signature is a refusal here, not a warning.
314
234
  const manifestSigStatus = verifyExtractedManifestSignature(manifest, pubPem);
315
235
  if (manifestSigStatus.status !== "valid") {
316
236
  fail(
@@ -324,8 +244,7 @@ try {
324
244
  .update(pubKey.export({ type: "spki", format: "der" }))
325
245
  .digest("base64");
326
246
 
327
- // Compute the same fingerprint for the SOURCE-tree public.pem so the log
328
- // shows the divergence explicitly.
247
+ // The same fingerprint for the SOURCE-tree key, so the log names the drift.
329
248
  const sourcePubPem = fs.readFileSync(path.join(ROOT, "keys", "public.pem"), "utf8");
330
249
  const sourcePubKey = crypto.createPublicKey(sourcePubPem);
331
250
  const sourcePubFp = crypto.createHash("sha256")
@@ -339,25 +258,17 @@ try {
339
258
  emit(`*** Something between sign and pack is swapping the key. Verify will fail below. ***`);
340
259
  }
341
260
 
342
- // key-pin cross-check against the EXTRACTED tree. The pin
343
- // is consumed from keys/EXPECTED_FINGERPRINT in the extracted package —
344
- // that's the file operators will actually receive on `npm install`.
345
- // Warn when absent, fail when present-but-mismatched (unless KEYS_ROTATED).
261
+ // The pin is read from the EXTRACTED tree — the file operators receive on
262
+ // install. Absent warns; mismatched fails unless KEYS_ROTATED=1.
346
263
  const expectedFpPath = path.join(pkgRoot, "keys", "EXPECTED_FINGERPRINT");
347
264
  if (fs.existsSync(expectedFpPath)) {
348
- // Route through the shared lib/verify loader so a BOM-prefixed pin
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 (`#`).
265
+ // The shared loader strips a leading U+FEFF, so a BOM-prefixed pin is tolerated.
352
266
  const { loadExpectedFingerprintFirstLine } = require(path.join(ROOT, "lib", "verify.js"));
353
267
  const firstLine = loadExpectedFingerprintFirstLine(expectedFpPath) || "";
354
268
  const liveFpLine = `SHA256:${pubFp}`;
355
269
  if (firstLine !== liveFpLine) {
356
270
  if (process.env.KEYS_ROTATED === "1") {
357
- // Surface the override through the structured warning channel as
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).
271
+ // Also on the structured warning channel, so a listener observes it.
361
272
  process.emitWarning(
362
273
  `extracted public.pem fingerprint ${liveFpLine} differs from pin ${firstLine}; KEYS_ROTATED=1 accepted. ` +
363
274
  `Update keys/EXPECTED_FINGERPRINT to lock the new pin.`,
@@ -389,23 +300,12 @@ try {
389
300
  failures.push(`${s.name}: file not found at ${s.path}`);
390
301
  continue;
391
302
  }
392
- // v0.12.16: the prior code passed the raw file bytes
393
- // directly to crypto.verify. lib/sign.js + lib/verify.js both NORMALIZE
394
- // bytes (strip UTF-8 BOM, convert CRLF -> LF) before sign/verify, per
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.
303
+ // Normalize before verify: lib/sign.js signs BOM-stripped, LF-normalized
304
+ // bytes, so feeding raw bytes here reports 0/N wherever line endings changed
305
+ // between sign and pack (`core.autocrlf=true`).
402
306
  const rawContent = fs.readFileSync(skillPath);
403
307
  const normalizedContent = normalizeSkillBytes(rawContent);
404
308
  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
309
  if (outcome.status === "missing") {
410
310
  miss++;
411
311
  failures.push(`${s.name}: no Ed25519 signature in manifest`);
@@ -415,13 +315,8 @@ try {
415
315
  if (ok) pass++;
416
316
  else {
417
317
  fail_count++;
418
- // Forensic detail: log size + sha256 of tarball-extracted content vs source-tree content
419
- // so we can pinpoint which bytes changed between npm pack and what was signed.
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.
318
+ // Sizes come from rawContent so an operator sees the on-disk shape, but
319
+ // the hashes cover the NORMALIZED bytes actually fed to crypto.verify.
425
320
  const tarSha = crypto.createHash("sha256").update(normalizedContent).digest("hex").slice(0, 16);
426
321
  let srcSha = "<missing>", srcSize = 0, srcContent;
427
322
  if (fs.existsSync(sourceSkillPath)) {
@@ -447,8 +342,7 @@ try {
447
342
  process.exitCode = 1;
448
343
  }
449
344
  } catch (e) {
450
- // ABORT is the fail() sentinel — cleanup still runs via finally below. Any
451
- // other error is unexpected: let finally run, then re-propagate it.
345
+ // ABORT is the fail() sentinel; any other error re-propagates after finally.
452
346
  if (e !== ABORT) throw e;
453
347
  } finally {
454
348
  // Best-effort cleanup; leave on failure for diagnostics.