@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
@@ -1,50 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  /*
3
- * scripts/refresh-sbom.js — regenerate sbom.cdx.json.
4
- *
5
- * The exceptd repository is zero-runtime-dependency by design (see
6
- * package.json `dependencies: {}`). The SBOM therefore documents the
7
- * project as an application component with an empty `components` array
8
- * and pulls live surface counts from manifest.json + data/*.json so the
9
- * artifact never silently drifts when the surface changes.
10
- *
11
- * Generated fields:
12
- * - bomFormat / specVersion CycloneDX 1.6
13
- * - serialNumber urn:uuid v4 derived from a stable
14
- * hash of (project name + version +
15
- * bundle digest), so identical content
16
- * reproduces the identical UUID and a
17
- * rerun that changed nothing is a no-op
18
- * rather than a spurious diff.
19
- * - metadata.timestamp the release date this version's
20
- * CHANGELOG heading declares — NOT the
21
- * wall-clock moment of generation, which
22
- * would make the artifact irreproducible
23
- * - metadata.tools this script itself, version pulled
24
- * from package.json at refresh time
25
- * - metadata.component application entry for exceptd-skills,
26
- * including a hashes[] bundle digest
27
- * that operators can recompute from
28
- * the per-file component list (see
29
- * `bundleDigest` below for the exact
30
- * canonical-input rule)
31
- * - metadata.properties catalog count, skill count, dataflow
32
- * inputs, and the per-skill Ed25519
33
- * integrity claim (lib/sign.js)
34
- * - components vendored libraries + a `type: file`
35
- * component per shipped file in the
36
- * package.json `files` allowlist, each
37
- * carrying its SHA-256 hash. Lets
38
- * CycloneDX-aware vuln scanners verify
39
- * individual files against the bundle
40
- * without re-deriving the canonical
41
- * list themselves.
42
- * - dependencies [] — nothing to depend on
43
- *
44
- * Run: node scripts/refresh-sbom.js
45
- * npm run refresh-sbom
46
- *
47
- * No external dependencies. Node 24 stdlib only.
3
+ * Regenerates sbom.cdx.json, a CycloneDX 1.6 bundle. Every generated field is
4
+ * deterministic — identical content reproduces an identical SBOM, which is what
5
+ * makes the currency gate's regenerate-and-compare mean anything.
48
6
  */
49
7
 
50
8
  'use strict';
@@ -71,9 +29,7 @@ function listDataCatalogs(dir) {
71
29
  .sort();
72
30
  }
73
31
 
74
- /* RFC 4122 v4 UUID derived deterministically from a seed string so a
75
- * given (project, version, timestamp) triple maps to a stable UUID
76
- * across observers. Uses crypto.randomUUID() fallback if no seed. */
32
+ /* RFC 4122 v4 UUID derived from a seed, so one seed maps to one UUID. */
77
33
  function uuidV4FromSeed(seed) {
78
34
  const hash = crypto.createHash('sha256').update(seed).digest();
79
35
  const b = Buffer.from(hash.subarray(0, 16));
@@ -103,19 +59,7 @@ function loadVendorProvenance() {
103
59
  }
104
60
  }
105
61
 
