@blamejs/exceptd-skills 0.9.1
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 +232 -0
- package/ARCHITECTURE.md +267 -0
- package/CHANGELOG.md +616 -0
- package/CONTEXT.md +203 -0
- package/LICENSE +200 -0
- package/NOTICE +82 -0
- package/README.md +307 -0
- package/SECURITY.md +73 -0
- package/agents/README.md +81 -0
- package/agents/report-generator.md +156 -0
- package/agents/skill-updater.md +102 -0
- package/agents/source-validator.md +119 -0
- package/agents/threat-researcher.md +149 -0
- package/bin/exceptd.js +183 -0
- package/data/_indexes/_meta.json +88 -0
- package/data/_indexes/activity-feed.json +362 -0
- package/data/_indexes/catalog-summaries.json +229 -0
- package/data/_indexes/chains.json +7135 -0
- package/data/_indexes/currency.json +359 -0
- package/data/_indexes/did-ladders.json +451 -0
- package/data/_indexes/frequency.json +2072 -0
- package/data/_indexes/handoff-dag.json +476 -0
- package/data/_indexes/jurisdiction-clocks.json +967 -0
- package/data/_indexes/jurisdiction-map.json +536 -0
- package/data/_indexes/recipes.json +319 -0
- package/data/_indexes/section-offsets.json +3656 -0
- package/data/_indexes/stale-content.json +14 -0
- package/data/_indexes/summary-cards.json +1736 -0
- package/data/_indexes/theater-fingerprints.json +381 -0
- package/data/_indexes/token-budget.json +2137 -0
- package/data/_indexes/trigger-table.json +1374 -0
- package/data/_indexes/xref.json +818 -0
- package/data/atlas-ttps.json +282 -0
- package/data/cve-catalog.json +496 -0
- package/data/cwe-catalog.json +1017 -0
- package/data/d3fend-catalog.json +738 -0
- package/data/dlp-controls.json +1039 -0
- package/data/exploit-availability.json +67 -0
- package/data/framework-control-gaps.json +1255 -0
- package/data/global-frameworks.json +2913 -0
- package/data/rfc-references.json +324 -0
- package/data/zeroday-lessons.json +377 -0
- package/keys/public.pem +3 -0
- package/lib/framework-gap.js +328 -0
- package/lib/job-queue.js +195 -0
- package/lib/lint-skills.js +536 -0
- package/lib/prefetch.js +372 -0
- package/lib/refresh-external.js +713 -0
- package/lib/schemas/cve-catalog.schema.json +151 -0
- package/lib/schemas/manifest.schema.json +106 -0
- package/lib/schemas/skill-frontmatter.schema.json +113 -0
- package/lib/scoring.js +149 -0
- package/lib/sign.js +197 -0
- package/lib/ttp-mapper.js +80 -0
- package/lib/validate-catalog-meta.js +198 -0
- package/lib/validate-cve-catalog.js +213 -0
- package/lib/validate-indexes.js +83 -0
- package/lib/validate-package.js +162 -0
- package/lib/validate-vendor.js +85 -0
- package/lib/verify.js +216 -0
- package/lib/worker-pool.js +84 -0
- package/manifest-snapshot.json +1833 -0
- package/manifest.json +2108 -0
- package/orchestrator/README.md +124 -0
- package/orchestrator/dispatcher.js +140 -0
- package/orchestrator/event-bus.js +146 -0
- package/orchestrator/index.js +874 -0
- package/orchestrator/pipeline.js +201 -0
- package/orchestrator/scanner.js +327 -0
- package/orchestrator/scheduler.js +137 -0
- package/package.json +113 -0
- package/sbom.cdx.json +158 -0
- package/scripts/audit-cross-skill.js +261 -0
- package/scripts/audit-perf.js +160 -0
- package/scripts/bootstrap.js +205 -0
- package/scripts/build-indexes.js +721 -0
- package/scripts/builders/activity-feed.js +79 -0
- package/scripts/builders/catalog-summaries.js +67 -0
- package/scripts/builders/currency.js +109 -0
- package/scripts/builders/cwe-chains.js +105 -0
- package/scripts/builders/did-ladders.js +149 -0
- package/scripts/builders/frequency.js +89 -0
- package/scripts/builders/jurisdiction-clocks.js +126 -0
- package/scripts/builders/recipes.js +159 -0
- package/scripts/builders/section-offsets.js +162 -0
- package/scripts/builders/stale-content.js +171 -0
- package/scripts/builders/summary-cards.js +166 -0
- package/scripts/builders/theater-fingerprints.js +198 -0
- package/scripts/builders/token-budget.js +96 -0
- package/scripts/check-manifest-snapshot.js +217 -0
- package/scripts/predeploy.js +267 -0
- package/scripts/refresh-manifest-snapshot.js +57 -0
- package/scripts/refresh-sbom.js +222 -0
- package/skills/age-gates-child-safety/skill.md +456 -0
- package/skills/ai-attack-surface/skill.md +282 -0
- package/skills/ai-c2-detection/skill.md +440 -0
- package/skills/ai-risk-management/skill.md +311 -0
- package/skills/api-security/skill.md +287 -0
- package/skills/attack-surface-pentest/skill.md +381 -0
- package/skills/cloud-security/skill.md +384 -0
- package/skills/compliance-theater/skill.md +365 -0
- package/skills/container-runtime-security/skill.md +379 -0
- package/skills/coordinated-vuln-disclosure/skill.md +473 -0
- package/skills/defensive-countermeasure-mapping/skill.md +300 -0
- package/skills/dlp-gap-analysis/skill.md +337 -0
- package/skills/email-security-anti-phishing/skill.md +206 -0
- package/skills/exploit-scoring/skill.md +331 -0
- package/skills/framework-gap-analysis/skill.md +374 -0
- package/skills/fuzz-testing-strategy/skill.md +313 -0
- package/skills/global-grc/skill.md +564 -0
- package/skills/identity-assurance/skill.md +272 -0
- package/skills/incident-response-playbook/skill.md +546 -0
- package/skills/kernel-lpe-triage/skill.md +303 -0
- package/skills/mcp-agent-trust/skill.md +326 -0
- package/skills/mlops-security/skill.md +325 -0
- package/skills/ot-ics-security/skill.md +340 -0
- package/skills/policy-exception-gen/skill.md +437 -0
- package/skills/pqc-first/skill.md +546 -0
- package/skills/rag-pipeline-security/skill.md +294 -0
- package/skills/researcher/skill.md +310 -0
- package/skills/sector-energy/skill.md +409 -0
- package/skills/sector-federal-government/skill.md +302 -0
- package/skills/sector-financial/skill.md +398 -0
- package/skills/sector-healthcare/skill.md +373 -0
- package/skills/security-maturity-tiers/skill.md +464 -0
- package/skills/skill-update-loop/skill.md +463 -0
- package/skills/supply-chain-integrity/skill.md +318 -0
- package/skills/threat-model-currency/skill.md +404 -0
- package/skills/threat-modeling-methodology/skill.md +312 -0
- package/skills/webapp-security/skill.md +281 -0
- package/skills/zeroday-gap-learn/skill.md +350 -0
- package/vendor/blamejs/LICENSE +201 -0
- package/vendor/blamejs/README.md +54 -0
- package/vendor/blamejs/_PROVENANCE.json +54 -0
- package/vendor/blamejs/retry.js +335 -0
- package/vendor/blamejs/worker-pool.js +418 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Orchestrator
|
|
2
|
+
|
|
3
|
+
The scanning and orchestration layer that ties all exceptd skills together. It scans an environment, routes findings to relevant skills, coordinates the multi-agent pipeline, and generates structured reports.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌──────────────────────────────────────────────────────┐
|
|
9
|
+
│ orchestrator/ │
|
|
10
|
+
│ │
|
|
11
|
+
│ scanner.js → dispatcher.js → pipeline.js │
|
|
12
|
+
│ │ │ │ │
|
|
13
|
+
│ ↓ ↓ ↓ │
|
|
14
|
+
│ findings.json matched skills agent handoffs │
|
|
15
|
+
│ │
|
|
16
|
+
│ event-bus.js ← external events (KEV, ATLAS, etc) │
|
|
17
|
+
│ scheduler.js ← weekly/annual currency checks │
|
|
18
|
+
└──────────────────────────────────────────────────────┘
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Components
|
|
22
|
+
|
|
23
|
+
### scanner.js
|
|
24
|
+
Discovers the security posture of the current environment:
|
|
25
|
+
- Kernel version and patch status
|
|
26
|
+
- MCP server configurations across AI coding assistants
|
|
27
|
+
- Cryptographic posture (TLS versions, algorithm inventory)
|
|
28
|
+
- Framework compliance claims
|
|
29
|
+
- AI API dependencies in use
|
|
30
|
+
|
|
31
|
+
Outputs: `findings.json` — a structured list of signals, each with a severity and a hint toward which skills apply.
|
|
32
|
+
|
|
33
|
+
### dispatcher.js
|
|
34
|
+
Routes scanner findings to relevant skills. Matches findings against skill `triggers` in `manifest.json`. Returns an ordered list of skills to invoke, sorted by RWEP urgency.
|
|
35
|
+
|
|
36
|
+
### pipeline.js
|
|
37
|
+
Coordinates the multi-agent research → validation → update → report pipeline:
|
|
38
|
+
1. **threat-researcher** — investigates new CVEs and TTPs
|
|
39
|
+
2. **source-validator** — gates data quality before it enters the catalog
|
|
40
|
+
3. **skill-updater** — applies validated findings to skill files and data
|
|
41
|
+
4. **report-generator** — produces structured output for the target audience
|
|
42
|
+
|
|
43
|
+
### event-bus.js
|
|
44
|
+
Event-driven trigger system. Fires when:
|
|
45
|
+
- CISA KEV catalog adds a new entry
|
|
46
|
+
- MITRE ATLAS publishes a new version
|
|
47
|
+
- A kernel CVE with RWEP > 80 is added to the catalog
|
|
48
|
+
- An AI/MCP platform CVE drops
|
|
49
|
+
- A compliance framework publishes an amendment
|
|
50
|
+
|
|
51
|
+
Each event triggers `skill-update-loop` and marks affected skills for review.
|
|
52
|
+
|
|
53
|
+
### scheduler.js
|
|
54
|
+
Scheduled tasks:
|
|
55
|
+
- **Weekly**: currency check on all skills (any `last_threat_review` > 30 days gets flagged)
|
|
56
|
+
- **Monthly**: full CVE catalog validation against NVD
|
|
57
|
+
- **Annual**: full skill audit — all skills reviewed against current threat landscape
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Scan current environment and produce findings
|
|
63
|
+
node orchestrator/index.js scan
|
|
64
|
+
|
|
65
|
+
# Route findings to relevant skills
|
|
66
|
+
node orchestrator/index.js dispatch
|
|
67
|
+
|
|
68
|
+
# Run a specific skill programmatically
|
|
69
|
+
node orchestrator/index.js skill kernel-lpe-triage
|
|
70
|
+
|
|
71
|
+
# Run the full agent pipeline (threat-researcher → report)
|
|
72
|
+
node orchestrator/index.js pipeline
|
|
73
|
+
|
|
74
|
+
# Check skill currency scores
|
|
75
|
+
node orchestrator/index.js currency
|
|
76
|
+
|
|
77
|
+
# Generate an executive report from current findings
|
|
78
|
+
node orchestrator/index.js report --format executive
|
|
79
|
+
|
|
80
|
+
# Watch for events and trigger updates automatically
|
|
81
|
+
node orchestrator/index.js watch
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Output Formats
|
|
85
|
+
|
|
86
|
+
The orchestrator produces output in three formats:
|
|
87
|
+
|
|
88
|
+
| Format | Audience | File |
|
|
89
|
+
|--------|----------|------|
|
|
90
|
+
| `executive` | CISO / Board | `reports/templates/executive-summary.md` |
|
|
91
|
+
| `technical` | Security Engineers | `reports/templates/technical-assessment.md` |
|
|
92
|
+
| `compliance` | Auditors / GRC | `reports/templates/compliance-gap-report.md` |
|
|
93
|
+
| `zero-day` | Incident Response | `reports/templates/zero-day-response.md` |
|
|
94
|
+
|
|
95
|
+
## Agent Handoff Protocol
|
|
96
|
+
|
|
97
|
+
When `pipeline.js` hands off between agents, it passes a structured JSON package:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"handoff_id": "uuid",
|
|
102
|
+
"from_agent": "threat-researcher",
|
|
103
|
+
"to_agent": "source-validator",
|
|
104
|
+
"timestamp": "2026-05-11T00:00:00Z",
|
|
105
|
+
"payload": {
|
|
106
|
+
"cve_id": "CVE-XXXX-XXXXX",
|
|
107
|
+
"findings": {},
|
|
108
|
+
"confidence": "high|medium|low",
|
|
109
|
+
"primary_sources": [],
|
|
110
|
+
"flags": []
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Source-validator either approves (passes to skill-updater), returns for revision, or rejects with reason.
|
|
116
|
+
|
|
117
|
+
## Environment Variables
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
EXCEPTD_SCAN_TARGETS=./ # Directories to scan for MCP configs
|
|
121
|
+
EXCEPTD_REPORT_FORMAT=technical # Default report format
|
|
122
|
+
EXCEPTD_KEV_CHECK_INTERVAL=3600 # KEV polling interval in seconds (default: 1h)
|
|
123
|
+
EXCEPTD_DATA_DIR=./data # Path to data directory
|
|
124
|
+
```
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Skill dispatcher. Routes scanner findings to relevant skills via manifest trigger matching.
|
|
5
|
+
* Returns an ordered dispatch plan sorted by RWEP urgency.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const MANIFEST_PATH = process.env.EXCEPTD_MANIFEST || path.join(__dirname, '..', 'manifest.json');
|
|
12
|
+
const SKILLS_DIR = process.env.EXCEPTD_SKILLS_DIR || path.join(__dirname, '..', 'skills');
|
|
13
|
+
|
|
14
|
+
// --- public API ---
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Route findings to skills and return a dispatch plan.
|
|
18
|
+
*
|
|
19
|
+
* @param {object[]} findings - From scanner.scan()
|
|
20
|
+
* @returns {{ plan: object[], unmatched: object[], summary: object }}
|
|
21
|
+
*/
|
|
22
|
+
function dispatch(findings) {
|
|
23
|
+
const manifest = loadManifest();
|
|
24
|
+
const plan = [];
|
|
25
|
+
const unmatched = [];
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
|
|
28
|
+
for (const finding of findings) {
|
|
29
|
+
const matched = matchFinding(finding, manifest.skills);
|
|
30
|
+
|
|
31
|
+
if (matched.length === 0) {
|
|
32
|
+
unmatched.push(finding);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const skill of matched) {
|
|
37
|
+
if (seen.has(skill.name)) continue;
|
|
38
|
+
seen.add(skill.name);
|
|
39
|
+
|
|
40
|
+
plan.push({
|
|
41
|
+
skill_name: skill.name,
|
|
42
|
+
skill_path: path.join(SKILLS_DIR, skill.name, 'skill.md'),
|
|
43
|
+
triggered_by: finding.signal,
|
|
44
|
+
finding_domain: finding.domain,
|
|
45
|
+
finding_severity: finding.severity,
|
|
46
|
+
action_required: finding.action_required,
|
|
47
|
+
priority: severityToPriority(finding.severity),
|
|
48
|
+
last_threat_review: skill.last_threat_review || 'unknown'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
plan.sort((a, b) => a.priority - b.priority);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
plan,
|
|
57
|
+
unmatched,
|
|
58
|
+
summary: {
|
|
59
|
+
total_findings: findings.length,
|
|
60
|
+
matched_findings: findings.length - unmatched.length,
|
|
61
|
+
skills_to_invoke: plan.length,
|
|
62
|
+
critical_priority: plan.filter(p => p.priority === 1).length,
|
|
63
|
+
high_priority: plan.filter(p => p.priority === 2).length
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Route a single natural language query to matching skills.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} query - Free text query
|
|
72
|
+
* @returns {object[]} Matched skills from manifest
|
|
73
|
+
*/
|
|
74
|
+
function routeQuery(query) {
|
|
75
|
+
const manifest = loadManifest();
|
|
76
|
+
const q = query.toLowerCase();
|
|
77
|
+
|
|
78
|
+
return manifest.skills.filter(skill => {
|
|
79
|
+
const triggers = skill.triggers || [];
|
|
80
|
+
return triggers.some(t => q.includes(t.toLowerCase()) || t.toLowerCase().includes(q));
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get the full dispatch context for a specific skill (data deps, frontmatter).
|
|
86
|
+
*
|
|
87
|
+
* @param {string} skillName
|
|
88
|
+
* @returns {{ skill: object, data_paths: object, skill_content: string|null }}
|
|
89
|
+
*/
|
|
90
|
+
function getSkillContext(skillName) {
|
|
91
|
+
const manifest = loadManifest();
|
|
92
|
+
const skill = manifest.skills.find(s => s.name === skillName);
|
|
93
|
+
if (!skill) return null;
|
|
94
|
+
|
|
95
|
+
const DATA_DIR = path.join(__dirname, '..', 'data');
|
|
96
|
+
const dataPaths = {};
|
|
97
|
+
for (const dep of skill.data_deps || []) {
|
|
98
|
+
const fullPath = path.join(DATA_DIR, dep);
|
|
99
|
+
dataPaths[dep] = { path: fullPath, exists: fs.existsSync(fullPath) };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const skillPath = path.join(SKILLS_DIR, skillName, 'skill.md');
|
|
103
|
+
let skillContent = null;
|
|
104
|
+
try {
|
|
105
|
+
skillContent = fs.readFileSync(skillPath, 'utf8');
|
|
106
|
+
} catch (_) {}
|
|
107
|
+
|
|
108
|
+
return { skill, data_paths: dataPaths, skill_content: skillContent };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- private helpers ---
|
|
112
|
+
|
|
113
|
+
function matchFinding(finding, skills) {
|
|
114
|
+
if (finding.skill_hint) {
|
|
115
|
+
const direct = skills.find(s => s.name === finding.skill_hint);
|
|
116
|
+
if (direct) return [direct];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const domainToSkills = {
|
|
120
|
+
kernel: ['kernel-lpe-triage', 'exploit-scoring', 'compliance-theater'],
|
|
121
|
+
mcp: ['mcp-agent-trust', 'ai-attack-surface', 'security-maturity-tiers'],
|
|
122
|
+
crypto: ['pqc-first', 'framework-gap-analysis'],
|
|
123
|
+
ai_api: ['ai-c2-detection', 'ai-attack-surface', 'threat-model-currency'],
|
|
124
|
+
framework: ['framework-gap-analysis', 'compliance-theater', 'global-grc']
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const candidateNames = domainToSkills[finding.domain] || [];
|
|
128
|
+
return skills.filter(s => candidateNames.includes(s.name));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function severityToPriority(severity) {
|
|
132
|
+
const map = { critical: 1, high: 2, medium: 3, low: 4, info: 5 };
|
|
133
|
+
return map[severity] || 5;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function loadManifest() {
|
|
137
|
+
return JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { dispatch, routeQuery, getSkillContext };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Event bus for trigger-driven skill updates.
|
|
5
|
+
*
|
|
6
|
+
* Events are typed and carry structured payloads. Handlers are registered per event type.
|
|
7
|
+
* This is an in-process event bus — no external message queue required.
|
|
8
|
+
* For production deployments, swap the internal emitter for a durable queue (Redis, SQS, etc.)
|
|
9
|
+
* without changing the event schema.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { EventEmitter } = require('events');
|
|
13
|
+
|
|
14
|
+
const EVENT_TYPES = {
|
|
15
|
+
CISA_KEV_ADDED: 'cisa.kev.added',
|
|
16
|
+
ATLAS_VERSION_RELEASED: 'atlas.version.released',
|
|
17
|
+
KERNEL_CVE_HIGH_RWEP: 'cve.kernel.high_rwep',
|
|
18
|
+
AI_PLATFORM_CVE: 'cve.ai_platform',
|
|
19
|
+
FRAMEWORK_AMENDMENT: 'framework.amendment',
|
|
20
|
+
PQC_STANDARD_UPDATE: 'pqc.standard.update',
|
|
21
|
+
EXPLOIT_STATUS_CHANGE: 'exploit.status.change',
|
|
22
|
+
NEW_ATTACK_CLASS: 'attack_class.new',
|
|
23
|
+
SKILL_CURRENCY_LOW: 'skill.currency.low'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Maps event types to the skills they should trigger for review
|
|
27
|
+
const EVENT_SKILL_MAP = {
|
|
28
|
+
[EVENT_TYPES.CISA_KEV_ADDED]: ['kernel-lpe-triage', 'exploit-scoring', 'compliance-theater', 'skill-update-loop'],
|
|
29
|
+
[EVENT_TYPES.ATLAS_VERSION_RELEASED]: ['ai-attack-surface', 'mcp-agent-trust', 'rag-pipeline-security', 'ai-c2-detection', 'skill-update-loop'],
|
|
30
|
+
[EVENT_TYPES.KERNEL_CVE_HIGH_RWEP]: ['kernel-lpe-triage', 'exploit-scoring', 'zeroday-gap-learn', 'framework-gap-analysis'],
|
|
31
|
+
[EVENT_TYPES.AI_PLATFORM_CVE]: ['mcp-agent-trust', 'ai-attack-surface', 'zeroday-gap-learn'],
|
|
32
|
+
[EVENT_TYPES.FRAMEWORK_AMENDMENT]: ['framework-gap-analysis', 'compliance-theater', 'global-grc', 'policy-exception-gen'],
|
|
33
|
+
[EVENT_TYPES.PQC_STANDARD_UPDATE]: ['pqc-first', 'framework-gap-analysis'],
|
|
34
|
+
[EVENT_TYPES.EXPLOIT_STATUS_CHANGE]: ['exploit-scoring', 'kernel-lpe-triage', 'compliance-theater'],
|
|
35
|
+
[EVENT_TYPES.NEW_ATTACK_CLASS]: ['threat-model-currency', 'ai-attack-surface', 'skill-update-loop'],
|
|
36
|
+
[EVENT_TYPES.SKILL_CURRENCY_LOW]: ['skill-update-loop']
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
class ExceptdEventBus extends EventEmitter {
|
|
40
|
+
constructor() {
|
|
41
|
+
super();
|
|
42
|
+
this.eventLog = [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Emit a typed event with structured payload.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} eventType - One of EVENT_TYPES
|
|
49
|
+
* @param {object} payload - Event-specific data
|
|
50
|
+
*/
|
|
51
|
+
emit(eventType, payload) {
|
|
52
|
+
const event = {
|
|
53
|
+
event_id: `evt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
54
|
+
type: eventType,
|
|
55
|
+
timestamp: new Date().toISOString(),
|
|
56
|
+
payload,
|
|
57
|
+
affected_skills: EVENT_SKILL_MAP[eventType] || []
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this.eventLog.push(event);
|
|
61
|
+
super.emit(eventType, event);
|
|
62
|
+
super.emit('*', event);
|
|
63
|
+
return event;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Register a handler for an event type.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} eventType
|
|
70
|
+
* @param {function} handler - (event) => void
|
|
71
|
+
*/
|
|
72
|
+
on(eventType, handler) {
|
|
73
|
+
super.on(eventType, handler);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Register a handler for all events.
|
|
79
|
+
*
|
|
80
|
+
* @param {function} handler - (event) => void
|
|
81
|
+
*/
|
|
82
|
+
onAny(handler) {
|
|
83
|
+
return this.on('*', handler);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get all events that affected a specific skill.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} skillName
|
|
90
|
+
* @returns {object[]}
|
|
91
|
+
*/
|
|
92
|
+
getSkillEvents(skillName) {
|
|
93
|
+
return this.eventLog.filter(e => e.affected_skills.includes(skillName));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get the event log, optionally filtered by type.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} [eventType]
|
|
100
|
+
* @returns {object[]}
|
|
101
|
+
*/
|
|
102
|
+
getLog(eventType) {
|
|
103
|
+
if (eventType) return this.eventLog.filter(e => e.type === eventType);
|
|
104
|
+
return [...this.eventLog];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Fire a CISA KEV addition event.
|
|
109
|
+
*
|
|
110
|
+
* @param {{ cve_id: string, kev_date: string, rwep_score: number }} params
|
|
111
|
+
*/
|
|
112
|
+
kevAdded({ cve_id, kev_date, rwep_score }) {
|
|
113
|
+
return this.emit(EVENT_TYPES.CISA_KEV_ADDED, { cve_id, kev_date, rwep_score });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Fire an ATLAS version release event.
|
|
118
|
+
*
|
|
119
|
+
* @param {{ old_version: string, new_version: string, release_date: string }} params
|
|
120
|
+
*/
|
|
121
|
+
atlasReleased({ old_version, new_version, release_date }) {
|
|
122
|
+
return this.emit(EVENT_TYPES.ATLAS_VERSION_RELEASED, { old_version, new_version, release_date });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Fire an exploit status change event.
|
|
127
|
+
*
|
|
128
|
+
* @param {{ cve_id: string, old_status: string, new_status: string }} params
|
|
129
|
+
*/
|
|
130
|
+
exploitStatusChanged({ cve_id, old_status, new_status }) {
|
|
131
|
+
return this.emit(EVENT_TYPES.EXPLOIT_STATUS_CHANGE, { cve_id, old_status, new_status });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Fire a skill currency low event (emitted by scheduler).
|
|
136
|
+
*
|
|
137
|
+
* @param {{ skill_name: string, currency_score: number, days_since_review: number }} params
|
|
138
|
+
*/
|
|
139
|
+
skillCurrencyLow({ skill_name, currency_score, days_since_review }) {
|
|
140
|
+
return this.emit(EVENT_TYPES.SKILL_CURRENCY_LOW, { skill_name, currency_score, days_since_review });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const bus = new ExceptdEventBus();
|
|
145
|
+
|
|
146
|
+
module.exports = { bus, EVENT_TYPES, EVENT_SKILL_MAP };
|