Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ AISSTREAM_API_KEY=
ACLED_EMAIL=
# OAuth2 password grant (API keys deprecated Sept 2025)
ACLED_PASSWORD=
# Cloudflare Radar internet outages & traffic anomalies (free: dash.cloudflare.com/profile/api-tokens, Account Analytics Read)
CLOUDFLARE_API_TOKEN=

# === Server Configuration ===

Expand Down
10 changes: 9 additions & 1 deletion apis/briefing.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ import { briefing as space } from './sources/space.mjs';
// === Tier 5: Live Market Data ===
import { briefing as yfinance } from './sources/yfinance.mjs';

// === Tier 6: Cyber & Infrastructure ===
import { briefing as cisaKev } from './sources/cisa-kev.mjs';
import { briefing as cloudflareRadar } from './sources/cloudflare-radar.mjs';

const SOURCE_TIMEOUT_MS = 30_000; // 30s max per individual source

export async function runSource(name, fn, ...args) {
Expand All @@ -63,7 +67,7 @@ export async function runSource(name, fn, ...args) {
}

export async function fullBriefing() {
console.error('[Crucix] Starting intelligence sweep — 27 sources...');
console.error('[Crucix] Starting intelligence sweep — 29 sources...');
const start = Date.now();

const allPromises = [
Expand Down Expand Up @@ -103,6 +107,10 @@ export async function fullBriefing() {

// Tier 5: Live Market Data
runSource('YFinance', yfinance),

// Tier 6: Cyber & Infrastructure
runSource('CISA-KEV', cisaKev),
runSource('Cloudflare-Radar', cloudflareRadar),
];

// Each runSource has its own 30s timeout, so allSettled will resolve
Expand Down
144 changes: 144 additions & 0 deletions apis/sources/cisa-kev.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// CISA KEV — Known Exploited Vulnerabilities Catalog
// No auth required. Tracks CVEs actively exploited in the wild.
// Federal agencies must patch these within due dates — useful signal
// for cybersecurity posture and active threat landscape.

import { safeFetch } from '../utils/fetch.mjs';

const KEV_URL = 'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json';

function summarizeVulnerabilities(vulns) {
if (!vulns.length) return {};

// Recent additions (last 30 days)
const thirtyDaysAgo = new Date(Date.now() - 30 * 86400_000);
const recent = vulns.filter(v => {
const added = new Date(v.dateAdded);
return !isNaN(added) && added >= thirtyDaysAgo;
});

// Group by vendor
const byVendor = {};
for (const v of vulns) {
const vendor = v.vendorProject || 'Unknown';
byVendor[vendor] = (byVendor[vendor] || 0) + 1;
}

// Top vendors sorted by count
const topVendors = Object.entries(byVendor)
.sort((a, b) => b[1] - a[1])
.slice(0, 15)
.map(([vendor, count]) => ({ vendor, count }));

// Ransomware-linked
const ransomwareLinked = vulns.filter(v => v.knownRansomwareCampaignUse === 'Known');

// Overdue (due date has passed)
const now = new Date();
const overdue = vulns.filter(v => {
const due = new Date(v.dueDate);
return !isNaN(due) && due < now;
});

// Group recent by product for signal detection
const recentByProduct = {};
for (const v of recent) {
const key = `${v.vendorProject} ${v.product}`;
if (!recentByProduct[key]) recentByProduct[key] = [];
recentByProduct[key].push(v);
}

const hotProducts = Object.entries(recentByProduct)
.filter(([, vs]) => vs.length >= 2)
.sort((a, b) => b[1].length - a[1].length)
.slice(0, 10)
.map(([product, vs]) => ({
product,
count: vs.length,
cves: vs.map(v => v.cveID)
}));

return {
totalInCatalog: vulns.length,
recentAdditions: recent.length,
ransomwareLinked: ransomwareLinked.length,
overdueCount: overdue.length,
topVendors,
hotProducts,
};
}

export async function briefing() {
const data = await safeFetch(KEV_URL, { timeout: 20000 });

if (data.error) {
return {
source: 'CISA-KEV',
timestamp: new Date().toISOString(),
error: data.error,
};
}

const vulns = data.vulnerabilities || [];
const catalogVersion = data.catalogVersion || null;
const dateReleased = data.dateReleased || null;

const summary = summarizeVulnerabilities(vulns);

// Get the 20 most recently added
const sorted = [...vulns]
.sort((a, b) => new Date(b.dateAdded) - new Date(a.dateAdded));

const recentEntries = sorted.slice(0, 20).map(v => ({
cveID: v.cveID,
vendorProject: v.vendorProject,
product: v.product,
vulnerabilityName: v.vulnerabilityName,
dateAdded: v.dateAdded,
dueDate: v.dueDate,
shortDescription: (v.shortDescription || '').substring(0, 300),
knownRansomwareCampaignUse: v.knownRansomwareCampaignUse,
}));

// Signals — actionable intelligence
const signals = [];

if (summary.recentAdditions > 5) {
signals.push({
severity: 'high',
signal: `${summary.recentAdditions} new KEV entries in last 30 days — elevated exploit activity`,
});
}

if (summary.hotProducts?.length > 0) {
const top = summary.hotProducts[0];
signals.push({
severity: 'medium',
signal: `${top.product} has ${top.count} actively exploited CVEs recently added`,
});
}

const ransomwareRecent = recentEntries.filter(v => v.knownRansomwareCampaignUse === 'Known');
if (ransomwareRecent.length > 0) {
signals.push({
severity: 'critical',
signal: `${ransomwareRecent.length} recently added CVEs linked to ransomware campaigns`,
});
}

return {
source: 'CISA-KEV',
timestamp: new Date().toISOString(),
catalogVersion,
dateReleased,
summary,
vulnerabilities: recentEntries,
signals,
};
}

// Run standalone
if (process.argv[1]?.endsWith('cisa-kev.mjs')) {
const data = await briefing();
console.log(JSON.stringify(data, null, 2));
}
Loading
Loading