106
- /* Recursively expand a `package.json.files` allowlist entry into the
107
- * concrete file list that npm pack would ship. The allowlist accepts
108
- * either a file path or a directory path (with trailing slash convention
109
- * inside this repo); directories expand to every regular file beneath
110
- * them. Returned paths are POSIX-style relative to REPO_ROOT so the
111
- * SHA-256 input is stable across operating systems.
112
- *
113
- * Mirrors npm's pack-time inclusion rules at the level of fidelity this
114
- * SBOM needs (a deeper match — .npmignore, package-lock fields, npm-CLI
115
- * defaults — is intentionally out of scope: any divergence here surfaces
116
- * as a SHA mismatch on the predeploy verify-shipped-tarball gate, which
117
- * is the authoritative consumer-side check).
118
- */
62
+ /* Every regular file beneath absDir, in name order for a stable inventory. */
119
63
  function walkFiles(absDir) {
120
64
  const out = [];
121
65
  const entries = fs.readdirSync(absDir, { withFileTypes: true });
@@ -130,42 +74,18 @@ function walkFiles(absDir) {
130
74
  return out;
131
75
  }
132
76
 
133
- /* Files that cannot have a stable SHA inside the SBOM they belong to.
134
- * `sbom.cdx.json` is the obvious self-reference: hashing it would always
135
- * be stale the moment the SBOM gets written back. The bundle digest in
136
- * metadata.component.hashes[] covers everything ELSE that ships and is
137
- * the operator's verification anchor for the bundle as a whole. */
77
+ /* No stable SHA inside the SBOM that contains it: the hash is stale on write. */
138
78
  const SELF_EXCLUDED = new Set(['sbom.cdx.json']);
139
79
 
140
- /* Path prefixes whose contents are derivable / cache-class artifacts.
141
- * `data/_indexes/` is the pre-computed index cache that ships in the
142
- * tarball but is regenerated by `npm run build-indexes`. The test suite
143
- * deliberately mutates these files (build-incremental.test.js,
144
- * indexes-v070.test.js), so per-file SHA verification would race against
145
- * any test run that touches the cache between refresh-sbom and the
146
- * verification gate. The bundle digest at metadata.component.hashes[] is
147
- * computed from a SBOM-generation-time snapshot of all OTHER files; the
148
- * cache is excluded from the per-file inventory entirely. Predeploy's
149
- * `Pre-computed indexes freshness` gate is the authoritative consumer-
150
- * side check for the cache. */
80
+ /* Derivable cache artifacts, kept out of the per-file inventory: the test suite
81
+ * mutates data/_indexes/, so a pinned per-file hash would race any run that
82
+ * touches the cache. Predeploy's index-freshness gate covers them instead. */
151
83
  const DERIVABLE_PREFIXES = ['data/_indexes/'];
152
84
 
153
- /* Files npm puts in every tarball regardless of the `files` allowlist.
154
- * package.json is never listed in `files` (npm adds it unconditionally), so
155
- * expanding the allowlist alone left the one file that declares the bin
156
- * entrypoint, the engines floor and the dependency set outside the hashed
157
- * inventory — the SBOM described 247 of the 268 files an operator receives and
158
- * said nothing about the gap. It is not derivable and not self-referential, so
159
- * nothing but the omission kept it out. README.md and LICENSE get the same
160
- * unconditional treatment from npm but are already named in `files`, so a
161
- * union covers the general rule without double-counting them. sources/README.md
162
- * is here for the same reason: npm collects README files it finds, `files` never
163
- * names that directory, and it shipped unhashed.
164
- *
165
- * This list is maintained by hand, which is only safe because something else
166
- * checks it: lib/validate-package.js compares the real `npm pack` output against
167
- * this SBOM, so a file npm decides to ship that is missing here fails a gate
168
- * instead of shipping silently. */
85
+ /* Files npm ships whatever the `files` allowlist says, unioned in so they are
86
+ * hashed too. The list is hand-maintained, which is only safe because
87
+ * lib/validate-package.js compares the real `npm pack` output against this SBOM
88
+ * — a shipped file missing here fails a gate. */
169
89
  const ALWAYS_SHIPPED = ['package.json', 'sources/README.md'];
170
90
 
171
91
  function isDerivable(rel) {
@@ -184,9 +104,9 @@ function expandAllowlist(allowlist) {
184
104
  abs.push(full);
185
105
  }
186
106
  }
187
- // dedupe + sort by relative POSIX path for deterministic output;
188
- // strip self-referential entries (see SELF_EXCLUDED) and derivable cache
189
- // entries (see DERIVABLE_PREFIXES).
107
+ // Deduped and sorted by POSIX-relative path, so the SHA-256 input is the same
108
+ // on every operating system. npm's pack rules are matched only as far as this
109
+ // SBOM needs; verify-shipped-tarball is the authoritative check.
190
110
  const rel = Array.from(new Set(abs.map((a) => toPosixRel(a))))
191
111
  .filter((r) => !SELF_EXCLUDED.has(r))
192
112
  .filter((r) => !isDerivable(r))
@@ -195,22 +115,10 @@ function expandAllowlist(allowlist) {
195
115
  }
196
116
 
197
117
  /* metadata.timestamp — the release date this version's CHANGELOG heading
198
- * declares, as an ISO-8601 instant.
199
- *
200
- * The field has to be deterministic: the SBOM-currency gate compares the
201
- * committed artifact against a freshly generated one, so a wall-clock value
202
- * would differ on every run and the comparison could never mean anything.
203
- * The previous approach kept determinism by folding the bundle hash into a
204
- * date offset, but the offset was a full uint32 of SECONDS — a 136-year
205
- * spread — so the field routinely landed a century or more in the future
206
- * (the shipped value read 2147-11-20). Deterministic, and untrue: consumers
207
- * that sort or age-check SBOMs read that as a real creation date.
208
- *
209
- * The CHANGELOG heading is both deterministic and true. Its format is already
210
- * enforced by scripts/check-changelog-extract.js, which the release flow runs
211
- * before this script, so the date is guaranteed present by the time a release
212
- * regenerates the SBOM. Refusing is deliberate when it is absent — inventing a
213
- * placeholder is what produced the 2147 date in the first place. */
118
+ * declares, as an ISO-8601 instant. It must be deterministic for the currency
119
+ * gate's regenerate-and-compare, and true because consumers age-check on it, so
120
+ * a missing heading throws rather than synthesizing a value.
121
+ * scripts/check-changelog-extract.js enforces the heading format. */
214
122
  function releaseTimestamp(version) {
215
123
  const changelogPath = path.join(REPO_ROOT, 'CHANGELOG.md');
216
124
  const text = fs.readFileSync(changelogPath, 'utf8');
@@ -240,22 +148,9 @@ function sha256File(absPath) {
240
148
  .digest('hex');
241
149
  }
242
150
 
243
- // v0.13.12 — emit SHA3-512 alongside SHA-256 for every file: component.
244
- // CycloneDX 1.6 supports multiple hash entries per component. Rationale
245
- // mirrors the existing key-fingerprint emission in lib/verify.js:
246
- //
247
- // - SHA-256 stays as the universal-tool contract (Anchore / Trivy /
248
- // Dependency-Track / GitHub Dependency Graph all parse it).
249
- // - SHA3-512 is the SHA-3 family (Keccak / sponge), different
250
- // mathematical foundation. Hedges against future SHA-2 weaknesses
251
- // and aligns with the project's PQ posture (ML-KEM / ML-DSA both
252
- // internally hash with SHA-3).
253
- //
254
- // check-sbom-currency.js verifies BOTH when present and refuses if a
255
- // SHA3-512 entry is recorded but its content drifts from the live
256
- // bytes — so a downgrade attack that drops SHA3-512 from the recorded
257
- // SBOM (leaving only SHA-256) is observable as a missing-hash error,
258
- // not a silent acceptance.
151
+ // SHA3-512 sits alongside SHA-256 on every file component: a different
152
+ // construction to fall back on. check-sbom-currency.js verifies both, so
153
+ // dropping the SHA3-512 entry reads as a missing-hash error, not acceptance.
259
154
  function sha3_512File(absPath) {
260
155
  return crypto
261
156
  .createHash('sha3-512')
@@ -281,12 +176,9 @@ function fileComponents(allowlist) {
281
176
  return out;
282
177
  }
283
178
 
284
- /* Bundle digest = SHA-256 over a deterministic newline-delimited
285
- * "<sha256>\t<relpath>\n" stream of every shipped file, sorted by
286
- * relpath. The same input shape an operator would assemble from the
287
- * components[] list (`type: file` entries) lets them recompute and
288
- * compare without trusting the SBOM's stored value blindly.
289
- */
179
+ /* SHA-256 over a "<sha256>\t<relpath>\n" stream of every shipped file, sorted by
180
+ * relpath — the same input an operator assembles from the `type: file` entries
181
+ * in components[], so the stored digest can be recomputed rather than trusted. */
290
182
  function bundleDigest(fileComps) {
291
183
  const sorted = [...fileComps].sort((a, b) =>
292
184
  a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
@@ -337,25 +229,12 @@ function buildSbom() {
337
229
  const fileComps = fileComponents(Array.isArray(pkg.files) ? pkg.files : []);
338
230
  const bundleSha = bundleDigest(fileComps);
339
231
 
340
- // Sort the union of vendor + file components by bom-ref for
341
- // deterministic regeneration.
232
+ // Sorted by bom-ref so regeneration is byte-identical.
342
233
  const allComponents = [...vendoredComponents, ...fileComps].sort((a, b) =>
343
234
  a['bom-ref'] < b['bom-ref'] ? -1 : a['bom-ref'] > b['bom-ref'] ? 1 : 0,
344
235
  );
345
236
 
346
- // v0.13.0: derive both serialNumber and metadata.timestamp from the
347
- // bundle content hash, not wall-clock. Pre-v0.13 every refresh produced
348
- // a new UUID + timestamp even when the bundle content was byte-identical,
349
- // so the SBOM-currency gate produced noisy diffs and the predeploy
350
- // comparison could not rely on stable byte-identity. The comment at the
351
- // top of this file says "stable across observers" — the implementation
352
- // contradicted it. Now: identical content → identical SBOM.
353
- //
354
- // The synthetic timestamp uses the bundle SHA folded into a date string
355
- // anchored at the Unix epoch + a deterministic offset; this is NOT a
356
- // real audit timestamp (the `metadata.lifecycles[]` block carries the
357
- // intended-lifecycle phase for that). Operators wanting the wall-clock
358
- // time of a refresh should read the file's mtime or refresh-report.json.
237
+ // Seeded from the bundle content, not the clock: a no-op refresh is a no-op.
359
238
  const seed = `${pkg.name}@${pkg.version}@${bundleSha}`;
360
239
  const serialNumber = 'urn:uuid:' + uuidV4FromSeed(seed);
361
240
  const timestamp = releaseTimestamp(pkg.version);
@@ -379,9 +258,6 @@ function buildSbom() {
379
258
  },
380
259
  ],
381
260
  component: {
382
- // Switch from project: scheme to pkg:npm scheme post-v0.9.0 — the
383
- // package is now published on npm with provenance attestation, so
384
- // the CycloneDX bom-ref should reflect the canonical PURL.
385
261
  'bom-ref': `pkg:npm/${pkg.name}@${pkg.version}`,
386
262
  type: 'application',
387
263
  name: pkg.name,
@@ -389,10 +265,7 @@ function buildSbom() {
389
265
  description: pkg.description,
390
266
  licenses: [{ license: { id: 'Apache-2.0' } }],
391
267
  purl: `pkg:npm/${pkg.name.replace(/@/g, '%40')}@${pkg.version}`,
392
- // Bundle digest over every shipped file (see bundleDigest above
393
- // for the canonical-input rule). Operators can recompute this
394
- // from the per-file components[] list and compare without
395
- // re-deriving package.json.files themselves.
268
+ // Recomputable from components[]; bundleDigest above states the input rule.
396
269
  hashes: [{ alg: 'SHA-256', content: bundleSha }],
397
270
  externalReferences: [
398
271
  { type: 'distribution', url: `https://www.npmjs.com/package/${pkg.name}/v/${pkg.version}` },
@@ -416,11 +289,8 @@ function buildSbom() {
416
289
  name: 'exceptd:integrity:method',
417
290
  value: 'Ed25519 per-skill (lib/sign.js)',
418
291
  },
419
- // An operator verifying the bundle holds the SBOM, not this script.
420
- // Without these two properties the only record of what the inventory
421
- // deliberately omits was a source comment they never see, so a partial
422
- // inventory was indistinguishable from a complete one. State the
423
- // uncovered prefix and the check that covers it instead.
292
+ // An operator verifying the bundle holds the SBOM, not this script: these
293
+ // two properties say the inventory is partial by design, and what covers it.
424
294
  {
425
295
  name: 'exceptd:integrity:uncovered:prefix',
426
296
  value: DERIVABLE_PREFIXES.join(','),