@blamejs/exceptd-skills 0.18.12 → 0.18.14
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 +20 -0
- package/bin/exceptd.js +26 -0
- package/data/_indexes/_meta.json +2 -2
- package/data/playbooks/ai-api.json +2 -2
- package/data/playbooks/ai-discovered-cve-triage.json +1 -1
- package/data/playbooks/audit-log-integrity.json +3 -3
- package/data/playbooks/cicd-pipeline-compromise.json +1 -1
- package/data/playbooks/citation-hygiene.json +2 -2
- package/data/playbooks/containers.json +2 -2
- package/data/playbooks/cred-stores.json +1 -1
- package/data/playbooks/crypto-codebase.json +1 -1
- package/data/playbooks/crypto.json +1 -1
- package/data/playbooks/decompression-dos.json +3 -3
- package/data/playbooks/framework.json +1 -1
- package/data/playbooks/hardening.json +1 -1
- package/data/playbooks/identity-sso-compromise.json +1 -1
- package/data/playbooks/kernel.json +2 -2
- package/data/playbooks/library-author.json +1 -1
- package/data/playbooks/llm-tool-use-exfil.json +1 -1
- package/data/playbooks/log-injection-telemetry.json +3 -3
- package/data/playbooks/mail-server-hardening.json +3 -3
- package/data/playbooks/mcp.json +2 -2
- package/data/playbooks/multitenancy-isolation.json +3 -3
- package/data/playbooks/network-trust.json +3 -3
- package/data/playbooks/post-quantum-migration.json +1 -1
- package/data/playbooks/privacy-consent-ops.json +3 -3
- package/data/playbooks/runtime.json +2 -2
- package/data/playbooks/sbom.json +1 -1
- package/data/playbooks/secrets.json +1 -1
- package/data/playbooks/self-update-integrity.json +3 -3
- package/data/playbooks/supply-chain-recovery.json +2 -2
- package/data/playbooks/vc-wallet-trust.json +5 -5
- package/data/playbooks/webhook-callback-abuse.json +1 -1
- package/lib/auto-discovery.js +10 -1
- package/lib/cve-regression-watcher.js +5 -1
- package/lib/job-queue.js +9 -2
- package/lib/lint-skills.js +34 -5
- package/lib/playbook-runner.js +51 -4
- package/lib/prefetch.js +15 -7
- package/lib/schemas/skill-frontmatter.schema.json +2 -2
- package/lib/source-ghsa.js +8 -2
- package/lib/source-osv.js +19 -3
- package/lib/ttp-mapper.js +15 -0
- package/lib/upstream-check.js +17 -3
- package/lib/validate-cve-catalog.js +16 -4
- package/lib/validate-package.js +9 -2
- package/lib/validate-playbooks.js +112 -10
- package/lib/validate-vendor.js +11 -5
- package/lib/xml-tokenizer.js +36 -8
- package/manifest.json +53 -53
- package/package.json +1 -1
- package/sbom.cdx.json +117 -117
- package/sources/validators/cve-validator.js +7 -1
- package/sources/validators/rfc-validator.js +21 -3
- package/sources/validators/version-pin-validator.js +39 -2
- package/vendor/blamejs/_PROVENANCE.json +3 -2
- package/vendor/blamejs/worker-pool.js +6 -0
|
@@ -272,7 +272,13 @@ async function validateCve(cveId, localEntry) {
|
|
|
272
272
|
const disputed = (fetched.cve_tags || []).some(t => /disputed/i.test(t)) ||
|
|
273
273
|
/disputed/i.test(fetched.nvd_vuln_status || '');
|
|
274
274
|
if (cveFoundInNvd && disputed) {
|
|
275
|
-
|
|
275
|
+
// `local` is the projected shape (cvss_*, cisa_kev*, epss_*) and never
|
|
276
|
+
// carries a status field — reading local?.status here always yielded null,
|
|
277
|
+
// so the local side of a disputed discrepancy reported nothing. Read the
|
|
278
|
+
// catalog's own status/cve_status off the RAW input so an entry that
|
|
279
|
+
// already records the dispute (or a stale "active" status) is surfaced.
|
|
280
|
+
const localCveStatus = localEntry?.cve_status ?? localEntry?.status ?? null;
|
|
281
|
+
pushDiscrepancy(discrepancies, 'cve_status', localCveStatus, 'disputed', 'high');
|
|
276
282
|
}
|
|
277
283
|
|
|
278
284
|
// --- Compare CVSS (only if NVD reachable & has data) ---
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* drift — at least one of those fields disagrees.
|
|
14
14
|
* missing — Datatracker does not have this RFC / draft.
|
|
15
15
|
* unreachable — network failed or the request timed out.
|
|
16
|
+
* skipped — the catalog key is not an IETF RFC/DRAFT (e.g. CSAF-2.0,
|
|
17
|
+
* ISO-29147, ISO-30111). Datatracker only tracks IETF
|
|
18
|
+
* documents, so these have no upstream to compare against —
|
|
19
|
+
* they are intentionally not validated here, NOT drift.
|
|
16
20
|
*
|
|
17
21
|
* Zero external dependencies. Node 24 stdlib (fetch + AbortController).
|
|
18
22
|
*
|
|
@@ -107,15 +111,26 @@ function compareEntry(local, upstream) {
|
|
|
107
111
|
return discrepancies;
|
|
108
112
|
}
|
|
109
113
|
|
|
114
|
+
// Datatracker only tracks IETF RFCs and Internet-Drafts. A catalog key that is
|
|
115
|
+
// neither (CSAF-2.0, ISO-29147, ISO-30111, …) has no IETF upstream to compare
|
|
116
|
+
// against, so it is out of scope for this validator rather than a discrepancy.
|
|
117
|
+
function isIetfKey(id) {
|
|
118
|
+
return typeof id === 'string' && (id.startsWith('RFC-') || id.startsWith('DRAFT-'));
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
async function validateRfc(id, entry) {
|
|
111
122
|
let docName;
|
|
112
123
|
if (id.startsWith('RFC-')) docName = rfcNumberFromKey(id);
|
|
113
124
|
else if (id.startsWith('DRAFT-')) docName = draftSlugFromKey(id);
|
|
114
125
|
else {
|
|
126
|
+
// Not an IETF document — there is nothing on Datatracker to diff against.
|
|
127
|
+
// Report a distinct, non-drift status with no discrepancies so callers
|
|
128
|
+
// don't surface a permanent false-positive "drift" for every ISO/CSAF
|
|
129
|
+
// reference the catalog carries.
|
|
115
130
|
return {
|
|
116
131
|
id,
|
|
117
|
-
status: '
|
|
118
|
-
discrepancies: [
|
|
132
|
+
status: 'skipped',
|
|
133
|
+
discrepancies: [],
|
|
119
134
|
local: entry,
|
|
120
135
|
fetched: null,
|
|
121
136
|
};
|
|
@@ -150,7 +165,10 @@ async function validateRfc(id, entry) {
|
|
|
150
165
|
}
|
|
151
166
|
|
|
152
167
|
async function validateAllRfcs(refs, { concurrency = 4 } = {}) {
|
|
153
|
-
|
|
168
|
+
// Skip "_"-prefixed meta keys AND any non-IETF reference (ISO/CSAF/etc.):
|
|
169
|
+
// Datatracker has no record of those, so validating them only ever produced
|
|
170
|
+
// permanent false-positive "drift". They are simply out of scope here.
|
|
171
|
+
const ids = Object.keys(refs).filter(k => !k.startsWith('_') && isIetfKey(k));
|
|
154
172
|
const results = [];
|
|
155
173
|
for (let i = 0; i < ids.length; i += concurrency) {
|
|
156
174
|
const batch = ids.slice(i, i + concurrency);
|
|
@@ -7,8 +7,17 @@
|
|
|
7
7
|
*
|
|
8
8
|
* - MITRE ATLAS: GitHub releases (mitre-atlas/atlas-data)
|
|
9
9
|
* - MITRE ATT&CK: GitHub releases (mitre-attack/attack-stix-data)
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* MITRE D3FEND and MITRE CWE do NOT publish tagged GitHub releases — D3FEND
|
|
12
|
+
* ships its ontology from d3fend/d3fend-ontology untagged, and CWE distributes
|
|
13
|
+
* XML from cwe.mitre.org. Their pin currency is tracked on the upstream-check
|
|
14
|
+
* path (lib/upstream-check.js) against those canonical mitre.org endpoints, not
|
|
15
|
+
* via the GitHub releases API. They remain enumerated here for completeness but
|
|
16
|
+
* are flagged `github_releases: false`, so the GitHub-release check is skipped
|
|
17
|
+
* for them and they are reported as `tracked_elsewhere` rather than emitting a
|
|
18
|
+
* spurious `unreachable` (a release API call for a repo with no releases always
|
|
19
|
+
* fails). This keeps the live path checking the same two GitHub-release repos
|
|
20
|
+
* the cache path (pinsDiffFromCache) checks.
|
|
12
21
|
*
|
|
13
22
|
* Each check returns:
|
|
14
23
|
* { pin_name, local_version, latest_version, drift: bool, source_url, error? }
|
|
@@ -29,24 +38,35 @@ const PINS = [
|
|
|
29
38
|
repo: "mitre-atlas/atlas-data",
|
|
30
39
|
local_path_hint: "manifest.json — atlas_version",
|
|
31
40
|
strip_v_prefix: true,
|
|
41
|
+
github_releases: true,
|
|
32
42
|
},
|
|
33
43
|
{
|
|
34
44
|
pin_name: "attack_version",
|
|
35
45
|
repo: "mitre-attack/attack-stix-data",
|
|
36
46
|
local_path_hint: "manifest.json — attack_version",
|
|
37
47
|
strip_v_prefix: true,
|
|
48
|
+
github_releases: true,
|
|
38
49
|
},
|
|
39
50
|
{
|
|
51
|
+
// D3FEND does not tag GitHub releases — currency is tracked via
|
|
52
|
+
// lib/upstream-check.js against the canonical mitre.org endpoint. Listed
|
|
53
|
+
// here for completeness; the GitHub-release check is skipped.
|
|
40
54
|
pin_name: "d3fend_version",
|
|
41
55
|
repo: "d3fend/d3fend-data",
|
|
42
56
|
local_path_hint: "data/d3fend-catalog.json _meta.version",
|
|
43
57
|
strip_v_prefix: true,
|
|
58
|
+
github_releases: false,
|
|
59
|
+
tracked_via: "lib/upstream-check.js (d3fend.mitre.org)",
|
|
44
60
|
},
|
|
45
61
|
{
|
|
62
|
+
// CWE distributes XML from cwe.mitre.org with no tagged GitHub releases —
|
|
63
|
+
// currency is tracked via lib/upstream-check.js. GitHub-release check skipped.
|
|
46
64
|
pin_name: "cwe_version",
|
|
47
65
|
repo: "mitre/cwe",
|
|
48
66
|
local_path_hint: "data/cwe-catalog.json _meta.version",
|
|
49
67
|
strip_v_prefix: true,
|
|
68
|
+
github_releases: false,
|
|
69
|
+
tracked_via: "lib/upstream-check.js (cwe.mitre.org)",
|
|
50
70
|
},
|
|
51
71
|
];
|
|
52
72
|
|
|
@@ -113,6 +133,23 @@ async function checkAllPins(ctx) {
|
|
|
113
133
|
const out = [];
|
|
114
134
|
for (const pin of PINS) {
|
|
115
135
|
const local = normalize(resolveLocalVersion(pin.pin_name, ctx), pin.strip_v_prefix);
|
|
136
|
+
// Pins whose upstream does not publish GitHub releases (D3FEND, CWE) have
|
|
137
|
+
// no release tag to compare against — calling the releases API would always
|
|
138
|
+
// fail and surface a spurious `unreachable`. Report them as tracked
|
|
139
|
+
// elsewhere (drift:null, no error) so the live path checks exactly the same
|
|
140
|
+
// GitHub-release repos as the cache path.
|
|
141
|
+
if (pin.github_releases === false) {
|
|
142
|
+
out.push({
|
|
143
|
+
pin_name: pin.pin_name,
|
|
144
|
+
local_version: local,
|
|
145
|
+
latest_version: null,
|
|
146
|
+
drift: null,
|
|
147
|
+
tracked_elsewhere: true,
|
|
148
|
+
tracked_via: pin.tracked_via || null,
|
|
149
|
+
local_path_hint: pin.local_path_hint,
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
116
153
|
const release = await latestGithubRelease(pin.repo);
|
|
117
154
|
if (release.error) {
|
|
118
155
|
out.push({
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"worker-pool.js": {
|
|
36
36
|
"vendored_path": "vendor/blamejs/worker-pool.js",
|
|
37
|
-
"vendored_sha256": "
|
|
37
|
+
"vendored_sha256": "948549d82e481f241e5babb6bdddd27bf7148fb8ed1c389b9f2f7ad035e4e56b",
|
|
38
38
|
"upstream_path": "lib/worker-pool.js",
|
|
39
39
|
"upstream_sha256_at_pin": "262f99e9cc3d4a8f4eba9ad3e28401e8a1f47f78040afa63c9b17eb998437171",
|
|
40
40
|
"stripped": [
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"worker recycle on uncaught error / timeout / exit"
|
|
51
51
|
],
|
|
52
52
|
"exceptd_deltas": [
|
|
53
|
-
"scriptPath validator rejects Windows UNC + extended-path prefixes (\\\\?\\, \\\\.\\, \\\\<server>\\) — defense-in-depth against worker-spawn from network shares on win32 platforms"
|
|
53
|
+
"scriptPath validator rejects Windows UNC + extended-path prefixes (\\\\?\\, \\\\.\\, \\\\<server>\\) — defense-in-depth against worker-spawn from network shares on win32 platforms",
|
|
54
|
+
"_onWorkerExit respawn branch: call _maybeResolveDrain() after _drainQueue() so drain() settles when a worker recycles with an empty queue (the no-respawn branch already did; the respawn branch could hang pending drain waiters)."
|
|
54
55
|
]
|
|
55
56
|
},
|
|
56
57
|
"codepoint-class.js": {
|
|
@@ -322,6 +322,12 @@ function create(scriptPath, opts) {
|
|
|
322
322
|
if (!terminated && workerSlots.length < size) {
|
|
323
323
|
try { _spawnWorker(); } catch (_e) { /* already audited */ }
|
|
324
324
|
_drainQueue();
|
|
325
|
+
// exceptd delta: also settle pending drain() waiters after a respawn.
|
|
326
|
+
// _drainQueue() only schedules queued tasks; if the queue is already
|
|
327
|
+
// empty (a worker recycled with nothing left to do) the pool is now idle
|
|
328
|
+
// and a caller awaiting drain() must be resolved, exactly as the
|
|
329
|
+
// no-respawn branch below does — otherwise drain() hangs forever.
|
|
330
|
+
_maybeResolveDrain();
|
|
325
331
|
} else {
|
|
326
332
|
_maybeResolveDrain();
|
|
327
333
|
}
|