@blamejs/exceptd-skills 0.12.40 → 0.12.41
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 +17 -0
- package/ARCHITECTURE.md +7 -4
- package/CHANGELOG.md +124 -248
- package/CONTEXT.md +2 -2
- package/README.md +2 -8
- package/agents/threat-researcher.md +2 -2
- package/bin/exceptd.js +128 -38
- package/data/_indexes/_meta.json +9 -9
- package/data/_indexes/activity-feed.json +1 -1
- package/data/_indexes/catalog-summaries.json +1 -1
- package/data/_indexes/frequency.json +4 -0
- package/data/_indexes/section-offsets.json +20 -20
- package/data/_indexes/token-budget.json +5 -5
- package/data/cve-catalog.json +21 -28
- package/data/exploit-availability.json +1 -0
- package/data/framework-control-gaps.json +137 -4
- package/data/global-frameworks.json +1 -0
- package/data/playbooks/crypto-codebase.json +13 -0
- package/data/zeroday-lessons.json +1 -0
- package/lib/lint-skills.js +1 -1
- package/lib/playbook-runner.js +8 -4
- package/lib/scoring.js +9 -1
- package/lib/sign.js +40 -7
- package/lib/verify.js +5 -5
- package/manifest.json +45 -45
- package/orchestrator/README.md +7 -7
- package/orchestrator/index.js +30 -17
- package/orchestrator/scheduler.js +2 -2
- package/package.json +1 -1
- package/sbom.cdx.json +33 -33
- package/scripts/check-test-coverage.js +6 -6
- package/scripts/refresh-reverse-refs.js +21 -20
- package/skills/mlops-security/skill.md +1 -1
package/data/cve-catalog.json
CHANGED
|
@@ -36,6 +36,16 @@
|
|
|
36
36
|
},
|
|
37
37
|
"vendor_advisory_field_added": "2026-05-11",
|
|
38
38
|
"vendor_advisory_note": "Each CVE carries a structured vendor_advisories array (vendor, advisory_id, url, severity, published_date) for downstream consumers that route by vendor advisory. Unknown advisory IDs are null with the canonical vendor CVE-resolver URL — never fabricated. Existing free-form references are preserved in verification_sources; vendor_advisories is additive.",
|
|
39
|
+
"active_exploitation_vocabulary": {
|
|
40
|
+
"values": ["confirmed", "suspected", "theoretical", "none", "unknown"],
|
|
41
|
+
"definitions": {
|
|
42
|
+
"confirmed": "Active in-the-wild exploitation observed and attributed",
|
|
43
|
+
"suspected": "Indicators consistent with exploitation; attribution incomplete",
|
|
44
|
+
"theoretical": "Working PoC published; no confirmed exploitation",
|
|
45
|
+
"none": "No exploitation observed; vulnerability disclosed and patched",
|
|
46
|
+
"unknown": "Insufficient telemetry to classify"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
39
49
|
"ai_discovery_methodology": {
|
|
40
50
|
"field_added": "2026-05-15",
|
|
41
51
|
"agents_md_target": "Hard Rule #7 — '41% of 2025 zero-days were AI-discovered'. Catalog target rate floor: 0.40.",
|
|
@@ -1318,7 +1328,7 @@
|
|
|
1318
1328
|
"NIST-800-53-SI-2": "30-day critical patch SLA is an exploitation window for a deterministic LPE with a public PoC. Module-unload mitigation is non-reboot and available immediately, but no SI-2 implementation requires it as a compensating control.",
|
|
1319
1329
|
"ISO-27001-2022-A.8.8": "'Appropriate timescales' undefined; standard 30-day interpretation is unsafe for deterministic LPE with public PoC. No requirement to track kernel-module-blacklist as a compensating control.",
|
|
1320
1330
|
"NIS2-Art21-patch-management": "Art. 21(2)(c) patch-management measures are undefined for fast-cycle kernel LPEs with public PoC. No guidance on module-blacklist as an interim measure.",
|
|
1321
|
-
"DORA-
|
|
1331
|
+
"DORA-Art-9": "ICT incident management presumes vendor-patch cadence; module-unload as immediate mitigation has no place in the typical DORA evidence pack.",
|
|
1322
1332
|
"UK-CAF-B4": "System security principle is silent on subsystem module disable as a compensating control for unpatched kernel LPE.",
|
|
1323
1333
|
"AU-ISM-1546": "Essential 8 patch-applications maturity ladder anchors on advisory date, not on PoC availability. ML3 48h is still long for a deterministic public exploit.",
|
|
1324
1334
|
"ISO-27001-2022-A.5.7": "Threat-intelligence control collects feeds but does not require the operational pivot (module unload) when intel shows a same-family sequel to a previously-patched bug."
|
|
@@ -1500,9 +1510,7 @@
|
|
|
1500
1510
|
},
|
|
1501
1511
|
"epss_score": 0.65,
|
|
1502
1512
|
"epss_date": "2026-05-14",
|
|
1503
|
-
"cwe_refs": [
|
|
1504
|
-
"CWE-403"
|
|
1505
|
-
],
|
|
1513
|
+
"cwe_refs": [],
|
|
1506
1514
|
"source_verified": "2026-05-14",
|
|
1507
1515
|
"verification_sources": [
|
|
1508
1516
|
"https://nvd.nist.gov/vuln/detail/CVE-2024-21626",
|
|
@@ -1626,8 +1634,7 @@
|
|
|
1626
1634
|
},
|
|
1627
1635
|
"atlas_refs": [],
|
|
1628
1636
|
"attack_refs": [
|
|
1629
|
-
"T1611"
|
|
1630
|
-
"T1547.006"
|
|
1637
|
+
"T1611"
|
|
1631
1638
|
],
|
|
1632
1639
|
"rwep_score": 30,
|
|
1633
1640
|
"rwep_factors": {
|
|
@@ -1689,10 +1696,7 @@
|
|
|
1689
1696
|
"atlas_refs": [
|
|
1690
1697
|
"AML.T0016"
|
|
1691
1698
|
],
|
|
1692
|
-
"attack_refs": [
|
|
1693
|
-
"T1083",
|
|
1694
|
-
"T1005"
|
|
1695
|
-
],
|
|
1699
|
+
"attack_refs": [],
|
|
1696
1700
|
"rwep_score": 30,
|
|
1697
1701
|
"rwep_factors": {
|
|
1698
1702
|
"cisa_kev": 0,
|
|
@@ -1834,7 +1838,6 @@
|
|
|
1834
1838
|
"epss_score": 0.967,
|
|
1835
1839
|
"epss_date": "2026-05-14",
|
|
1836
1840
|
"cwe_refs": [
|
|
1837
|
-
"CWE-119",
|
|
1838
1841
|
"CWE-787"
|
|
1839
1842
|
],
|
|
1840
1843
|
"source_verified": "2026-05-14",
|
|
@@ -1896,9 +1899,7 @@
|
|
|
1896
1899
|
},
|
|
1897
1900
|
"epss_score": 0.973,
|
|
1898
1901
|
"epss_date": "2026-05-14",
|
|
1899
|
-
"cwe_refs": [
|
|
1900
|
-
"CWE-288"
|
|
1901
|
-
],
|
|
1902
|
+
"cwe_refs": [],
|
|
1902
1903
|
"source_verified": "2026-05-14",
|
|
1903
1904
|
"verification_sources": [
|
|
1904
1905
|
"https://nvd.nist.gov/vuln/detail/CVE-2024-1709",
|
|
@@ -2006,8 +2007,7 @@
|
|
|
2006
2007
|
},
|
|
2007
2008
|
"atlas_refs": [],
|
|
2008
2009
|
"attack_refs": [
|
|
2009
|
-
"T1525"
|
|
2010
|
-
"T1046"
|
|
2010
|
+
"T1525"
|
|
2011
2011
|
],
|
|
2012
2012
|
"rwep_score": 30,
|
|
2013
2013
|
"rwep_factors": {
|
|
@@ -2022,9 +2022,7 @@
|
|
|
2022
2022
|
},
|
|
2023
2023
|
"epss_score": 0.005,
|
|
2024
2024
|
"epss_date": "2026-05-14",
|
|
2025
|
-
"cwe_refs": [
|
|
2026
|
-
"CWE-190"
|
|
2027
|
-
],
|
|
2025
|
+
"cwe_refs": [],
|
|
2028
2026
|
"source_verified": "2026-05-14",
|
|
2029
2027
|
"verification_sources": [
|
|
2030
2028
|
"https://nvd.nist.gov/vuln/detail/CVE-2024-40635",
|
|
@@ -2073,8 +2071,7 @@
|
|
|
2073
2071
|
"NIS2-Art21-supply-chain": "Generic supply chain controls without npm-ecosystem-specific guidance."
|
|
2074
2072
|
},
|
|
2075
2073
|
"atlas_refs": [
|
|
2076
|
-
"AML.T0010"
|
|
2077
|
-
"AML.T0019"
|
|
2074
|
+
"AML.T0010"
|
|
2078
2075
|
],
|
|
2079
2076
|
"attack_refs": [
|
|
2080
2077
|
"T1195.001",
|
|
@@ -2238,8 +2235,7 @@
|
|
|
2238
2235
|
"epss_score": null,
|
|
2239
2236
|
"epss_date": "2026-05-14",
|
|
2240
2237
|
"cwe_refs": [
|
|
2241
|
-
"CWE-287"
|
|
2242
|
-
"CWE-841"
|
|
2238
|
+
"CWE-287"
|
|
2243
2239
|
],
|
|
2244
2240
|
"source_verified": "2026-05-14",
|
|
2245
2241
|
"verification_sources": [
|
|
@@ -2597,8 +2593,7 @@
|
|
|
2597
2593
|
},
|
|
2598
2594
|
"atlas_refs": [],
|
|
2599
2595
|
"attack_refs": [
|
|
2600
|
-
"T1190"
|
|
2601
|
-
"T1490"
|
|
2596
|
+
"T1190"
|
|
2602
2597
|
],
|
|
2603
2598
|
"rwep_score": 45,
|
|
2604
2599
|
"rwep_factors": {
|
|
@@ -2737,8 +2732,7 @@
|
|
|
2737
2732
|
"AML.T0040"
|
|
2738
2733
|
],
|
|
2739
2734
|
"attack_refs": [
|
|
2740
|
-
"T1190"
|
|
2741
|
-
"T1505.003"
|
|
2735
|
+
"T1190"
|
|
2742
2736
|
],
|
|
2743
2737
|
"rwep_score": 40,
|
|
2744
2738
|
"rwep_factors": {
|
|
@@ -2755,7 +2749,6 @@
|
|
|
2755
2749
|
"epss_score": null,
|
|
2756
2750
|
"epss_date": "2026-05-14",
|
|
2757
2751
|
"cwe_refs": [
|
|
2758
|
-
"CWE-122",
|
|
2759
2752
|
"CWE-787"
|
|
2760
2753
|
],
|
|
2761
2754
|
"source_verified": "2026-05-14",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"_meta": {
|
|
3
3
|
"schema_version": "1.1.0",
|
|
4
4
|
"last_updated": "2026-05-15",
|
|
5
|
+
"last_threat_review": "2026-05-17",
|
|
5
6
|
"note": "Tracks PoC availability and weaponization stage per CVE. Update when status changes. last_verified must be within 90 days. v1.1.0 (2026-05-15): added ai_discovery_source enum + ai_assist_factor ladder (low|moderate|high|very_high) per AGENTS.md Hard Rule #7.",
|
|
6
7
|
"tlp": "CLEAR",
|
|
7
8
|
"source_confidence": {
|
|
@@ -132,8 +132,7 @@
|
|
|
132
132
|
"AML.T0051"
|
|
133
133
|
],
|
|
134
134
|
"attack_refs": [
|
|
135
|
-
"T1059"
|
|
136
|
-
"T1204"
|
|
135
|
+
"T1059"
|
|
137
136
|
],
|
|
138
137
|
"theater_test": {
|
|
139
138
|
"claim": "We hardened user applications per Essential Eight Maturity Level 2; browsers and Office are locked down.",
|
|
@@ -2184,7 +2183,10 @@
|
|
|
2184
2183
|
"opened_date": "2026-05-15",
|
|
2185
2184
|
"evidence_cves": [],
|
|
2186
2185
|
"atlas_refs": [],
|
|
2187
|
-
"attack_refs": [
|
|
2186
|
+
"attack_refs": [
|
|
2187
|
+
"T1573",
|
|
2188
|
+
"T1600"
|
|
2189
|
+
],
|
|
2188
2190
|
"theater_test": {
|
|
2189
2191
|
"claim": "Our cryptographic suite review meets PCI DSS 4.0.1 12.3.3 annual cadence.",
|
|
2190
2192
|
"test": "Pull the cryptographic suite inventory and most-recent annual review. Confirm enumeration of in-use algorithms with deprecation status. Confirm a PQC-readiness assessment exists with migration roadmap for long-lived keys (TLS for >5y data, signing for code/SBOM). Theater verdict if PQC is absent from the review, or if deprecated algorithms remain in use without a documented exception.",
|
|
@@ -3686,7 +3688,8 @@
|
|
|
3686
3688
|
"opened_date": "2026-05-15",
|
|
3687
3689
|
"evidence_cves": [
|
|
3688
3690
|
"CVE-2026-0300",
|
|
3689
|
-
"CVE-2026-42897"
|
|
3691
|
+
"CVE-2026-42897",
|
|
3692
|
+
"CVE-2026-46300"
|
|
3690
3693
|
],
|
|
3691
3694
|
"atlas_refs": [],
|
|
3692
3695
|
"attack_refs": [
|
|
@@ -3945,5 +3948,135 @@
|
|
|
3945
3948
|
],
|
|
3946
3949
|
"verdict_when_failed": "compliance-theater"
|
|
3947
3950
|
}
|
|
3951
|
+
},
|
|
3952
|
+
"UK-CAF-B4": {
|
|
3953
|
+
"framework": "UK NCSC Cyber Assessment Framework (CAF)",
|
|
3954
|
+
"control_id": "B4",
|
|
3955
|
+
"control_name": "System security",
|
|
3956
|
+
"designed_for": "Principle B4 — networks and information systems supporting essential functions are protected against attack. Covers secure configuration, secure architecture, and the management of vulnerabilities in deployed systems.",
|
|
3957
|
+
"misses": [
|
|
3958
|
+
"Subsystem-level kernel-module disable as a compensating control for an unpatched deterministic local-privilege-escalation is not enumerated as an interim posture distinct from vendor-patch application",
|
|
3959
|
+
"CAF assumes patch-application timelines tied to advisory dates; deterministic LPEs with public PoC require operational pivots (module unload, syscall filter) that the principle does not name",
|
|
3960
|
+
"Where vendor patch + reboot cycle is multi-day on operationally-sensitive hosts, the absence of a named compensating-control path forces operators to either accept the exposure window or schedule disruptive reboots without policy cover"
|
|
3961
|
+
],
|
|
3962
|
+
"real_requirement": "B4 implementation must explicitly enumerate compensating-control postures for unpatched deterministic LPEs: kernel-module blacklist (esp4 / esp6 / rxrpc class), syscall filter (seccomp profile narrowing), or live-patch where vendor offers it. Each compensating control must be reversible, monitored, and have a documented conversion-SLA to the vendor binary patch.",
|
|
3963
|
+
"status": "open",
|
|
3964
|
+
"opened_date": "2026-05-17",
|
|
3965
|
+
"evidence_cves": [
|
|
3966
|
+
"CVE-2026-46300"
|
|
3967
|
+
],
|
|
3968
|
+
"atlas_refs": [],
|
|
3969
|
+
"attack_refs": [
|
|
3970
|
+
"T1068"
|
|
3971
|
+
],
|
|
3972
|
+
"theater_test": {
|
|
3973
|
+
"claim": "Our UK CAF B4 system-security posture covers unpatched kernel LPEs with documented compensating controls.",
|
|
3974
|
+
"test": "Pull the operator's B4 evidence pack. For the most recent deterministic kernel LPE with public PoC (CVE-2026-46300 / Fragnesia is the reference case), confirm whether the evidence pack names a compensating-control posture (module unload, syscall filter, or live-patch) distinct from the binary-patch path, and whether that posture is monitored and has a conversion-SLA back to the binary patch. Theater verdict if the evidence pack reduces to 'patch within 30 days' without a named interim compensating control, or if the compensating control is deployed without monitoring and SLA.",
|
|
3975
|
+
"evidence_required": [
|
|
3976
|
+
"B4 evidence pack covering the most recent deterministic kernel LPE",
|
|
3977
|
+
"named compensating-control posture (module blacklist, seccomp, live-patch) with monitoring",
|
|
3978
|
+
"conversion-SLA documenting return-to-binary-patch timeline"
|
|
3979
|
+
],
|
|
3980
|
+
"verdict_when_failed": "compliance-theater"
|
|
3981
|
+
}
|
|
3982
|
+
},
|
|
3983
|
+
"AU-ISM-1546": {
|
|
3984
|
+
"framework": "Australian Government Information Security Manual (ISM)",
|
|
3985
|
+
"control_id": "ISM-1546",
|
|
3986
|
+
"control_name": "Patch operating systems and applications",
|
|
3987
|
+
"designed_for": "Patching operating systems and applications within timeframes set by the Essential Eight Maturity Model — ML1: 1 month for non-critical, 2 weeks for internet-facing; ML2: 2 weeks for non-critical, 48 hours for internet-facing or exploited; ML3: 48 hours for non-critical, 48 hours for internet-facing or exploited.",
|
|
3988
|
+
"misses": [
|
|
3989
|
+
"Patch-application timeframes anchor on advisory date, not on public-PoC availability — a deterministic LPE with a public PoC is exploitable from disclosure-minus-zero regardless of the 48h ML3 window",
|
|
3990
|
+
"The maturity ladder does not differentiate between exploitable-from-disclosure (public PoC + deterministic primitive) and theoretically-exploitable, so the highest-tempo bucket is still slower than the threat",
|
|
3991
|
+
"No requirement to deploy reversible compensating controls (kernel-module blacklist, syscall filter) while the patch cycle proceeds, even when the vendor offers them in the same advisory window"
|
|
3992
|
+
],
|
|
3993
|
+
"real_requirement": "ISM-1546 implementation must add: (1) a PoC-availability-aware tempo overlay where deterministic LPEs with public PoCs trigger a same-day-mitigation requirement separate from patch SLA, (2) a named compensating-control posture per maturity level (module blacklist at ML1, seccomp at ML2, live-patch at ML3), (3) explicit evidence that the operator inspected the advisory for non-binary mitigation paths before defaulting to the patch-only response.",
|
|
3994
|
+
"status": "open",
|
|
3995
|
+
"opened_date": "2026-05-17",
|
|
3996
|
+
"evidence_cves": [
|
|
3997
|
+
"CVE-2026-46300"
|
|
3998
|
+
],
|
|
3999
|
+
"atlas_refs": [],
|
|
4000
|
+
"attack_refs": [
|
|
4001
|
+
"T1068"
|
|
4002
|
+
],
|
|
4003
|
+
"theater_test": {
|
|
4004
|
+
"claim": "Our AU ISM-1546 patch programme meets Essential Eight Maturity Level 3 for kernel-class vulnerabilities.",
|
|
4005
|
+
"test": "Pull the patch-management evidence pack and select the most recent deterministic kernel LPE with public PoC (CVE-2026-46300 / Fragnesia is the reference case). Confirm whether the evidence shows (a) same-day deployment of a named compensating control (module blacklist, seccomp profile, live-patch) distinct from the binary patch, and (b) the operator documented inspection of the advisory for non-binary mitigation before defaulting to the patch SLA. Theater verdict if the evidence collapses to 'patch within 48h' without a named same-day compensating control, or if the compensating control was deployed without advisory-side evidence of evaluation.",
|
|
4006
|
+
"evidence_required": [
|
|
4007
|
+
"patch-management evidence pack for the reference deterministic LPE",
|
|
4008
|
+
"same-day compensating-control deployment record",
|
|
4009
|
+
"advisory inspection notes documenting non-binary mitigation evaluation"
|
|
4010
|
+
],
|
|
4011
|
+
"verdict_when_failed": "compliance-theater"
|
|
4012
|
+
}
|
|
4013
|
+
},
|
|
4014
|
+
"ISO-27001-2022-A.5.7": {
|
|
4015
|
+
"framework": "ISO/IEC 27001:2022",
|
|
4016
|
+
"control_id": "A.5.7",
|
|
4017
|
+
"control_name": "Threat intelligence",
|
|
4018
|
+
"designed_for": "Information about information-security threats is collected and analysed to produce threat intelligence. Output feeds risk-management, vulnerability-management, incident-management, and awareness programmes.",
|
|
4019
|
+
"misses": [
|
|
4020
|
+
"Threat intelligence collection is treated as feed ingestion; the control does not require an operational pivot when intel surfaces a same-family sequel to a previously-patched bug (Dirty Frag → Fragnesia is the reference case)",
|
|
4021
|
+
"AI-attack-development feeds (AI-assisted discovery, AI-built exploitation chains, AI-orchestrated supply-chain attacks) are not explicitly enumerated as a feed category, despite being a current-reality threat per Hard Rule #7",
|
|
4022
|
+
"Threat-intelligence-to-action latency is undefined; intel may be 'collected' weeks before the operational response, with no control text requiring conversion-SLA from intel to action"
|
|
4023
|
+
],
|
|
4024
|
+
"real_requirement": "A.5.7 implementation must add: (1) AI-attack-development feeds as a named feed category (GTIG zero-day attribution, Anthropic / OpenAI / Google threat reports, Zellic / depthfirst / Big Sleep disclosure channels), (2) intel-to-action conversion-SLA per threat category (deterministic LPE same-family sequel: 24h to compensating control), (3) explicit operational-pivot list mapping intel signal to immediate non-patch action (module blacklist, syscall filter, egress block, MFA enforcement).",
|
|
4025
|
+
"status": "open",
|
|
4026
|
+
"opened_date": "2026-05-17",
|
|
4027
|
+
"evidence_cves": [
|
|
4028
|
+
"CVE-2026-46300"
|
|
4029
|
+
],
|
|
4030
|
+
"atlas_refs": [
|
|
4031
|
+
"AML.T0010"
|
|
4032
|
+
],
|
|
4033
|
+
"attack_refs": [
|
|
4034
|
+
"T1068"
|
|
4035
|
+
],
|
|
4036
|
+
"theater_test": {
|
|
4037
|
+
"claim": "Our ISO 27001:2022 A.5.7 threat-intelligence programme drives operational action against current-reality threats including AI-assisted attack development.",
|
|
4038
|
+
"test": "Pull the threat-intelligence feed inventory and the last 12 months of intel-driven action records. Confirm explicit enumeration of AI-attack-development feed sources (GTIG, vendor threat reports, AI-assisted-disclosure outlets). Confirm an intel-to-action conversion-SLA per threat category. Sample the most recent same-family sequel disclosure (Fragnesia following Dirty Frag, or equivalent) and verify a compensating-control action fired within the SLA. Theater verdict if AI-attack-development feeds are absent from the inventory, or if intel-to-action conversion-SLA is undocumented, or if the sampled same-family sequel produced no operational pivot.",
|
|
4039
|
+
"evidence_required": [
|
|
4040
|
+
"threat-intelligence feed inventory with AI-attack-development category",
|
|
4041
|
+
"intel-to-action conversion-SLA per threat category",
|
|
4042
|
+
"operational pivot record for the most recent same-family sequel disclosure"
|
|
4043
|
+
],
|
|
4044
|
+
"verdict_when_failed": "compliance-theater"
|
|
4045
|
+
}
|
|
4046
|
+
},
|
|
4047
|
+
"NIS2-Art21-supply-chain": {
|
|
4048
|
+
"framework": "EU NIS2 Directive (Directive (EU) 2022/2555)",
|
|
4049
|
+
"control_id": "Art-21-supply-chain",
|
|
4050
|
+
"control_name": "Supply chain security measures",
|
|
4051
|
+
"designed_for": "Article 21(2)(d) — supply-chain security including security-related aspects concerning the relationships between each entity and its direct suppliers or service providers. Covers risk-management measures for the supply chain, including transitive dependencies where systemically relevant.",
|
|
4052
|
+
"misses": [
|
|
4053
|
+
"Generic supply-chain controls do not address ecosystem-specific compromise classes — npm registry account-recovery via expired maintainer-email domain, postinstall vs main-module payload distinction, and registry-account MFA enforcement are not enumerated",
|
|
4054
|
+
"Container-runtime supply chain is not differentiated from application-runtime supply chain — the runtime (containerd, runc, CRI-O) and the workloads it executes have different exposure shapes that the directive collapses",
|
|
4055
|
+
"Maintainer-account integrity is presumed; the directive does not require monitoring of maintainer-email-domain expiry, registry-side MFA enforcement on critical-path packages, or post-publish freshness cooldowns as protective measures"
|
|
4056
|
+
],
|
|
4057
|
+
"real_requirement": "NIS2 Art. 21 supply-chain measures must add ecosystem-specific controls: (1) container-runtime supply chain enumerated distinct from application supply chain with separate risk-management posture, (2) npm / PyPI / RubyGems / crates.io maintainer-account integrity monitoring (email-domain expiry, MFA enforcement, registry-side anomaly detection), (3) post-publish cooldown periods on consumption of fresh releases from systemically-important upstream maintainers, (4) postinstall vs main-module payload distinction in consumer-side defence (--ignore-scripts is insufficient against main-module payloads), (5) lockfile audit against known-malicious version sets during the active exposure window.",
|
|
4058
|
+
"status": "open",
|
|
4059
|
+
"opened_date": "2026-05-17",
|
|
4060
|
+
"evidence_cves": [
|
|
4061
|
+
"MAL-2026-NODE-IPC-STEALER"
|
|
4062
|
+
],
|
|
4063
|
+
"atlas_refs": [
|
|
4064
|
+
"AML.T0010",
|
|
4065
|
+
"AML.T0020"
|
|
4066
|
+
],
|
|
4067
|
+
"attack_refs": [
|
|
4068
|
+
"T1195.001",
|
|
4069
|
+
"T1195.002"
|
|
4070
|
+
],
|
|
4071
|
+
"theater_test": {
|
|
4072
|
+
"claim": "Our NIS2 Art. 21 supply-chain security programme covers ecosystem-specific compromise classes including container runtime and registry account-recovery abuse.",
|
|
4073
|
+
"test": "Pull the supply-chain risk-management evidence pack. Confirm container-runtime supply chain is enumerated distinct from application supply chain. Confirm maintainer-account integrity monitoring (email-domain expiry tracking, registry-side MFA enforcement evidence) for critical-path packages. Sample the most recent registry account-recovery incident (MAL-2026-NODE-IPC-STEALER reference case) and verify the consumer-side response covered lockfile audit against the malicious version set during the exposure window. Theater verdict if container runtime and application runtime collapse into a single supply-chain register, or if maintainer-account integrity monitoring is undocumented, or if the sampled incident response did not include lockfile audit within the exposure window.",
|
|
4074
|
+
"evidence_required": [
|
|
4075
|
+
"supply-chain register differentiating container runtime from application runtime",
|
|
4076
|
+
"maintainer-account integrity monitoring records for critical-path packages",
|
|
4077
|
+
"lockfile audit log from the reference registry account-recovery incident"
|
|
4078
|
+
],
|
|
4079
|
+
"verdict_when_failed": "compliance-theater"
|
|
4080
|
+
}
|
|
3948
4081
|
}
|
|
3949
4082
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"schema_version": "1.3.0",
|
|
4
4
|
"version": "1.3.0",
|
|
5
5
|
"last_updated": "2026-05-15",
|
|
6
|
+
"last_threat_review": "2026-05-17",
|
|
6
7
|
"note": "Multi-jurisdiction framework registry. patch_sla in hours. notification_sla in hours. source field must be primary regulatory source. v1.3.0 expansion: NO, MX, AR, TR, TH, PH, US_CALIFORNIA top-level jurisdictions added; EU member-state sub-regulator blocks added for Germany (BSI), France (ANSSI), Spain (AEPD + AESIA), Italy (ACN + AgID); EU-level technical body ENISA added as cross-cutting reference. v1.2.0 expansion: IL, CH, HK, TW, ID, VN, US_NYDFS added; JP expanded with APPI/PPC, FISC, NISC, METI, Economic Security Promotion Act, AI Strategy Council guidance. v1.1.0 expansion: BR, CN, ZA, AE, SA, NZ, KR, CL added; IN and CA enriched with data-protection law entries (DPDPA, Quebec Law 25, PIPEDA).",
|
|
7
8
|
"tlp": "CLEAR",
|
|
8
9
|
"source_confidence": {
|
|
@@ -381,6 +381,7 @@
|
|
|
381
381
|
"id": "package-manifests",
|
|
382
382
|
"type": "config_file",
|
|
383
383
|
"source": "Read package.json, pyproject.toml, setup.py, go.mod, Cargo.toml, pom.xml, build.gradle, Gemfile, composer.json, mix.exs at repo root and at every nested package boundary. Use Glob `**/{package.json,pyproject.toml,go.mod,Cargo.toml,pom.xml,build.gradle,Gemfile,composer.json}` excluding `node_modules`, `vendor`, `.venv`, `target`, `dist`, `build`.",
|
|
384
|
+
"air_gap_alternative": "Identical local-filesystem read; no network dependency. Same Glob/Read against the working repo.",
|
|
384
385
|
"description": "Package manifests — establish the language ecosystems present, declared crypto-adjacent dependencies, and the public API surface.",
|
|
385
386
|
"required": true
|
|
386
387
|
},
|
|
@@ -388,6 +389,7 @@
|
|
|
388
389
|
"id": "lockfiles",
|
|
389
390
|
"type": "config_file",
|
|
390
391
|
"source": "Read package-lock.json, yarn.lock, pnpm-lock.yaml, poetry.lock, uv.lock, Pipfile.lock, go.sum, Cargo.lock, composer.lock at repo root and at every package boundary.",
|
|
392
|
+
"air_gap_alternative": "Identical local-filesystem read; no network dependency. Same Read against the working repo.",
|
|
391
393
|
"description": "Lockfiles — concrete versions of crypto-adjacent deps actually shipped to consumers (openssl, sodium, libsodium-wrappers, tweetnacl, noble-curves, noble-hashes, noble-post-quantum, jsrsasign, node-forge, pqcrypto, oqs-rs, kyber-crystals, dilithium, aws-lc-rs, ring, rustls, rust-openssl).",
|
|
392
394
|
"required": false
|
|
393
395
|
},
|
|
@@ -395,6 +397,7 @@
|
|
|
395
397
|
"id": "hash-primitive-call-sites",
|
|
396
398
|
"type": "file",
|
|
397
399
|
"source": "Grep across the repo (excluding test/spec/fixture/node_modules/vendor/.venv/target/dist/build) for hash-primitive call sites. Patterns: `crypto.createHash\\(`, `crypto.createHmac\\(`, `hashlib\\.(md5|sha1|sha224|sha256|sha384|sha512|blake2b|blake2s|sha3_)`, `MessageDigest\\.getInstance\\(`, `Digest::(MD5|SHA1|SHA256)`, `hash/md5`, `hash/sha1`, `\"md5\"`, `\"sha1\"`, `\"sha-1\"`, `\"sha256\"`, `\"sha3-256\"`. Use Grep with multiline=true where the algorithm-name string is on a different line from the constructor.",
|
|
400
|
+
"air_gap_alternative": "Identical local-filesystem Grep; no network dependency. Run the same patterns against the working repo.",
|
|
398
401
|
"description": "Every hash-primitive call site. Distinguish security-context usage (signature input, MAC input, integrity check, password derivation, token derivation) from non-security usage (cache key, ETag, dedup). The non-security distinction must be evidenced inline; absent evidence, treat as security-context.",
|
|
399
402
|
"required": true
|
|
400
403
|
},
|
|
@@ -402,6 +405,7 @@
|
|
|
402
405
|
"id": "cipher-and-kex-call-sites",
|
|
403
406
|
"type": "file",
|
|
404
407
|
"source": "Grep for cipher and KEX call sites: `createCipheriv\\(`, `createDecipheriv\\(`, `crypto\\.publicEncrypt\\(`, `crypto\\.privateDecrypt\\(`, `createDiffieHellman\\(`, `createECDH\\(`, `crypto\\.generateKeyPair`, `Cipher\\.getInstance\\(`, `aesgcm`, `ChaCha20Poly1305`, `\\.(seal|open)\\(`, `OpenSSL::Cipher`, hardcoded curve names `\"P-256\"`, `\"secp256r1\"`, `\"prime256v1\"`, `\"P-384\"`, `\"secp384r1\"`, `\"P-521\"`, `\"secp521r1\"`, `\"secp256k1\"`, `\"X25519\"`, `\"X448\"`.",
|
|
408
|
+
"air_gap_alternative": "Identical local-filesystem Grep; no network dependency. Run the same patterns against the working repo.",
|
|
405
409
|
"description": "Cipher and key-exchange call sites. Identify mode (GCM/CBC/CTR/ECB), curve, key size, IV-generation pattern. ECB mode anywhere is a hard fail. CBC without HMAC is a hard fail. AES-128 without GCM authenticator is a finding.",
|
|
406
410
|
"required": true
|
|
407
411
|
},
|
|
@@ -409,6 +413,7 @@
|
|
|
409
413
|
"id": "signature-call-sites",
|
|
410
414
|
"type": "file",
|
|
411
415
|
"source": "Grep for signature operations: `crypto\\.sign\\(`, `crypto\\.verify\\(`, `Signature\\.getInstance\\(`, `\\.sign_pss\\(`, `\\.verify_pss\\(`, `RSA-PSS`, `RSA-PKCS1`, `ECDSA`, `Ed25519`, `Ed448`, `ML-DSA`, `Dilithium`, `SLH-DSA`, `SPHINCS`. Capture key sizes for RSA (look for `modulusLength`, `key_size`, `--rsa-2048` literals), curve choice for ECDSA, hash choice paired with signature (RSA-SHA1 is a hard fail).",
|
|
416
|
+
"air_gap_alternative": "Identical local-filesystem Grep; no network dependency. Run the same patterns against the working repo.",
|
|
412
417
|
"description": "Signature scheme inventory. RSA-1024 anywhere is a hard fail; RSA-2048 with > 5-year sensitivity requires PQC roadmap; ECDSA-P256 acceptable today but needs hybrid ML-DSA migration plan; bare RSA-PKCS1 (not PSS) for new signatures is a finding.",
|
|
413
418
|
"required": true
|
|
414
419
|
},
|
|
@@ -416,6 +421,7 @@
|
|
|
416
421
|
"id": "kdf-call-sites",
|
|
417
422
|
"type": "file",
|
|
418
423
|
"source": "Grep for key-derivation calls: `pbkdf2(Sync)?\\(`, `PBKDF2`, `hashlib\\.pbkdf2_hmac`, `bcrypt\\.(hash|hashSync|compare)`, `scrypt(Sync)?\\(`, `argon2\\.(hash|verify)`, `argon2id`, `hkdf\\(`, `HKDF`, `derive_key`. For each, extract the cost parameters: PBKDF2 iterations, bcrypt cost factor, scrypt N/r/p, argon2 t/m/p.",
|
|
424
|
+
"air_gap_alternative": "Identical local-filesystem Grep; no network dependency. Run the same patterns against the working repo.",
|
|
419
425
|
"description": "Key-derivation parameter inventory. Apply OWASP 2023 minimums: PBKDF2-HMAC-SHA256 >= 600,000; PBKDF2-HMAC-SHA512 >= 210,000; bcrypt cost >= 12; scrypt N >= 2^17, r=8, p=1; argon2id m >= 19 MiB (19456 KiB), t >= 2, p >= 1. Any parameter below minimum is a finding.",
|
|
420
426
|
"required": true
|
|
421
427
|
},
|
|
@@ -423,6 +429,7 @@
|
|
|
423
429
|
"id": "rng-call-sites",
|
|
424
430
|
"type": "file",
|
|
425
431
|
"source": "Grep for RNG sources: `Math\\.random\\(`, `random\\.random\\(`, `random\\.randint\\(`, `random\\.choice\\(`, `rand\\(`, `srand\\(`, `mt_rand\\(`, `secrets\\.(token_|randbits|choice)`, `crypto\\.randomBytes\\(`, `crypto\\.getRandomValues\\(`, `crypto\\.randomUUID\\(`, `os\\.urandom\\(`, `getrandom\\(`, `SecureRandom`, `OsRng`, `ThreadRng`, `/dev/urandom`, `/dev/random`. Capture file_path:line for each.",
|
|
432
|
+
"air_gap_alternative": "Identical local-filesystem Grep; no network dependency. Run the same patterns against the working repo.",
|
|
426
433
|
"description": "RNG-source inventory. Production-context Math.random / random.random / rand without cryptographic-RNG fallback is CWE-338. Distinguish test/spec/fixture usage explicitly via path allowlist; production usage requires a cryptographic RNG.",
|
|
427
434
|
"required": true
|
|
428
435
|
},
|
|
@@ -430,6 +437,7 @@
|
|
|
430
437
|
"id": "hardcoded-key-material",
|
|
431
438
|
"type": "file",
|
|
432
439
|
"source": "Grep for hardcoded crypto material: PEM markers `-----BEGIN (RSA |EC |DSA |PRIVATE |PUBLIC |CERTIFICATE )?(PRIVATE|PUBLIC) KEY-----`, SSH key prefixes `ssh-rsa AAAA`, `ssh-ed25519 AAAA`, `ecdsa-sha2-nistp256 AAAA`, hex-blob heuristics for keys (>= 64 hex chars on a single literal line), base64-encoded blobs >= 256 chars in source files. Cross-reference with the `secrets` playbook for the exfil-secret angle; here the focus is library-author shipping defaults that look like keys (e.g. example certs, demo keys, sample HMAC seeds that downstream consumers fail to rotate).",
|
|
440
|
+
"air_gap_alternative": "Identical local-filesystem Grep; no network dependency. Run the same patterns against the working repo.",
|
|
433
441
|
"description": "Hardcoded key material shipped with the library. Library-author angle: any 'demo' or 'example' key that downstream consumers fail to rotate becomes a universal-default vulnerability (cf. embedded-router demo keys, Wi-Fi default WPA keys, IoT bootloader signing keys).",
|
|
434
442
|
"required": false
|
|
435
443
|
},
|
|
@@ -437,6 +445,7 @@
|
|
|
437
445
|
"id": "tls-config-construction",
|
|
438
446
|
"type": "file",
|
|
439
447
|
"source": "Grep for in-code TLS context construction: `tls\\.createSecureContext`, `tls\\.createServer`, `https\\.createServer`, `ssl\\.SSLContext\\(`, `ssl\\.create_default_context`, `rustls::ServerConfig`, `rustls::ClientConfig`, `tls\\.Config\\{`, `SSL_CTX_new`, options like `minVersion`, `maxVersion`, `secureProtocol`, `ciphers`, `ecdhCurve`, `sigalgs`, `groups`, `ALPNProtocols`.",
|
|
448
|
+
"air_gap_alternative": "Identical local-filesystem Grep; no network dependency. Run the same patterns against the working repo.",
|
|
440
449
|
"description": "In-code TLS configuration. Library authors that construct TLS contexts internally must default to TLS 1.3 minimum, X25519MLKEM768 group preference (when openssl >= 3.5 detected), modern cipher list. Hardcoded `secureProtocol: 'TLSv1_method'` or `minVersion: 'TLSv1'` is a hard fail.",
|
|
441
450
|
"required": false
|
|
442
451
|
},
|
|
@@ -444,6 +453,7 @@
|
|
|
444
453
|
"id": "pqc-adoption-signals",
|
|
445
454
|
"type": "file",
|
|
446
455
|
"source": "Grep for PQC adoption: `ml[-_]?kem`, `ml[-_]?dsa`, `slh[-_]?dsa`, `kyber`, `dilithium`, `sphincs`, `falcon`, `kemEncapsulate`, `kemDecapsulate`, `EVP_KEM_`, `OQS_KEM_`, `oqsprovider`, `liboqs`, `noble-post-quantum`, `pqcrypto::`, `aws-lc-rs::pqc`, `circl/sign/dilithium`, `circl/kem/kyber`.",
|
|
456
|
+
"air_gap_alternative": "Identical local-filesystem Grep; no network dependency. Run the same patterns against the working repo.",
|
|
447
457
|
"description": "PQC adoption signals. If `pqc-readiness-gap` directive runs, this artifact is the primary input. Distinguish concrete cryptographic operations from configuration strings, feature-flag names, and comments.",
|
|
448
458
|
"required": false
|
|
449
459
|
},
|
|
@@ -451,6 +461,7 @@
|
|
|
451
461
|
"id": "fips-provider-activation",
|
|
452
462
|
"type": "file",
|
|
453
463
|
"source": "Grep for FIPS-mode activation: `OSSL_PROVIDER_load.*fips`, `crypto\\.setFips\\(`, `openssl::provider::Provider::load_default`, `Provider::load.*\"fips\"`, openssl.cnf or fipsmodule.cnf files in the repo, environment-variable references to `OPENSSL_FIPS`, `OPENSSL_CONF`. Capture whether the activation is conditional (e.g. only if env var set) or unconditional.",
|
|
464
|
+
"air_gap_alternative": "Identical local-filesystem Grep; no network dependency. Run the same patterns against the working repo.",
|
|
454
465
|
"description": "FIPS-provider activation evidence. The `fips-validation-status` directive uses this to distinguish runtime FIPS activation from link-time FIPS claims.",
|
|
455
466
|
"required": false
|
|
456
467
|
},
|
|
@@ -458,6 +469,7 @@
|
|
|
458
469
|
"id": "vendored-crypto-tree",
|
|
459
470
|
"type": "file",
|
|
460
471
|
"source": "Glob for vendored crypto: `vendor/**/{crypto,openssl,sodium,nacl,kyber,dilithium,sphincs,curve25519,blake2,sha3,argon2}*`, `third_party/**/*crypto*`, `crates/**/*crypto*` (excluding the package's own crypto module). Look for upstream-reference files: `UPSTREAM`, `ORIGIN`, `PROVENANCE.md`, `.upstream-commit`, integrity hashes in lockfiles.",
|
|
472
|
+
"air_gap_alternative": "Identical local-filesystem Glob; no network dependency. Run the same patterns against the working repo.",
|
|
461
473
|
"description": "Vendored cryptographic primitives. Library authors sometimes vendor crypto to reduce dep tree or for licensing reasons. Without provenance + integrity audit, vendored crypto is a supply-chain backdoor opportunity.",
|
|
462
474
|
"required": false
|
|
463
475
|
},
|
|
@@ -465,6 +477,7 @@
|
|
|
465
477
|
"id": "ci-crypto-tests",
|
|
466
478
|
"type": "file",
|
|
467
479
|
"source": "Glob for CI configs: `.github/workflows/**/*.yml`, `.gitlab-ci.yml`, `.circleci/config.yml`, `azure-pipelines.yml`, `Jenkinsfile`. Grep for crypto-test invocations, FIPS-test runs, constant-time analysis tools (`dudect`, `valgrind --tool=memcheck` on secret-dependent code, `ctgrind`).",
|
|
480
|
+
"air_gap_alternative": "Identical local-filesystem Glob + Grep; no network dependency. Run the same patterns against the working repo.",
|
|
468
481
|
"description": "Build-time crypto verification. Absence of constant-time tests on vendored PQC primitives is a finding for the `pqc-readiness-gap` directive.",
|
|
469
482
|
"required": false
|
|
470
483
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"_meta": {
|
|
3
3
|
"schema_version": "1.1.0",
|
|
4
4
|
"last_updated": "2026-05-15",
|
|
5
|
+
"last_threat_review": "2026-05-17",
|
|
5
6
|
"purpose": "Zero-day learning loop output. Each entry maps a CVE to: attack vector, defense chain analysis, framework coverage, new control requirements generated, and exposure scoring. v1.1.0 (2026-05-15): every entry now carries ai_discovered_zeroday boolean + ai_discovery_source enum + ai_discovery_date + ai_assist_factor ladder, per AGENTS.md Hard Rule #7.",
|
|
6
7
|
"note": "Never delete entries. Closed gaps are marked status: closed. History is data.",
|
|
7
8
|
"tlp": "CLEAR",
|
package/lib/lint-skills.js
CHANGED
|
@@ -759,7 +759,7 @@ function main() {
|
|
|
759
759
|
for (const o of orphans) {
|
|
760
760
|
console.log(`FAIL <orphan>`);
|
|
761
761
|
console.log(` - skill.md exists on disk but not in manifest: ${o}`);
|
|
762
|
-
console.log(` fix: re-run \`node lib/sign.js sign-all\` after adding it to manifest.json, OR delete the orphan directory`);
|
|
762
|
+
console.log(` fix: re-run sign-all (\`node $(exceptd path)/lib/sign.js sign-all\` from a contributor checkout) after adding it to manifest.json, OR delete the orphan directory`);
|
|
763
763
|
}
|
|
764
764
|
// P4 — air-gap completeness lint over data/playbooks/*.json.
|
|
765
765
|
airGapWarnings = lintPlaybookAirGap();
|
package/lib/playbook-runner.js
CHANGED
|
@@ -1679,7 +1679,7 @@ function close(playbookId, directiveId, analyzeResult, validateResult, agentSign
|
|
|
1679
1679
|
evidencePackage.signature_algorithm = 'HMAC-SHA256-session-key';
|
|
1680
1680
|
} else if (evidencePackage && evidencePackage.signed) {
|
|
1681
1681
|
evidencePackage.signature = null;
|
|
1682
|
-
evidencePackage.signature_pending = 'No session_key provided. Sign with Ed25519 via `node lib/sign.js sign-evidence <bundle.json>` post-emit.';
|
|
1682
|
+
evidencePackage.signature_pending = 'No session_key provided. Sign with Ed25519 via `node $(exceptd path)/lib/sign.js sign-evidence <bundle.json>` post-emit (contributor checkout) or `exceptd doctor --fix` to enable signing.';
|
|
1683
1683
|
}
|
|
1684
1684
|
|
|
1685
1685
|
// learning_loop lesson
|
|
@@ -2830,13 +2830,17 @@ function normalizeSubmission(submission, playbook) {
|
|
|
2830
2830
|
// which confuses detect()'s indicator-id lookup. Strip and log instead.
|
|
2831
2831
|
if (submission.signal_overrides !== undefined && submission.signal_overrides !== null
|
|
2832
2832
|
&& (typeof submission.signal_overrides !== 'object' || Array.isArray(submission.signal_overrides))) {
|
|
2833
|
-
|
|
2834
|
-
|
|
2833
|
+
// Clone before mutating _runErrors so a frozen / shared input
|
|
2834
|
+
// submission isn't modified in place. Pre-fix a caller passing a
|
|
2835
|
+
// frozen submission (Object.freeze for safety, or a shared reference
|
|
2836
|
+
// across parallel runs) threw uncaught on the _runErrors push.
|
|
2837
|
+
const carry = Array.isArray(submission._runErrors) ? submission._runErrors.slice() : [];
|
|
2838
|
+
pushRunError(carry, {
|
|
2835
2839
|
kind: 'signal_overrides_invalid',
|
|
2836
2840
|
supplied_type: Array.isArray(submission.signal_overrides) ? 'array' : typeof submission.signal_overrides,
|
|
2837
2841
|
reason: 'signal_overrides must be a plain object mapping indicator-id → verdict.'
|
|
2838
2842
|
}, { dedupeKey: e => String(e.supplied_type) });
|
|
2839
|
-
submission = { ...submission, signal_overrides: {} };
|
|
2843
|
+
submission = { ...submission, signal_overrides: {}, _runErrors: carry };
|
|
2840
2844
|
}
|
|
2841
2845
|
|
|
2842
2846
|
// v0.11.3 #71 fix: the CLI may inject `signals._bundle_formats` before
|
package/lib/scoring.js
CHANGED
|
@@ -300,7 +300,15 @@ function compare(cveId, catalog, opts) {
|
|
|
300
300
|
// SLA is insufficient. ±10 is the tightest classifier that still treats
|
|
301
301
|
// ordinary CVSS rounding noise as alignment.
|
|
302
302
|
let explanation = '';
|
|
303
|
-
|
|
303
|
+
// Surface the "no scoring signal" case distinctly from "broadly
|
|
304
|
+
// aligned". Pre-fix a CVE with rwep_score: 0 AND cvss_score: 0 (e.g.
|
|
305
|
+
// catalog entry created before scoring backfill) printed "broadly
|
|
306
|
+
// aligned" — coincidence-passing per the field-present-not-populated
|
|
307
|
+
// pitfall. Now the operator sees a specific signal pointing at the
|
|
308
|
+
// catalog gap rather than a false sense of alignment.
|
|
309
|
+
if ((rwep == null || rwep === 0) && (cvss == null || cvss === 0)) {
|
|
310
|
+
explanation = 'No scoring signal — both RWEP and CVSS are zero/null. Investigate the catalog entry; this CVE has no usable risk score.';
|
|
311
|
+
} else if (delta > 10) {
|
|
304
312
|
explanation = `RWEP significantly higher than CVSS equivalent. Factors driving delta: `;
|
|
305
313
|
const driving = [];
|
|
306
314
|
if (entry.cisa_kev) driving.push('CISA KEV (+25)');
|
package/lib/sign.js
CHANGED
|
@@ -101,6 +101,22 @@ function generateKeypair({ rotate = false } = {}) {
|
|
|
101
101
|
process.exit(1);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// Refuse to silently overwrite an existing public key when no private key
|
|
105
|
+
// is present. This is the v0.11.x signature-regression class: a host with
|
|
106
|
+
// a working pubkey but missing privkey running generate-keypair would
|
|
107
|
+
// produce a fresh pubkey divergent from every shipped signature. Operators
|
|
108
|
+
// running `exceptd doctor --fix` on a stock install would replace the
|
|
109
|
+
// shipped keys/public.pem with one whose private half exists only on
|
|
110
|
+
// their machine — every subsequent verify against shipped signatures fails.
|
|
111
|
+
// Force the operator to be explicit via --rotate (which signals intent to
|
|
112
|
+
// re-sign).
|
|
113
|
+
if (fs.existsSync(PUBLIC_KEY_PATH) && !rotate) {
|
|
114
|
+
console.error('[sign] Public key already exists at keys/public.pem but no matching private key.');
|
|
115
|
+
console.error('[sign] Refusing to overwrite the public key — that would orphan every existing signature.');
|
|
116
|
+
console.error('[sign] If you are setting up a fresh signing identity, pass --rotate to confirm. After --rotate you must re-sign all skills with sign-all.');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
104
120
|
fs.mkdirSync(KEYS_DIR, { recursive: true, mode: 0o700 });
|
|
105
121
|
fs.mkdirSync(PUBLIC_KEYS_DIR, { recursive: true });
|
|
106
122
|
|
|
@@ -115,20 +131,35 @@ function generateKeypair({ rotate = false } = {}) {
|
|
|
115
131
|
// on win32, fs.writeFileSync `mode` does not produce
|
|
116
132
|
// a POSIX-style restrictive ACL. Tighten via icacls so other desktop
|
|
117
133
|
// users on the same workstation / CI runner can't read the key.
|
|
118
|
-
restrictWindowsAcl(PRIVATE_KEY_PATH);
|
|
134
|
+
const aclHardened = restrictWindowsAcl(PRIVATE_KEY_PATH);
|
|
119
135
|
|
|
120
136
|
if (rotate) {
|
|
121
|
-
console.log('[sign] Keypair rotated. All existing signatures are now invalid —
|
|
137
|
+
console.log('[sign] Keypair rotated. All existing signatures are now invalid — re-sign with sign-all.');
|
|
122
138
|
} else {
|
|
123
139
|
console.log('[sign] Ed25519 keypair generated.');
|
|
124
140
|
console.log(` Private key: .keys/private.pem (gitignored — do not commit)`);
|
|
125
141
|
console.log(` Public key: keys/public.pem (tracked — commit this)`);
|
|
126
142
|
}
|
|
143
|
+
if (process.platform === 'win32') {
|
|
144
|
+
console.log(` Windows ACL hardened: ${aclHardened ? 'yes' : 'NO — other desktop users on this machine may be able to read the private key'}`);
|
|
145
|
+
}
|
|
127
146
|
|
|
128
147
|
console.log('\nNext steps:');
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
148
|
+
if (rotate) {
|
|
149
|
+
// After --rotate the private key IS present, so `doctor --fix`'s
|
|
150
|
+
// missing-key path won't fire. Tell the operator to re-sign
|
|
151
|
+
// directly. (doctor --fix v0.12.41+ also detects this case and
|
|
152
|
+
// chains sign-all, so either path converges.)
|
|
153
|
+
console.log(' 1. exceptd doctor --fix — detects post-rotate stale signatures and chains sign-all');
|
|
154
|
+
console.log(' (or: node $(exceptd path)/lib/sign.js sign-all — re-sign directly)');
|
|
155
|
+
console.log(' 2. exceptd doctor — confirm signatures verify against the new public key');
|
|
156
|
+
console.log(' 3. git add keys/public.pem && git commit -m "rotate signing public key"');
|
|
157
|
+
} else {
|
|
158
|
+
console.log(' 1. exceptd doctor --fix — chains sign-all after first key generation');
|
|
159
|
+
console.log(' 2. exceptd doctor — confirm signatures verify');
|
|
160
|
+
console.log(' 3. git add keys/public.pem && git commit -m "add signing public key"');
|
|
161
|
+
}
|
|
162
|
+
return { aclHardened };
|
|
132
163
|
}
|
|
133
164
|
|
|
134
165
|
/**
|
|
@@ -380,11 +411,11 @@ function signCanonicalManifest(manifest, privateKey) {
|
|
|
380
411
|
* @param {string} targetPath absolute path of the private key file
|
|
381
412
|
*/
|
|
382
413
|
function restrictWindowsAcl(targetPath) {
|
|
383
|
-
if (process.platform !== 'win32') return;
|
|
414
|
+
if (process.platform !== 'win32') return true;
|
|
384
415
|
const user = process.env.USERNAME;
|
|
385
416
|
if (!user) {
|
|
386
417
|
console.warn('[sign] WARN: USERNAME env var not set — skipping Windows ACL hardening on ' + targetPath);
|
|
387
|
-
return;
|
|
418
|
+
return false;
|
|
388
419
|
}
|
|
389
420
|
try {
|
|
390
421
|
execFileSync('icacls', [
|
|
@@ -393,6 +424,7 @@ function restrictWindowsAcl(targetPath) {
|
|
|
393
424
|
'/grant:r',
|
|
394
425
|
`${user}:F`,
|
|
395
426
|
], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
427
|
+
return true;
|
|
396
428
|
} catch (err) {
|
|
397
429
|
console.warn(
|
|
398
430
|
'[sign] WARN: icacls hardening failed on ' + targetPath + ': ' +
|
|
@@ -400,6 +432,7 @@ function restrictWindowsAcl(targetPath) {
|
|
|
400
432
|
' — the key was written but ACL inheritance was not stripped. ' +
|
|
401
433
|
'Other desktop users on this machine may be able to read it.'
|
|
402
434
|
);
|
|
435
|
+
return false;
|
|
403
436
|
}
|
|
404
437
|
}
|
|
405
438
|
|