@blamejs/exceptd-skills 0.9.4 → 0.10.0
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 +45 -0
- package/CHANGELOG.md +131 -0
- package/README.md +30 -5
- package/bin/exceptd.js +403 -1
- package/data/_indexes/_meta.json +3 -3
- package/data/_indexes/currency.json +138 -138
- package/data/playbooks/ai-api.json +763 -0
- package/data/playbooks/containers.json +766 -0
- package/data/playbooks/cred-stores.json +715 -0
- package/data/playbooks/crypto.json +726 -0
- package/data/playbooks/framework.json +725 -0
- package/data/playbooks/hardening.json +672 -0
- package/data/playbooks/kernel.json +549 -0
- package/data/playbooks/mcp.json +727 -0
- package/data/playbooks/runtime.json +649 -0
- package/data/playbooks/sbom.json +893 -0
- package/data/playbooks/secrets.json +690 -0
- package/lib/cross-ref-api.js +224 -0
- package/lib/playbook-runner.js +826 -0
- package/lib/schemas/playbook.schema.json +652 -0
- package/lib/verify.js +45 -0
- package/manifest-snapshot.json +1 -1
- package/manifest.json +39 -39
- package/orchestrator/dispatcher.js +13 -1
- package/orchestrator/index.js +119 -5
- package/orchestrator/pipeline.js +8 -2
- package/orchestrator/scanner.js +191 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
- package/scripts/builders/currency.js +5 -3
package/orchestrator/scanner.js
CHANGED
|
@@ -19,12 +19,34 @@ const DATA_DIR = process.env.EXCEPTD_DATA_DIR || path.join(__dirname, '..', 'dat
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Run all scanners and return consolidated findings.
|
|
22
|
+
*
|
|
23
|
+
* DEPRECATED in v0.10.0. This is the pre-seven-phase legacy scanner that
|
|
24
|
+
* shells out from Node — kept as a safety-net path for non-AI operators.
|
|
25
|
+
* New code should call the playbook runner instead:
|
|
26
|
+
*
|
|
27
|
+
* const runner = require('../lib/playbook-runner');
|
|
28
|
+
* const result = await runner.run('kernel', 'all-catalogued-kernel-cves', agentSubmission);
|
|
29
|
+
*
|
|
30
|
+
* The runner emits seven-phase findings (govern through close) with full
|
|
31
|
+
* GRC closure: CSAF-2.0 evidence bundles, jurisdiction-aware notification
|
|
32
|
+
* deadlines, auditor-ready exception language, regression schedules.
|
|
33
|
+
* `exceptd scan` will be removed in v1.0.
|
|
34
|
+
*
|
|
22
35
|
* @returns {{ timestamp: string, host: object, findings: object[], summary: object }}
|
|
23
36
|
*/
|
|
24
37
|
async function scan() {
|
|
25
38
|
const timestamp = new Date().toISOString();
|
|
26
39
|
const findings = [];
|
|
27
40
|
|
|
41
|
+
// Deprecation banner — surfaces via stderr so JSON consumers aren't broken.
|
|
42
|
+
if (process.stderr.isTTY && !process.env.EXCEPTD_SUPPRESS_DEPRECATION) {
|
|
43
|
+
process.stderr.write(
|
|
44
|
+
'[scan] DEPRECATION: `exceptd scan` is legacy. Prefer `exceptd plan` + `exceptd run <playbook>` ' +
|
|
45
|
+
'(seven-phase contract with full GRC closure). See AGENTS.md "Seven-phase playbook contract". ' +
|
|
46
|
+
'Suppress this notice with EXCEPTD_SUPPRESS_DEPRECATION=1.\n'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
28
50
|
const host = hostInfo();
|
|
29
51
|
findings.push(...kernelScan());
|
|
30
52
|
findings.push(...mcpScan());
|
|
@@ -33,7 +55,7 @@ async function scan() {
|
|
|
33
55
|
findings.push(...frameworkScan());
|
|
34
56
|
|
|
35
57
|
const summary = summarize(findings);
|
|
36
|
-
return { timestamp, host, findings, summary };
|
|
58
|
+
return { timestamp, host, findings, summary, _deprecation: 'Use `exceptd plan` + `exceptd run <playbook>` for the seven-phase contract.' };
|
|
37
59
|
}
|
|
38
60
|
|
|
39
61
|
/**
|
|
@@ -152,16 +174,34 @@ function cryptoScan() {
|
|
|
152
174
|
const [major, minor] = version.split('.').map(Number);
|
|
153
175
|
const isPqcReady = major > 3 || (major === 3 && minor >= 5);
|
|
154
176
|
|
|
177
|
+
// Probe the full NIST PQC suite + stateful hash signatures.
|
|
178
|
+
// Reports per-algo via boolean flags so downstream callers can
|
|
179
|
+
// decide which gaps matter for their threat model (ML-KEM for
|
|
180
|
+
// HNDL exposure, LMS/XMSS for firmware signing, etc.).
|
|
181
|
+
const pqc = probePqcAlgorithms();
|
|
182
|
+
const pqcDetail = [
|
|
183
|
+
`ML-KEM=${pqc.ml_kem ? 'avail' : 'missing'}`, // FIPS 203
|
|
184
|
+
`ML-DSA=${pqc.ml_dsa ? 'avail' : 'missing'}`, // FIPS 204
|
|
185
|
+
`SLH-DSA=${pqc.slh_dsa ? 'avail' : 'missing'}`, // FIPS 205
|
|
186
|
+
`FN-DSA=${pqc.fn_dsa ? 'avail' : 'missing'}`, // FIPS 206 draft (Falcon)
|
|
187
|
+
`HQC=${pqc.hqc ? 'avail' : 'missing'}`, // alternate KEM (March 2025)
|
|
188
|
+
`LMS=${pqc.lms ? 'avail' : 'missing'}`, // RFC 8554 (firmware)
|
|
189
|
+
`XMSS=${pqc.xmss ? 'avail' : 'missing'}`, // RFC 8391
|
|
190
|
+
].join(' ');
|
|
191
|
+
|
|
155
192
|
findings.push({
|
|
156
193
|
domain: 'crypto',
|
|
157
194
|
signal: 'openssl_version',
|
|
158
195
|
value: version,
|
|
159
196
|
pqc_ready: isPqcReady,
|
|
160
|
-
|
|
197
|
+
pqc_algorithms: pqc,
|
|
198
|
+
severity: isPqcReady && pqc.ml_kem ? 'info' : (isPqcReady ? 'medium' : 'high'),
|
|
161
199
|
skill_hint: 'pqc-first',
|
|
162
200
|
action_required: isPqcReady
|
|
163
|
-
?
|
|
164
|
-
|
|
201
|
+
? (pqc.ml_kem
|
|
202
|
+
? `OpenSSL ${version} — PQC-capable. Probed: ${pqcDetail}.`
|
|
203
|
+
: `OpenSSL ${version} — claims PQC support but probe found no ML-KEM. Check provider config; may need OQS-Provider or Node 24 crypto.kemEncapsulate. Probed: ${pqcDetail}.`)
|
|
204
|
+
: `OpenSSL ${version} — below 3.5. Upgrade required for PQC support (ML-KEM, ML-DSA, SLH-DSA, FN-DSA, HQC, LMS, XMSS).`
|
|
165
205
|
});
|
|
166
206
|
}
|
|
167
207
|
|
|
@@ -296,6 +336,153 @@ function safeExecFile(cmd, args) {
|
|
|
296
336
|
}
|
|
297
337
|
}
|
|
298
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Probe runtime for PQC algorithm availability across the full
|
|
341
|
+
* emerging-standards landscape. Returns boolean flags per algorithm
|
|
342
|
+
* + a `provider_hint` indicating which surface confirmed each one.
|
|
343
|
+
*
|
|
344
|
+
* --- NIST PQC finalized (FIPS 203/204/205, 2024) ---
|
|
345
|
+
* ml_kem ML-KEM (Kyber) — FIPS 203, key encapsulation
|
|
346
|
+
* ml_dsa ML-DSA (Dilithium) — FIPS 204, signatures
|
|
347
|
+
* slh_dsa SLH-DSA (SPHINCS+) — FIPS 205, stateless hash sigs
|
|
348
|
+
*
|
|
349
|
+
* --- NIST PQC draft / alternate (2025+) ---
|
|
350
|
+
* fn_dsa FN-DSA (Falcon) — FIPS 206 draft, compact lattice sigs
|
|
351
|
+
* hqc HQC — alternate KEM selected March 2025
|
|
352
|
+
*
|
|
353
|
+
* --- NIST PQC Round-4 alternates (still relevant in niche / archival) ---
|
|
354
|
+
* frodo FrodoKEM — conservative lattice KEM
|
|
355
|
+
* ntru NTRU / NTRU-Prime / sNTRU — original lattice KEM family
|
|
356
|
+
* mceliece Classic McEliece — code-based, long-term archival use
|
|
357
|
+
* bike BIKE — code-based KEM, OQS-Provider exposed
|
|
358
|
+
*
|
|
359
|
+
* --- NIST additional signature on-ramp (2023+ Round 2) ---
|
|
360
|
+
* hawk HAWK — NTRU-based lattice signatures
|
|
361
|
+
* mayo MAYO — multivariate
|
|
362
|
+
* sqisign SQIsign — isogeny-based, small signatures
|
|
363
|
+
* cross CROSS — code-based
|
|
364
|
+
* uov UOV / SNOVA — Unbalanced Oil & Vinegar multivariate
|
|
365
|
+
* sdith SDitH — code-based (Syndrome Decoding in the Head)
|
|
366
|
+
* mirath MIRATH — code-based (rank metric)
|
|
367
|
+
* faest FAEST — symmetric-key/AES-based signatures
|
|
368
|
+
* perk PERK — code-based (Permuted Kernel Problem)
|
|
369
|
+
*
|
|
370
|
+
* --- Stateful hash signatures (RFC 8391 / 8554) ---
|
|
371
|
+
* lms LMS — Leighton-Micali Signatures (firmware)
|
|
372
|
+
* xmss XMSS — eXtended Merkle Signature Scheme
|
|
373
|
+
* hss HSS — Hierarchical Signature System
|
|
374
|
+
*
|
|
375
|
+
* --- IETF hybrid / composite sigs (emerging RFC drafts) ---
|
|
376
|
+
* composite_sig Composite Signatures (e.g. RSA+ML-DSA, ECDSA+ML-DSA)
|
|
377
|
+
* composite_kem Composite KEMs (e.g. X25519+ML-KEM)
|
|
378
|
+
*/
|
|
379
|
+
function probePqcAlgorithms() {
|
|
380
|
+
const result = {
|
|
381
|
+
// NIST finalized
|
|
382
|
+
ml_kem: false, ml_dsa: false, slh_dsa: false,
|
|
383
|
+
// NIST draft / alternate
|
|
384
|
+
fn_dsa: false, hqc: false,
|
|
385
|
+
// NIST Round-4 / niche
|
|
386
|
+
frodo: false, ntru: false, mceliece: false, bike: false,
|
|
387
|
+
// NIST signature on-ramp (Round 2)
|
|
388
|
+
hawk: false, mayo: false, sqisign: false, cross: false,
|
|
389
|
+
uov: false, sdith: false, mirath: false, faest: false, perk: false,
|
|
390
|
+
// Stateful hash sigs
|
|
391
|
+
lms: false, xmss: false, hss: false,
|
|
392
|
+
// IETF composite / hybrid
|
|
393
|
+
composite_sig: false, composite_kem: false,
|
|
394
|
+
provider_hint: {},
|
|
395
|
+
};
|
|
396
|
+
const crypto = require('crypto');
|
|
397
|
+
|
|
398
|
+
// Pattern table — values are regexes matching common spellings
|
|
399
|
+
// emitted by Node / OpenSSL / OQS-Provider / Bouncy Castle. Tested
|
|
400
|
+
// against algorithm-list output specifically (no English-prose
|
|
401
|
+
// false positives expected; the lists contain only algo names).
|
|
402
|
+
const PATTERNS = {
|
|
403
|
+
// NIST finalized
|
|
404
|
+
ml_kem: /\b(ml-?kem|kyber)\b/i,
|
|
405
|
+
ml_dsa: /\b(ml-?dsa|dilithium)\b/i,
|
|
406
|
+
slh_dsa: /\b(slh-?dsa|sphincs(?:\+|plus)?)\b/i,
|
|
407
|
+
// NIST draft / alternate
|
|
408
|
+
fn_dsa: /\b(fn-?dsa|falcon-?\d{3,4})\b/i,
|
|
409
|
+
hqc: /\bhqc(-?\d+)?\b/i,
|
|
410
|
+
// NIST Round-4 / niche
|
|
411
|
+
frodo: /\bfrodo(-?\d+)?(kem)?\b/i,
|
|
412
|
+
ntru: /\b(s?ntru(-?prime)?(-?\d+)?|hrss\d*|hps\d+)\b/i,
|
|
413
|
+
mceliece: /\b(classic-?)?mceliece(-?\d+)?\b/i,
|
|
414
|
+
bike: /\bbike(-?l?\d+)?\b/i,
|
|
415
|
+
// NIST signature on-ramp (Round 2, 2024+)
|
|
416
|
+
hawk: /\bhawk(-?\d+)?\b/i,
|
|
417
|
+
mayo: /\bmayo(-?\d+)?\b/i,
|
|
418
|
+
sqisign: /\bsqi-?sign(?:hd)?\b/i,
|
|
419
|
+
cross: /\bcross-?(rsdp|rsdpg)?-?(small|fast|balanced)?-?[lns]?\d*\b/i,
|
|
420
|
+
uov: /\b(uov|snova)(-?\d+)?\b/i,
|
|
421
|
+
sdith: /\bsd-?i?t?h(-?gf\d+)?(-?cat\d+)?\b/i,
|
|
422
|
+
mirath: /\bmirath(-?\d+)?\b/i,
|
|
423
|
+
faest: /\bfaest(-em)?(-?\d+)?\b/i,
|
|
424
|
+
perk: /\bperk-?[ls]?-?\d*\b/i,
|
|
425
|
+
// Stateful hash signatures
|
|
426
|
+
lms: /\blms(?!-sha)\b/i,
|
|
427
|
+
xmss: /\bxmss(\^?mt)?\b/i,
|
|
428
|
+
hss: /\bhss(?!-sha)\b/i,
|
|
429
|
+
// Composite / hybrid (IETF drafts)
|
|
430
|
+
composite_sig: /\bcomposite-?(sig|signature)\b|\bhybrid-?sig\b|\b(rsa|ecdsa|eddsa)-?(\+|with)-?(ml-?dsa|dilithium|slh-?dsa|sphincs|falcon|fn-?dsa)\b/i,
|
|
431
|
+
composite_kem: /\bcomposite-?kem\b|\b(x25519|x448|secp\d+)-?(\+|with)-?(ml-?kem|kyber)\b|\bml-?kem-?(\+|with)-?(x25519|x448)\b/i,
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
function record(algo, source) {
|
|
435
|
+
if (!result[algo]) {
|
|
436
|
+
result[algo] = true;
|
|
437
|
+
result.provider_hint[algo] = source;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Channel 1 — Node's crypto APIs (no shellouts). Node 24+ exposes
|
|
442
|
+
// crypto.kemEncapsulate as an experimental ML-KEM gateway; its
|
|
443
|
+
// presence is itself an ML-KEM availability signal.
|
|
444
|
+
try {
|
|
445
|
+
if (typeof crypto.kemEncapsulate === 'function') {
|
|
446
|
+
record('ml_kem', 'node:crypto.kemEncapsulate');
|
|
447
|
+
}
|
|
448
|
+
const allNames = [
|
|
449
|
+
...(crypto.getCurves ? crypto.getCurves() : []),
|
|
450
|
+
...(crypto.getHashes ? crypto.getHashes() : []),
|
|
451
|
+
...(crypto.getCiphers ? crypto.getCiphers() : []),
|
|
452
|
+
];
|
|
453
|
+
for (const name of allNames) {
|
|
454
|
+
for (const [algo, re] of Object.entries(PATTERNS)) {
|
|
455
|
+
if (re.test(name)) record(algo, `node:${name}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
} catch (_) { /* probe failure → fall through to openssl */ }
|
|
459
|
+
|
|
460
|
+
// Channel 2 — `openssl list` enumerations. KEMs and signature
|
|
461
|
+
// algorithms split across two list commands; OQS-Provider exposes
|
|
462
|
+
// the on-ramp / niche algos when installed.
|
|
463
|
+
const kemList = safeExecFile('openssl', ['list', '-kem-algorithms']);
|
|
464
|
+
if (kemList) {
|
|
465
|
+
const kemAlgos = ['ml_kem', 'hqc', 'frodo', 'ntru', 'mceliece', 'bike', 'composite_kem'];
|
|
466
|
+
for (const algo of kemAlgos) {
|
|
467
|
+
if (PATTERNS[algo].test(kemList)) record(algo, 'openssl:list -kem-algorithms');
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const sigList = safeExecFile('openssl', ['list', '-signature-algorithms']);
|
|
471
|
+
if (sigList) {
|
|
472
|
+
const sigAlgos = [
|
|
473
|
+
'ml_dsa', 'slh_dsa', 'fn_dsa',
|
|
474
|
+
'hawk', 'mayo', 'sqisign', 'cross', 'uov', 'sdith', 'mirath', 'faest', 'perk',
|
|
475
|
+
'lms', 'xmss', 'hss',
|
|
476
|
+
'composite_sig',
|
|
477
|
+
];
|
|
478
|
+
for (const algo of sigAlgos) {
|
|
479
|
+
if (PATTERNS[algo].test(sigList)) record(algo, 'openssl:list -signature-algorithms');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
|
|
299
486
|
function probeTls() {
|
|
300
487
|
const result = spawnSync('openssl', ['s_client', '-connect', 'google.com:443', '-brief'], {
|
|
301
488
|
input: '',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/exceptd-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 38 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-security",
|
package/sbom.cdx.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:7ad6ee8c-6fb3-4eee-b37d-d9b770e964b6",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-05-
|
|
7
|
+
"timestamp": "2026-05-12T05:38:55.099Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"name": "hand-written",
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"component": {
|
|
16
|
-
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.
|
|
16
|
+
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.10.0",
|
|
17
17
|
"type": "application",
|
|
18
18
|
"name": "@blamejs/exceptd-skills",
|
|
19
|
-
"version": "0.
|
|
19
|
+
"version": "0.10.0",
|
|
20
20
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 38 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
|
|
21
21
|
"licenses": [
|
|
22
22
|
{
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
|
-
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.
|
|
28
|
+
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.10.0",
|
|
29
29
|
"externalReferences": [
|
|
30
30
|
{
|
|
31
31
|
"type": "distribution",
|
|
32
|
-
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.
|
|
32
|
+
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.10.0"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"type": "vcs",
|
|
@@ -20,13 +20,15 @@
|
|
|
20
20
|
const fs = require("fs");
|
|
21
21
|
const path = require("path");
|
|
22
22
|
|
|
23
|
-
function currencyScore(daysSinceReview,
|
|
23
|
+
function currencyScore(daysSinceReview, _forwardWatchCount) {
|
|
24
|
+
// See orchestrator/pipeline.js — forward_watch count no longer
|
|
25
|
+
// affects currency score (it's a maintenance signal, not staleness).
|
|
26
|
+
// Param retained for ABI compatibility with callers.
|
|
24
27
|
let score = 100;
|
|
25
28
|
if (daysSinceReview > 180) score -= 30;
|
|
26
29
|
else if (daysSinceReview > 90) score -= 20;
|
|
27
30
|
else if (daysSinceReview > 60) score -= 10;
|
|
28
31
|
else if (daysSinceReview > 30) score -= 5;
|
|
29
|
-
score -= forwardWatchCount * 5;
|
|
30
32
|
return Math.max(0, score);
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -99,7 +101,7 @@ function buildCurrency({ root, manifest, skills }) {
|
|
|
99
101
|
schema_version: "1.0.0",
|
|
100
102
|
reference_date: ref,
|
|
101
103
|
note: "Pre-computed skill currency snapshot. Reference date is manifest.threat_review_date (deterministic). Re-runs of build-indexes against the same inputs produce byte-identical output. The orchestrator `currency` command produces a real-time view against today's date.",
|
|
102
|
-
decay_formula: "100 base; -30/-20/-10/-5 at 180/90/60/30-day thresholds
|
|
104
|
+
decay_formula: "100 base; -30/-20/-10/-5 at 180/90/60/30-day thresholds. forward_watch count does NOT affect the score (it's a maintenance signal, not a staleness one). Label thresholds: ≥90 current, ≥70 acceptable, ≥50 stale, <50 critical_stale.",
|
|
103
105
|
},
|
|
104
106
|
summary,
|
|
105
107
|
skills: rows,
|