@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.
Files changed (119) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/bin/exceptd.js +896 -2824
  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 +20 -54
  8. package/lib/collectors/cicd-pipeline-compromise.js +40 -108
  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 +57 -206
  17. package/lib/collectors/mcp.js +24 -70
  18. package/lib/collectors/runtime.js +24 -86
  19. package/lib/collectors/sbom.js +34 -106
  20. package/lib/collectors/scan-excludes.js +31 -138
  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 -152
  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 +35 -114
  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 +64 -232
  37. package/lib/playbook-runner.js +693 -2095
  38. package/lib/prefetch.js +100 -376
  39. package/lib/refresh-external.js +199 -627
  40. package/lib/refresh-network.js +75 -307
  41. package/lib/rfc-cli.js +23 -68
  42. package/lib/scoring.js +77 -145
  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 +14 -24
  48. package/lib/upstream-check-cli.js +10 -28
  49. package/lib/upstream-check.js +19 -44
  50. package/lib/validate-catalog-meta.js +17 -61
  51. package/lib/validate-cve-catalog.js +43 -119
  52. package/lib/validate-indexes.js +25 -76
  53. package/lib/validate-package.js +16 -62
  54. package/lib/validate-playbooks.js +69 -275
  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 -412
  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 +241 -241
  69. package/scripts/audit-catalog-gaps.js +9 -62
  70. package/scripts/audit-cross-skill.js +5 -31
  71. package/scripts/audit-perf.js +6 -16
  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 +12 -27
  87. package/scripts/builders/token-budget.js +4 -31
  88. package/scripts/check-agents-md-collectors.js +11 -54
  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 +50 -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 +13 -73
  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 +66 -197
  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 +3 -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 +3 -8
  110. package/scripts/refresh-reverse-refs.js +27 -94
  111. package/scripts/refresh-rfc-index.js +2 -10
  112. package/scripts/refresh-sbom.js +31 -161
  113. package/scripts/refresh-upstream-catalogs.js +40 -137
  114. package/scripts/release.js +69 -232
  115. package/scripts/run-e2e-scenarios.js +24 -71
  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 -140
@@ -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") {
@@ -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
- // 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
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 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.
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
- // 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.
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
- // 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.
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
- // 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.
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
- // 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.
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
- // Walk in parallel; tarball entry order is deterministic so positional
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
- // 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.
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
- // 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.
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
- // Compute the same fingerprint for the SOURCE-tree public.pem so the log
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
- // 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).
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
- // 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 (`#`).
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
- // 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).
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
- // 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.
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
- // 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.
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 — cleanup still runs via finally below. Any
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.