@blamejs/exceptd-skills 0.12.28 → 0.12.30
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/AGENTS.md +1 -1
- package/CHANGELOG.md +53 -0
- package/bin/exceptd.js +30 -20
- package/data/_indexes/_meta.json +9 -9
- package/data/_indexes/activity-feed.json +7 -7
- package/data/_indexes/chains.json +9 -9
- package/data/_indexes/currency.json +43 -43
- package/data/_indexes/stale-content.json +1 -1
- package/data/atlas-ttps.json +61 -111
- package/data/cve-catalog.json +136 -65
- package/data/cwe-catalog.json +151 -95
- package/data/d3fend-catalog.json +201 -54
- package/data/dlp-controls.json +2 -1
- package/data/framework-control-gaps.json +1214 -110
- package/data/playbooks/crypto-codebase.json +1 -1
- package/data/rfc-references.json +23 -67
- package/lib/exit-codes.js +2 -0
- package/lib/playbook-runner.js +25 -1
- package/manifest-snapshot.json +2 -2
- package/manifest-snapshot.sha256 +1 -1
- package/manifest.json +49 -48
- package/package.json +3 -2
- package/sbom.cdx.json +1853 -10
- package/scripts/backfill-theater-test.js +806 -0
- package/scripts/check-test-coverage.js +18 -4
- package/scripts/refresh-reverse-refs.js +171 -0
- package/scripts/refresh-sbom.js +155 -8
|
@@ -184,15 +184,29 @@ function readMaybe(p) {
|
|
|
184
184
|
|
|
185
185
|
// --- Categorization ---------------------------------------------------------
|
|
186
186
|
|
|
187
|
+
// Mechanical / contributor-only docs the gate auto-allows: their content
|
|
188
|
+
// has no operator-facing semantic surface (CONTRIBUTING is for PRs;
|
|
189
|
+
// LICENSE / NOTICE / CODE_OF_CONDUCT are boilerplate; .gitignore / .npmrc
|
|
190
|
+
// / .editorconfig are tooling). Edits here never need a regression test.
|
|
187
191
|
const DOCS_ALWAYS_GREEN = new Set([
|
|
188
|
-
"
|
|
189
|
-
"
|
|
190
|
-
|
|
192
|
+
"CONTRIBUTING.md", "LICENSE", "NOTICE", "CODE_OF_CONDUCT.md",
|
|
193
|
+
"CLAUDE.md", "SUPPORT.md", ".gitignore", ".npmrc", ".editorconfig",
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
// Cycle 9 finding: operator-facing docs (release notes, install instructions,
|
|
197
|
+
// security disclosure policy, migration guides, AI-assistant ground truth)
|
|
198
|
+
// previously auto-greened. A PR could land deceptive copy here without any
|
|
199
|
+
// reviewer signal. Downgrade to manual-review so the diff surfaces in the
|
|
200
|
+
// gate output — a human (or the maintainer reviewing the bot summary) at
|
|
201
|
+
// least sees the change exists.
|
|
202
|
+
const DOCS_MANUAL_REVIEW = new Set([
|
|
203
|
+
"CHANGELOG.md", "README.md", "SECURITY.md", "MIGRATING.md", "AGENTS.md",
|
|
191
204
|
]);
|
|
192
205
|
|
|
193
206
|
function categorize(file) {
|
|
194
207
|
const norm = file.replace(/\\/g, "/");
|
|
195
208
|
if (DOCS_ALWAYS_GREEN.has(norm)) return "docs";
|
|
209
|
+
if (DOCS_MANUAL_REVIEW.has(norm)) return "manual-review";
|
|
196
210
|
if (norm.startsWith("tests/")) return "test"; // no recursion
|
|
197
211
|
if (norm.startsWith("docs/")) return "docs";
|
|
198
212
|
if (norm.endsWith(".md") && !norm.startsWith("data/")) return "docs";
|
|
@@ -662,5 +676,5 @@ module.exports = {
|
|
|
662
676
|
extractCliSurface, extractLibExports, extractPlaybookIds, extractCveIocChanges,
|
|
663
677
|
coversCliVerb, coversCliFlag, coversLibExport, coversPlaybookId, coversCveIoc,
|
|
664
678
|
scanForCoincidenceAsserts,
|
|
665
|
-
DOCS_ALWAYS_GREEN,
|
|
679
|
+
DOCS_ALWAYS_GREEN, DOCS_MANUAL_REVIEW,
|
|
666
680
|
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* scripts/refresh-reverse-refs.js — rebuild reverse references in
|
|
4
|
+
* data/{atlas-ttps,cwe-catalog,d3fend-catalog,rfc-references}.json from
|
|
5
|
+
* the manifest.json forward direction.
|
|
6
|
+
*
|
|
7
|
+
* Background. Each skill in manifest.json declares forward references via
|
|
8
|
+
* atlas_refs / cwe_refs / d3fend_refs / rfc_refs. The four catalogs above
|
|
9
|
+
* carry a denormalised reverse field per entry (`exceptd_skills` for
|
|
10
|
+
* atlas-ttps, `skills_referencing` for the other three) listing every
|
|
11
|
+
* skill that points at that entry. The reverse field drifts whenever a
|
|
12
|
+
* skill adds or removes a forward ref without the catalog being updated
|
|
13
|
+
* in lockstep — Cycle 9 audit found this drift in production.
|
|
14
|
+
*
|
|
15
|
+
* Behaviour. For each catalog file:
|
|
16
|
+
* 1. Walk every skill's relevant forward-ref array in manifest.json.
|
|
17
|
+
* 2. For every catalog entry, list every skill that references it.
|
|
18
|
+
* 3. Sort the resulting skill list and write it back into the per-entry
|
|
19
|
+
* reverse field. All other fields are preserved untouched.
|
|
20
|
+
*
|
|
21
|
+
* The script is idempotent: a second run produces no further changes.
|
|
22
|
+
*
|
|
23
|
+
* The script does NOT touch playbooks_referencing — that field carries
|
|
24
|
+
* playbook ids (data/playbooks/*.json), not skill names; it has its own
|
|
25
|
+
* source of truth and is out of scope for this audit fix.
|
|
26
|
+
*
|
|
27
|
+
* Run: node scripts/refresh-reverse-refs.js
|
|
28
|
+
* npm run refresh-reverse-refs
|
|
29
|
+
*
|
|
30
|
+
* Exit code: 0 always (script is unconditionally write-mode). Use
|
|
31
|
+
* tests/reverse-ref-drift.test.js as the read-only drift detector.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
'use strict';
|
|
35
|
+
|
|
36
|
+
const fs = require('node:fs');
|
|
37
|
+
const path = require('node:path');
|
|
38
|
+
|
|
39
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
40
|
+
const MANIFEST_PATH = path.join(REPO_ROOT, 'manifest.json');
|
|
41
|
+
const DATA_DIR = path.join(REPO_ROOT, 'data');
|
|
42
|
+
|
|
43
|
+
/* Per-catalog config:
|
|
44
|
+
* file relative path under data/
|
|
45
|
+
* forwardField manifest.skills[].* array name
|
|
46
|
+
* reverseField per-entry reverse field name in the catalog
|
|
47
|
+
*/
|
|
48
|
+
const CATALOGS = [
|
|
49
|
+
{
|
|
50
|
+
file: 'atlas-ttps.json',
|
|
51
|
+
forwardField: 'atlas_refs',
|
|
52
|
+
reverseField: 'exceptd_skills',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
file: 'cwe-catalog.json',
|
|
56
|
+
forwardField: 'cwe_refs',
|
|
57
|
+
reverseField: 'skills_referencing',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
file: 'd3fend-catalog.json',
|
|
61
|
+
forwardField: 'd3fend_refs',
|
|
62
|
+
reverseField: 'skills_referencing',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
file: 'rfc-references.json',
|
|
66
|
+
forwardField: 'rfc_refs',
|
|
67
|
+
reverseField: 'skills_referencing',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
function readJson(p) {
|
|
72
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildReverseIndex(skills, forwardField) {
|
|
76
|
+
// entryId -> Set<skillName>
|
|
77
|
+
const index = new Map();
|
|
78
|
+
for (const skill of skills) {
|
|
79
|
+
const refs = Array.isArray(skill[forwardField]) ? skill[forwardField] : [];
|
|
80
|
+
for (const id of refs) {
|
|
81
|
+
if (!index.has(id)) index.set(id, new Set());
|
|
82
|
+
index.get(id).add(skill.name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return index;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function rebuildCatalog(cfg, manifest) {
|
|
89
|
+
const filePath = path.join(DATA_DIR, cfg.file);
|
|
90
|
+
const catalog = readJson(filePath);
|
|
91
|
+
const index = buildReverseIndex(manifest.skills, cfg.forwardField);
|
|
92
|
+
let changed = 0;
|
|
93
|
+
let added = 0;
|
|
94
|
+
let removed = 0;
|
|
95
|
+
let unchanged = 0;
|
|
96
|
+
const orphans = []; // forward refs that don't resolve to a catalog entry
|
|
97
|
+
const seenIds = new Set();
|
|
98
|
+
|
|
99
|
+
for (const [id, entry] of Object.entries(catalog)) {
|
|
100
|
+
if (id === '_meta') continue;
|
|
101
|
+
if (typeof entry !== 'object' || entry === null) continue;
|
|
102
|
+
seenIds.add(id);
|
|
103
|
+
const before = Array.isArray(entry[cfg.reverseField])
|
|
104
|
+
? [...entry[cfg.reverseField]]
|
|
105
|
+
: [];
|
|
106
|
+
const computed = index.has(id)
|
|
107
|
+
? Array.from(index.get(id)).sort()
|
|
108
|
+
: [];
|
|
109
|
+
const beforeSet = new Set(before);
|
|
110
|
+
const computedSet = new Set(computed);
|
|
111
|
+
const sameLen = before.length === computed.length;
|
|
112
|
+
const sameContent =
|
|
113
|
+
sameLen && before.every((s, i) => s === computed[i]);
|
|
114
|
+
if (!sameContent) {
|
|
115
|
+
entry[cfg.reverseField] = computed;
|
|
116
|
+
changed += 1;
|
|
117
|
+
for (const s of computed) if (!beforeSet.has(s)) added += 1;
|
|
118
|
+
for (const s of before) if (!computedSet.has(s)) removed += 1;
|
|
119
|
+
} else {
|
|
120
|
+
unchanged += 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Surface forward refs that point at catalog entries that don't exist.
|
|
125
|
+
// Not fatal here — that's a separate validation concern — but we report.
|
|
126
|
+
for (const id of index.keys()) {
|
|
127
|
+
if (!seenIds.has(id)) orphans.push(id);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (changed > 0) {
|
|
131
|
+
fs.writeFileSync(filePath, JSON.stringify(catalog, null, 2) + '\n', 'utf8');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
file: cfg.file,
|
|
136
|
+
changed,
|
|
137
|
+
added,
|
|
138
|
+
removed,
|
|
139
|
+
unchanged,
|
|
140
|
+
orphans,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function main() {
|
|
145
|
+
const manifest = readJson(MANIFEST_PATH);
|
|
146
|
+
const results = [];
|
|
147
|
+
for (const cfg of CATALOGS) {
|
|
148
|
+
results.push(rebuildCatalog(cfg, manifest));
|
|
149
|
+
}
|
|
150
|
+
for (const r of results) {
|
|
151
|
+
process.stdout.write(
|
|
152
|
+
`${r.file}: ${r.changed} entries changed ` +
|
|
153
|
+
`(+${r.added} / -${r.removed} skill refs), ` +
|
|
154
|
+
`${r.unchanged} unchanged` +
|
|
155
|
+
(r.orphans.length
|
|
156
|
+
? `, ${r.orphans.length} orphan forward ref(s) [${r.orphans.join(', ')}]`
|
|
157
|
+
: '') +
|
|
158
|
+
'\n',
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
CATALOGS,
|
|
165
|
+
buildReverseIndex,
|
|
166
|
+
rebuildCatalog,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (require.main === module) {
|
|
170
|
+
main();
|
|
171
|
+
}
|
package/scripts/refresh-sbom.js
CHANGED
|
@@ -15,12 +15,25 @@
|
|
|
15
15
|
* timestamp) so reruns produce a new
|
|
16
16
|
* UUID per refresh.
|
|
17
17
|
* - metadata.timestamp ISO 8601 of generation
|
|
18
|
-
* - metadata.tools
|
|
19
|
-
*
|
|
18
|
+
* - metadata.tools this script itself, version pulled
|
|
19
|
+
* from package.json at refresh time
|
|
20
|
+
* - metadata.component application entry for exceptd-skills,
|
|
21
|
+
* including a hashes[] bundle digest
|
|
22
|
+
* that operators can recompute from
|
|
23
|
+
* the per-file component list (see
|
|
24
|
+
* `bundleDigest` below for the exact
|
|
25
|
+
* canonical-input rule)
|
|
20
26
|
* - metadata.properties catalog count, skill count, dataflow
|
|
21
27
|
* inputs, and the per-skill Ed25519
|
|
22
28
|
* integrity claim (lib/sign.js)
|
|
23
|
-
* - components
|
|
29
|
+
* - components vendored libraries + a `type: file`
|
|
30
|
+
* component per shipped file in the
|
|
31
|
+
* package.json `files` allowlist, each
|
|
32
|
+
* carrying its SHA-256 hash. Lets
|
|
33
|
+
* CycloneDX-aware vuln scanners verify
|
|
34
|
+
* individual files against the bundle
|
|
35
|
+
* without re-deriving the canonical
|
|
36
|
+
* list themselves.
|
|
24
37
|
* - dependencies [] — nothing to depend on
|
|
25
38
|
*
|
|
26
39
|
* Run: node scripts/refresh-sbom.js
|
|
@@ -85,6 +98,128 @@ function loadVendorProvenance() {
|
|
|
85
98
|
}
|
|
86
99
|
}
|
|
87
100
|
|
|
101
|
+
/* Recursively expand a `package.json.files` allowlist entry into the
|
|
102
|
+
* concrete file list that npm pack would ship. The allowlist accepts
|
|
103
|
+
* either a file path or a directory path (with trailing slash convention
|
|
104
|
+
* inside this repo); directories expand to every regular file beneath
|
|
105
|
+
* them. Returned paths are POSIX-style relative to REPO_ROOT so the
|
|
106
|
+
* SHA-256 input is stable across operating systems.
|
|
107
|
+
*
|
|
108
|
+
* Mirrors npm's pack-time inclusion rules at the level of fidelity this
|
|
109
|
+
* SBOM needs (a deeper match — .npmignore, package-lock fields, npm-CLI
|
|
110
|
+
* defaults — is intentionally out of scope: any divergence here surfaces
|
|
111
|
+
* as a SHA mismatch on the predeploy verify-shipped-tarball gate, which
|
|
112
|
+
* is the authoritative consumer-side check).
|
|
113
|
+
*/
|
|
114
|
+
function walkFiles(absDir) {
|
|
115
|
+
const out = [];
|
|
116
|
+
const entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
117
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
118
|
+
const abs = path.join(absDir, entry.name);
|
|
119
|
+
if (entry.isDirectory()) {
|
|
120
|
+
out.push(...walkFiles(abs));
|
|
121
|
+
} else if (entry.isFile()) {
|
|
122
|
+
out.push(abs);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Files that cannot have a stable SHA inside the SBOM they belong to.
|
|
129
|
+
* `sbom.cdx.json` is the obvious self-reference: hashing it would always
|
|
130
|
+
* be stale the moment the SBOM gets written back. The bundle digest in
|
|
131
|
+
* metadata.component.hashes[] covers everything ELSE that ships and is
|
|
132
|
+
* the operator's verification anchor for the bundle as a whole. */
|
|
133
|
+
const SELF_EXCLUDED = new Set(['sbom.cdx.json']);
|
|
134
|
+
|
|
135
|
+
/* Path prefixes whose contents are derivable / cache-class artifacts.
|
|
136
|
+
* `data/_indexes/` is the pre-computed index cache that ships in the
|
|
137
|
+
* tarball but is regenerated by `npm run build-indexes`. The test suite
|
|
138
|
+
* deliberately mutates these files (build-incremental.test.js,
|
|
139
|
+
* indexes-v070.test.js), so per-file SHA verification would race against
|
|
140
|
+
* any test run that touches the cache between refresh-sbom and the
|
|
141
|
+
* verification gate. The bundle digest at metadata.component.hashes[] is
|
|
142
|
+
* computed from a SBOM-generation-time snapshot of all OTHER files; the
|
|
143
|
+
* cache is excluded from the per-file inventory entirely. Predeploy's
|
|
144
|
+
* `Pre-computed indexes freshness` gate is the authoritative consumer-
|
|
145
|
+
* side check for the cache. */
|
|
146
|
+
const DERIVABLE_PREFIXES = ['data/_indexes/'];
|
|
147
|
+
|
|
148
|
+
function isDerivable(rel) {
|
|
149
|
+
return DERIVABLE_PREFIXES.some((p) => rel === p.replace(/\/$/, '') || rel.startsWith(p));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function expandAllowlist(allowlist) {
|
|
153
|
+
const abs = [];
|
|
154
|
+
for (const entry of allowlist) {
|
|
155
|
+
const full = path.join(REPO_ROOT, entry);
|
|
156
|
+
if (!fs.existsSync(full)) continue; // tolerate a stale entry; predeploy gate flags
|
|
157
|
+
const stat = fs.statSync(full);
|
|
158
|
+
if (stat.isDirectory()) {
|
|
159
|
+
abs.push(...walkFiles(full));
|
|
160
|
+
} else if (stat.isFile()) {
|
|
161
|
+
abs.push(full);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// dedupe + sort by relative POSIX path for deterministic output;
|
|
165
|
+
// strip self-referential entries (see SELF_EXCLUDED) and derivable cache
|
|
166
|
+
// entries (see DERIVABLE_PREFIXES).
|
|
167
|
+
const rel = Array.from(new Set(abs.map((a) => toPosixRel(a))))
|
|
168
|
+
.filter((r) => !SELF_EXCLUDED.has(r))
|
|
169
|
+
.filter((r) => !isDerivable(r))
|
|
170
|
+
.sort();
|
|
171
|
+
return rel;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function toPosixRel(absPath) {
|
|
175
|
+
return path
|
|
176
|
+
.relative(REPO_ROOT, absPath)
|
|
177
|
+
.split(path.sep)
|
|
178
|
+
.join('/');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function sha256File(absPath) {
|
|
182
|
+
return crypto
|
|
183
|
+
.createHash('sha256')
|
|
184
|
+
.update(fs.readFileSync(absPath))
|
|
185
|
+
.digest('hex');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function fileComponents(allowlist) {
|
|
189
|
+
const rels = expandAllowlist(allowlist);
|
|
190
|
+
const out = [];
|
|
191
|
+
for (const rel of rels) {
|
|
192
|
+
const abs = path.join(REPO_ROOT, rel);
|
|
193
|
+
out.push({
|
|
194
|
+
'bom-ref': `file:${rel}`,
|
|
195
|
+
type: 'file',
|
|
196
|
+
name: rel,
|
|
197
|
+
hashes: [{ alg: 'SHA-256', content: sha256File(abs) }],
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Bundle digest = SHA-256 over a deterministic newline-delimited
|
|
204
|
+
* "<sha256>\t<relpath>\n" stream of every shipped file, sorted by
|
|
205
|
+
* relpath. The same input shape an operator would assemble from the
|
|
206
|
+
* components[] list (`type: file` entries) lets them recompute and
|
|
207
|
+
* compare without trusting the SBOM's stored value blindly.
|
|
208
|
+
*/
|
|
209
|
+
function bundleDigest(fileComps) {
|
|
210
|
+
const sorted = [...fileComps].sort((a, b) =>
|
|
211
|
+
a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
|
|
212
|
+
);
|
|
213
|
+
const hash = crypto.createHash('sha256');
|
|
214
|
+
for (const c of sorted) {
|
|
215
|
+
hash.update(c.hashes[0].content);
|
|
216
|
+
hash.update('\t');
|
|
217
|
+
hash.update(c.name);
|
|
218
|
+
hash.update('\n');
|
|
219
|
+
}
|
|
220
|
+
return hash.digest('hex');
|
|
221
|
+
}
|
|
222
|
+
|
|
88
223
|
function vendorComponents(prov) {
|
|
89
224
|
if (!prov || !prov.files) return [];
|
|
90
225
|
const out = [];
|
|
@@ -119,6 +254,14 @@ function buildSbom() {
|
|
|
119
254
|
const catalogCount = catalogs.length;
|
|
120
255
|
const vendorProv = loadVendorProvenance();
|
|
121
256
|
const vendoredComponents = vendorComponents(vendorProv);
|
|
257
|
+
const fileComps = fileComponents(Array.isArray(pkg.files) ? pkg.files : []);
|
|
258
|
+
const bundleSha = bundleDigest(fileComps);
|
|
259
|
+
|
|
260
|
+
// Sort the union of vendor + file components by bom-ref for
|
|
261
|
+
// deterministic regeneration.
|
|
262
|
+
const allComponents = [...vendoredComponents, ...fileComps].sort((a, b) =>
|
|
263
|
+
a['bom-ref'] < b['bom-ref'] ? -1 : a['bom-ref'] > b['bom-ref'] ? 1 : 0,
|
|
264
|
+
);
|
|
122
265
|
|
|
123
266
|
const serialNumber =
|
|
124
267
|
'urn:uuid:' +
|
|
@@ -137,10 +280,9 @@ function buildSbom() {
|
|
|
137
280
|
timestamp: timestamp,
|
|
138
281
|
tools: [
|
|
139
282
|
{
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
'SBOM generated from package.json + manual review (scripts/refresh-sbom.js).',
|
|
283
|
+
vendor: 'blamejs',
|
|
284
|
+
name: 'scripts/refresh-sbom.js',
|
|
285
|
+
version: pkg.version,
|
|
144
286
|
},
|
|
145
287
|
],
|
|
146
288
|
component: {
|
|
@@ -154,6 +296,11 @@ function buildSbom() {
|
|
|
154
296
|
description: pkg.description,
|
|
155
297
|
licenses: [{ license: { id: 'Apache-2.0' } }],
|
|
156
298
|
purl: `pkg:npm/${pkg.name.replace('@', '%40')}@${pkg.version}`,
|
|
299
|
+
// Bundle digest over every shipped file (see bundleDigest above
|
|
300
|
+
// for the canonical-input rule). Operators can recompute this
|
|
301
|
+
// from the per-file components[] list and compare without
|
|
302
|
+
// re-deriving package.json.files themselves.
|
|
303
|
+
hashes: [{ alg: 'SHA-256', content: bundleSha }],
|
|
157
304
|
externalReferences: [
|
|
158
305
|
{ type: 'distribution', url: `https://www.npmjs.com/package/${pkg.name}/v/${pkg.version}` },
|
|
159
306
|
{ type: 'vcs', url: (pkg.repository && pkg.repository.url) || 'https://github.com/blamejs/exceptd-skills' },
|
|
@@ -196,7 +343,7 @@ function buildSbom() {
|
|
|
196
343
|
},
|
|
197
344
|
],
|
|
198
345
|
},
|
|
199
|
-
components:
|
|
346
|
+
components: allComponents,
|
|
200
347
|
dependencies: [],
|
|
201
348
|
};
|
|
202
349
|
|