admin / SAynapse-Horizon
publicSelf Hosted Cyber Threat Intelligence Hub
SAynapse-Horizon / Synapse-Horizon / backend / src / sources / rss.js
2718 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | import { XMLParser } from 'fast-xml-parser' import { makeIoc, fetchWithTimeout, ok, SEVERITY } from '../normalize.js' // RSS parser — public security advisory feeds (no auth). Feeds are configurable // via the RSS_FEEDS env var (comma-separated URLs). const DEFAULT_FEEDS = [ { url: 'https://www.cisa.gov/cybersecurity-advisories/all.xml', name: 'CISA Advisories' }, { url: 'https://feeds.feedburner.com/TheHackersNews', name: 'The Hacker News' }, { url: 'https://www.bleepingcomputer.com/feed/', name: 'BleepingComputer' }, ] const PER_FEED = 6 const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' }) const HIGH_SIGNAL = /zero-day|0-day|wiper|ransomware|supply.chain|actively exploited|critical/i export const meta = { key: 'rss', requiresKey: false } export function configured() { return true } function feeds() { const env = process.env.RSS_FEEDS if (!env) return DEFAULT_FEEDS return env .split(',') .map((u) => u.trim()) .filter(Boolean) .map((url) => ({ url, name: new URL(url).host })) } const asArray = (v) => (Array.isArray(v) ? v : v ? [v] : []) const strip = (s) => String(s || '').replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim() export async function fetchSource() { const iocs = [] for (const feed of feeds()) { try { const res = await fetchWithTimeout(feed.url) const xml = await res.text() const doc = parser.parse(xml) // Support both RSS (<rss><channel><item>) and Atom (<feed><entry>). const items = asArray(doc?.rss?.channel?.item).length ? asArray(doc.rss.channel.item) : asArray(doc?.feed?.entry) for (const item of items.slice(0, PER_FEED)) { const title = strip(item.title?.['#text'] ?? item.title) if (!title) continue const link = item.link?.['@_href'] || (typeof item.link === 'string' ? item.link : '') || '' const published = item.pubDate || item.published || item.updated const critical = HIGH_SIGNAL.test(title) iocs.push( makeIoc('rss', { detectedAt: published, severity: critical ? SEVERITY.HIGH : SEVERITY.LOW, category: 'Advisory', indicator: title, indicatorType: 'advisory', target: feed.name, confidence: critical ? 70 : 55, mitre: [], headline: `Advisory: "${title}" published via the ${feed.name} feed`, raw: { title, link, published, source: feed.name }, stix: { type: 'report', name: title, labels: ['threat-report'] }, }) ) } } catch { // Skip an individual unreachable feed. } } return ok(iocs) } |