/** * OSIRIS Intelligence Layer — osiris-intel * * Centralized ontology engine that ingests, indexes, and correlates entities * across open-source intelligence feeds. All other services query this one * brain via GET /resolve. * * Data sources: * - OpenSanctions (OFAC SDN) — bulk CSV, refreshed every 24h * - Wikidata SPARQL — on-demand with aggressive LRU cache * * Security: * - Outbound requests only to allowlisted domains * - SPARQL inputs sanitized against injection * - Rate-limited per client IP */ const express = require('express'); const app = express(); const PORT = process.env.INTEL_PORT || 4000; // ════════════════════════════════════════════════════ // §1 — CONFIGURATION // ════════════════════════════════════════════════════ const SDN_CSV_URL = 'https://data.opensanctions.org/datasets/latest/us_ofac_sdn/targets.simple.csv'; const WIKIDATA_ENDPOINT = 'https://query.wikidata.org/sparql'; const WIKIDATA_UA = 'OSIRIS-Intel/1.0 (https://osirisai.live; ontology engine)'; const SDN_REFRESH_MS = 24 * 60 * 60 * 1000; // 24h const WIKIDATA_CACHE_TTL = 24 * 60 * 60 * 1000; // 24h const WIKIDATA_CACHE_MAX = 10_000; const ALLOWED_DOMAINS = new Set(['query.wikidata.org', 'data.opensanctions.org', 'www.wikidata.org', 'ip-api.com', 'stat.ripe.net']); // ════════════════════════════════════════════════════ // §2 — SANCTIONS INDEX (in-memory graph) // ════════════════════════════════════════════════════ let sanctionsIndex = { entries: [], byNorm: new Map(), // normalised name/alias → [entry] fetchedAt: 0, }; function normName(s) { return s.toLowerCase().replace(/[^\p{L}\p{N}\s]+/gu, ' ').replace(/\s+/g, ' ').trim(); } function parseCsv(text) { const rows = []; let field = '', row = [], inQ = false; for (let i = 0; i < text.length; i++) { const c = text[i]; if (inQ) { if (c === '"') { if (text[i + 1] === '"') { field += '"'; i++; } else inQ = false; } else field += c; } else if (c === '"') inQ = true; else if (c === ',') { row.push(field); field = ''; } else if (c === '\n') { row.push(field); rows.push(row); row = []; field = ''; } else if (c !== '\r') field += c; } if (field.length || row.length) { row.push(field); rows.push(row); } return rows; } async function loadSanctions() { console.log('[INTEL] Loading OpenSanctions OFAC SDN...'); try { const res = await fetch(SDN_CSV_URL, { signal: AbortSignal.timeout(30000), headers: { Accept: 'text/csv' }, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const text = await res.text(); const rows = parseCsv(text); if (rows.length < 2) throw new Error('CSV empty'); const headers = rows[0]; const idx = (col) => headers.indexOf(col); const i = { id: idx('id'), schema: idx('schema'), name: idx('name'), aliases: idx('aliases'), countries: idx('countries'), programs: idx('program_ids'), sanctions: idx('sanctions'), first_seen: idx('first_seen'), last_seen: idx('last_seen'), }; const entries = []; const byNorm = new Map(); for (let r = 1; r < rows.length; r++) { const row = rows[r]; if (!row[i.name]) continue; const entry = { id: row[i.id] || '', schema: row[i.schema] || 'LegalEntity', name: row[i.name], aliases: (row[i.aliases] || '').split(';').map(s => s.trim()).filter(Boolean), countries: (row[i.countries] || '').split(';').map(s => s.trim()).filter(Boolean), programs: (row[i.programs] || '').split(';').map(s => s.trim()).filter(Boolean), sanctions: row[i.sanctions] || '', first_seen: i.first_seen >= 0 ? row[i.first_seen] : undefined, }; entries.push(entry); const keys = new Set([entry.name, ...entry.aliases].map(normName)); for (const key of keys) { if (!key) continue; if (!byNorm.has(key)) byNorm.set(key, []); byNorm.get(key).push(entry); } } sanctionsIndex = { entries, byNorm, fetchedAt: Date.now() }; console.log(`[INTEL] Sanctions index loaded: ${entries.length} entities, ${byNorm.size} name keys`); } catch (e) { console.error('[INTEL] Sanctions load failed:', e.message); if (sanctionsIndex.entries.length > 0) { console.log('[INTEL] Keeping stale index'); } } } function sanctionsSearch(query, limit = 5) { if (!query || query.length < 3) return []; const q = normName(query); const exact = sanctionsIndex.byNorm.get(q) || []; if (exact.length > 0) return exact.slice(0, limit); const results = []; const seen = new Set(); for (const entry of sanctionsIndex.entries) { if (results.length >= limit) break; if (seen.has(entry.id)) continue; const n = normName(entry.name); if (n.includes(q) || entry.aliases.some(a => normName(a).includes(q))) { seen.add(entry.id); results.push(entry); } } return results; } // ════════════════════════════════════════════════════ // §3 — WIKIDATA LRU CACHE // ════════════════════════════════════════════════════ const wdCache = new Map(); // key → { data, ts } function wdCacheGet(key) { const entry = wdCache.get(key); if (!entry) return null; if (Date.now() - entry.ts > WIKIDATA_CACHE_TTL) { wdCache.delete(key); return null; } // Move to end (LRU) wdCache.delete(key); wdCache.set(key, entry); return entry.data; } function wdCacheSet(key, data) { if (wdCache.size >= WIKIDATA_CACHE_MAX) { const oldest = wdCache.keys().next().value; wdCache.delete(oldest); } wdCache.set(key, { data, ts: Date.now() }); } // ════════════════════════════════════════════════════ // §4 — WIKIDATA SPARQL (safe) // ════════════════════════════════════════════════════ function sanitizeId(id) { return id.replace(/[^a-zA-Z0-9 \-._]/g, '').trim(); } async function sparql(query) { const url = `${WIKIDATA_ENDPOINT}?query=${encodeURIComponent(query)}&format=json`; const parsed = new URL(url); if (!ALLOWED_DOMAINS.has(parsed.hostname)) { throw new Error(`Blocked domain: ${parsed.hostname}`); } const res = await fetch(url, { headers: { 'User-Agent': WIKIDATA_UA, Accept: 'application/sparql-results+json' }, signal: AbortSignal.timeout(10000), }); if (!res.ok) return []; const json = await res.json(); return json.results?.bindings || []; } // Search Wikidata for an entity by name, returns QID or null async function wdSearch(query, type = 'item') { const url = `https://www.wikidata.org/w/api.php?action=wbsearchentities&search=${encodeURIComponent(query)}&language=en&limit=1&format=json`; const parsed = new URL(url); if (!ALLOWED_DOMAINS.has(parsed.hostname)) return null; try { const res = await fetch(url, { headers: { 'User-Agent': WIKIDATA_UA }, signal: AbortSignal.timeout(5000), }); if (!res.ok) return null; const json = await res.json(); return json.search?.[0]?.id || null; } catch { return null; } } // ════════════════════════════════════════════════════ // §5 — RESOLVERS (the intelligence) // ════════════════════════════════════════════════════ function addSanctionsToGraph(query, rootId, nodes, links) { const matches = sanctionsSearch(query); for (const m of matches) { const sid = `sanction:${m.id}`; nodes.push({ id: sid, label: `⚠ ${m.name}`, type: 'sanction', properties: { schema: m.schema, countries: m.countries.join(', '), programs: m.programs.join(', '), sanctions: m.sanctions, aliases: m.aliases.slice(0, 5).join('; '), first_seen: m.first_seen, sanctioned: true, }, }); links.push({ source: rootId, target: sid, label: 'SANCTIONS MATCH' }); } } function dedup(nodes, links) { const seen = new Set(); const uNodes = []; for (const n of nodes) { if (!seen.has(n.id)) { seen.add(n.id); uNodes.push(n); } } const lSeen = new Set(); const uLinks = []; for (const l of links) { const k = `${l.source}→${l.target}→${l.label}`; if (!lSeen.has(k)) { lSeen.add(k); uLinks.push(l); } } return { nodes: uNodes, links: uLinks }; } async function resolveAircraft(id, properties = {}) { const rootId = `aircraft:${id}`; const nodes = [], links = []; const cacheKey = `aircraft:${id}:${properties.registration || ''}`; const cached = wdCacheGet(cacheKey); if (cached) return { ...cached }; const callsign = id.toUpperCase().trim(); const registration = (properties.registration || '').toUpperCase().trim(); const model = properties.model || ''; // Step 1: Decode ICAO airline prefix from callsign (e.g. TRK → Turkish Airlines) // The prefix is the alphabetic portion of the callsign const airlinePrefix = callsign.replace(/[0-9]+$/, ''); let airlineName = null; if (airlinePrefix && airlinePrefix.length >= 2) { // Search Wikidata for the ICAO airline code try { const results = await sparql(` SELECT ?item ?itemLabel ?countryLabel ?ceoLabel ?parentLabel WHERE { ?item wdt:P230 "${airlinePrefix}" . OPTIONAL { ?item wdt:P17 ?country . } OPTIONAL { ?item wdt:P169 ?ceo . } OPTIONAL { ?item wdt:P749 ?parent . } SERVICE wikibase:label { bd:serviceParam wikibase:language "en" . } } LIMIT 5`); for (const r of results) { if (r.itemLabel?.value) { airlineName = r.itemLabel.value; const airId = `company:${airlineName}`; nodes.push({ id: airId, label: airlineName, type: 'company', properties: { icao_code: airlinePrefix, source: 'Wikidata' } }); links.push({ source: rootId, target: airId, label: 'OPERATED BY' }); if (r.countryLabel?.value) { const cid = `country:${r.countryLabel.value}`; nodes.push({ id: cid, label: r.countryLabel.value, type: 'country', properties: { source: 'Wikidata' } }); links.push({ source: airId, target: cid, label: 'HEADQUARTERED' }); } if (r.ceoLabel?.value) { const pid = `person:${r.ceoLabel.value}`; nodes.push({ id: pid, label: r.ceoLabel.value, type: 'person', properties: { role: 'CEO', source: 'Wikidata' } }); links.push({ source: airId, target: pid, label: 'CEO' }); } if (r.parentLabel?.value) { const pid = `company:${r.parentLabel.value}`; nodes.push({ id: pid, label: r.parentLabel.value, type: 'company', properties: { source: 'Wikidata' } }); links.push({ source: airId, target: pid, label: 'PARENT ORG' }); } } } } catch (e) { console.warn('[INTEL] Airline ICAO lookup error:', e.message); } } // Step 2: Decode registration prefix → country (e.g. TC → Turkey, N → USA, G → UK) const REG_PREFIXES = { 'N':'United States','G':'United Kingdom','F':'France','D':'Germany','I':'Italy', 'JA':'Japan','HL':'South Korea','B':'China','VT':'India','TC':'Turkey', 'SU':'Russia','RA':'Russia','UR':'Ukraine','A6':'UAE','A7':'Qatar','9V':'Singapore', 'VH':'Australia','C':'Canada','PP':'Brazil','PR':'Brazil','PT':'Brazil', 'EC':'Spain','PH':'Philippines','HS':'Thailand','9M':'Malaysia','PK':'Pakistan', 'EP':'Iran','YI':'Iraq','HZ':'Saudi Arabia','4X':'Israel','SX':'Greece', 'OE':'Austria','HB':'Switzerland','SE':'Sweden','OH':'Finland','LN':'Norway', 'OY':'Denmark','PH':'Netherlands','OO':'Belgium','CS':'Portugal','SP':'Poland', 'OK':'Czech Republic','HA':'Hungary','YR':'Romania','LZ':'Bulgaria', 'EI':'Ireland','EW':'Belarus','ES':'Estonia','YL':'Latvia','LY':'Lithuania', }; if (registration) { let regCountry = null; // Try 2-char prefix first, then 1-char if (REG_PREFIXES[registration.substring(0, 2)]) regCountry = REG_PREFIXES[registration.substring(0, 2)]; else if (REG_PREFIXES[registration.substring(0, 1)]) regCountry = REG_PREFIXES[registration.substring(0, 1)]; if (regCountry) { const cid = `country:${regCountry}`; nodes.push({ id: cid, label: regCountry, type: 'country', properties: { source: 'Registration prefix' } }); links.push({ source: rootId, target: cid, label: 'REGISTERED IN' }); } } // Step 3: Add aircraft model info if (model) { const mid = `aircraft:model:${model}`; nodes.push({ id: mid, label: model, type: 'aircraft', properties: { type: 'model', source: 'ADS-B' } }); links.push({ source: rootId, target: mid, label: 'AIRCRAFT TYPE' }); } // Step 4: Cross-ref sanctions on airline name + callsign addSanctionsToGraph(callsign, rootId, nodes, links); if (airlineName) addSanctionsToGraph(airlineName, rootId, nodes, links); if (registration) addSanctionsToGraph(registration, rootId, nodes, links); const result = dedup(nodes, links); wdCacheSet(cacheKey, result); return result; } async function resolveVessel(id) { const rootId = `vessel:${id}`; const nodes = [], links = []; const cached = wdCacheGet(`vessel:${id}`); if (cached) return { ...cached }; try { const results = await sparql(` SELECT ?item ?itemLabel ?ownerLabel ?countryLabel ?operatorLabel ?flagLabel WHERE { { ?item wdt:P458 "${id}" . } UNION { ?item rdfs:label "${id}"@en . ?item wdt:P31/wdt:P279* wd:Q11446 . } OPTIONAL { ?item wdt:P127 ?owner . } OPTIONAL { ?item wdt:P17 ?country . } OPTIONAL { ?item wdt:P137 ?operator . } OPTIONAL { ?item wdt:P8047 ?flag . } SERVICE wikibase:label { bd:serviceParam wikibase:language "en" . } } LIMIT 10`); for (const r of results) { if (r.ownerLabel?.value) { const oid = `company:${r.ownerLabel.value}`; nodes.push({ id: oid, label: r.ownerLabel.value, type: 'company', properties: { source: 'Wikidata' } }); links.push({ source: rootId, target: oid, label: 'OWNED BY' }); } const flag = r.flagLabel?.value || r.countryLabel?.value; if (flag) { const cid = `country:${flag}`; nodes.push({ id: cid, label: flag, type: 'country', properties: { source: 'Wikidata' } }); links.push({ source: rootId, target: cid, label: 'FLAG STATE' }); } if (r.operatorLabel?.value) { const oid = `company:${r.operatorLabel.value}`; nodes.push({ id: oid, label: r.operatorLabel.value, type: 'company', properties: { source: 'Wikidata' } }); links.push({ source: rootId, target: oid, label: 'OPERATED BY' }); } } } catch (e) { console.warn('[INTEL] Wikidata vessel error:', e.message); } addSanctionsToGraph(id, rootId, nodes, links); const result = dedup(nodes, links); wdCacheSet(`vessel:${id}`, result); return result; } async function resolveCompany(id) { const rootId = `company:${id}`; const nodes = [], links = []; const cached = wdCacheGet(`company:${id}`); if (cached) return { ...cached }; try { // Use Wikidata search to find the QID first, then resolve by QID const qid = await wdSearch(id); const filter = qid ? `VALUES ?item { wd:${qid} }` : `?item rdfs:label "${id}"@en . { ?item wdt:P31/wdt:P279* wd:Q4830453 . } UNION { ?item wdt:P31/wdt:P279* wd:Q43229 . }`; const results = await sparql(` SELECT ?item ?itemLabel ?countryLabel ?parentLabel ?ceoLabel ?industryLabel WHERE { ${filter} OPTIONAL { ?item wdt:P17 ?country . } OPTIONAL { ?item wdt:P749 ?parent . } OPTIONAL { ?item wdt:P169 ?ceo . } OPTIONAL { ?item wdt:P452 ?industry . } SERVICE wikibase:label { bd:serviceParam wikibase:language "en" . } } LIMIT 10`); for (const r of results) { if (r.countryLabel?.value) { const cid = `country:${r.countryLabel.value}`; nodes.push({ id: cid, label: r.countryLabel.value, type: 'country', properties: { source: 'Wikidata' } }); links.push({ source: rootId, target: cid, label: 'HEADQUARTERED' }); } if (r.parentLabel?.value) { const pid = `company:${r.parentLabel.value}`; nodes.push({ id: pid, label: r.parentLabel.value, type: 'company', properties: { source: 'Wikidata' } }); links.push({ source: rootId, target: pid, label: 'PARENT ORG' }); } if (r.ceoLabel?.value) { const pid = `person:${r.ceoLabel.value}`; nodes.push({ id: pid, label: r.ceoLabel.value, type: 'person', properties: { role: 'CEO', source: 'Wikidata' } }); links.push({ source: rootId, target: pid, label: 'CEO' }); } } } catch (e) { console.warn('[INTEL] Wikidata company error:', e.message); } addSanctionsToGraph(id, rootId, nodes, links); const result = dedup(nodes, links); wdCacheSet(`company:${id}`, result); return result; } async function resolvePerson(id) { const rootId = `person:${id}`; const nodes = [], links = []; const cached = wdCacheGet(`person:${id}`); if (cached) return { ...cached }; try { const qid = await wdSearch(id); const filter = qid ? `VALUES ?item { wd:${qid} }` : `?item rdfs:label "${id}"@en . ?item wdt:P31 wd:Q5 .`; const results = await sparql(` SELECT ?item ?itemLabel ?nationalityLabel ?employerLabel ?positionLabel WHERE { ${filter} OPTIONAL { ?item wdt:P27 ?nationality . } OPTIONAL { ?item wdt:P108 ?employer . } OPTIONAL { ?item wdt:P39 ?position . } SERVICE wikibase:label { bd:serviceParam wikibase:language "en" . } } LIMIT 10`); for (const r of results) { if (r.nationalityLabel?.value) { const cid = `country:${r.nationalityLabel.value}`; nodes.push({ id: cid, label: r.nationalityLabel.value, type: 'country', properties: { source: 'Wikidata' } }); links.push({ source: rootId, target: cid, label: 'NATIONALITY' }); } if (r.employerLabel?.value) { const eid = `company:${r.employerLabel.value}`; nodes.push({ id: eid, label: r.employerLabel.value, type: 'company', properties: { source: 'Wikidata' } }); links.push({ source: rootId, target: eid, label: 'EMPLOYER' }); } if (r.positionLabel?.value) { const pid = `event:${r.positionLabel.value}`; nodes.push({ id: pid, label: r.positionLabel.value, type: 'event', properties: { source: 'Wikidata' } }); links.push({ source: rootId, target: pid, label: 'POSITION HELD' }); } } } catch (e) { console.warn('[INTEL] Wikidata person error:', e.message); } addSanctionsToGraph(id, rootId, nodes, links); const result = dedup(nodes, links); wdCacheSet(`person:${id}`, result); return result; } async function resolveIP(id) { const rootId = `ip:${id}`; const nodes = [], links = []; const cached = wdCacheGet(`ip:${id}`); if (cached) return { ...cached }; // Step 1: ip-api.com — geolocation, ISP, ASN, proxy/hosting detection try { const ipApiUrl = `http://ip-api.com/json/${encodeURIComponent(id)}?fields=status,message,country,countryCode,region,regionName,city,zip,lat,lon,timezone,isp,org,as,asname,mobile,proxy,hosting`; const parsed = new URL(ipApiUrl); if (!ALLOWED_DOMAINS.has(parsed.hostname)) throw new Error(`Blocked domain: ${parsed.hostname}`); const res = await fetch(ipApiUrl, { signal: AbortSignal.timeout(8000) }); if (res.ok) { const data = await res.json(); if (data.status === 'success') { // ISP node if (data.isp) { const ispId = `company:${data.isp}`; nodes.push({ id: ispId, label: data.isp, type: 'company', properties: { role: 'ISP', org: data.org || '', source: 'ip-api.com' } }); links.push({ source: rootId, target: ispId, label: 'HOSTED_BY' }); addSanctionsToGraph(data.isp, rootId, nodes, links); } // ASN node if (data.as) { const asLabel = data.asname || data.as; const asId = `company:${data.as}`; nodes.push({ id: asId, label: asLabel, type: 'company', properties: { as_number: data.as, source: 'ip-api.com' } }); links.push({ source: rootId, target: asId, label: 'ASN' }); } // Country node if (data.country) { const cid = `country:${data.country}`; nodes.push({ id: cid, label: data.country, type: 'country', properties: { code: data.countryCode || '', source: 'ip-api.com' } }); links.push({ source: rootId, target: cid, label: 'LOCATED_IN' }); addSanctionsToGraph(data.country, rootId, nodes, links); } // City node (as event type with lat/lng) if (data.city) { const cityId = `event:${data.city}`; nodes.push({ id: cityId, label: data.city, type: 'event', properties: { lat: data.lat, lon: data.lon, region: data.regionName || '', zip: data.zip || '', timezone: data.timezone || '', source: 'ip-api.com', }, }); links.push({ source: rootId, target: cityId, label: 'GEOLOCATED' }); } // Tag proxy/hosting/mobile flags on the root IP node nodes.push({ id: rootId, label: id, type: 'ip', properties: { proxy: !!data.proxy, hosting: !!data.hosting, mobile: !!data.mobile, source: 'ip-api.com', }, }); } } } catch (e) { console.warn('[INTEL] ip-api.com error:', e.message); } // Step 2: RIPEstat WHOIS try { const whoisUrl = `https://stat.ripe.net/data/whois/data.json?resource=${encodeURIComponent(id)}`; const parsed = new URL(whoisUrl); if (!ALLOWED_DOMAINS.has(parsed.hostname)) throw new Error(`Blocked domain: ${parsed.hostname}`); const res = await fetch(whoisUrl, { signal: AbortSignal.timeout(8000) }); if (res.ok) { const json = await res.json(); const records = json.data?.records || []; for (const record of records) { for (const field of record) { if (field.key === 'netname' || field.key === 'NetName') { const netId = `company:${field.value}`; nodes.push({ id: netId, label: field.value, type: 'company', properties: { role: 'Network', source: 'RIPEstat WHOIS' } }); links.push({ source: rootId, target: netId, label: 'HOSTED_BY' }); } } } } } catch (e) { console.warn('[INTEL] RIPEstat WHOIS error:', e.message); } // Step 3: RIPEstat Abuse Contact try { const abuseUrl = `https://stat.ripe.net/data/abuse-contact-finder/data.json?resource=${encodeURIComponent(id)}`; const parsed = new URL(abuseUrl); if (!ALLOWED_DOMAINS.has(parsed.hostname)) throw new Error(`Blocked domain: ${parsed.hostname}`); const res = await fetch(abuseUrl, { signal: AbortSignal.timeout(8000) }); if (res.ok) { const json = await res.json(); const contacts = json.data?.abuse_contacts || []; for (const email of contacts) { if (email) { const eid = `person:${email}`; nodes.push({ id: eid, label: email, type: 'person', properties: { role: 'Abuse Contact', source: 'RIPEstat' } }); links.push({ source: rootId, target: eid, label: 'ABUSE CONTACT' }); } } } } catch (e) { console.warn('[INTEL] RIPEstat abuse-contact error:', e.message); } // Step 4: RIPEstat Network Info try { const netUrl = `https://stat.ripe.net/data/network-info/data.json?resource=${encodeURIComponent(id)}`; const parsed = new URL(netUrl); if (!ALLOWED_DOMAINS.has(parsed.hostname)) throw new Error(`Blocked domain: ${parsed.hostname}`); const res = await fetch(netUrl, { signal: AbortSignal.timeout(8000) }); if (res.ok) { const json = await res.json(); const prefix = json.data?.prefix; const asns = json.data?.asns || []; if (prefix) { const prefId = `ip:${prefix}`; nodes.push({ id: prefId, label: prefix, type: 'ip', properties: { role: 'Prefix', source: 'RIPEstat' } }); links.push({ source: rootId, target: prefId, label: 'PREFIX' }); } for (const asn of asns) { const asnId = `company:AS${asn}`; nodes.push({ id: asnId, label: `AS${asn}`, type: 'company', properties: { as_number: `AS${asn}`, source: 'RIPEstat' } }); links.push({ source: rootId, target: asnId, label: 'ASN' }); } } } catch (e) { console.warn('[INTEL] RIPEstat network-info error:', e.message); } const result = dedup(nodes, links); wdCacheSet(`ip:${id}`, result); return result; } async function resolveCountry(id) { const rootId = `country:${id}`; const nodes = [], links = []; const cached = wdCacheGet(`country:${id}`); if (cached) return { ...cached }; try { const qid = await wdSearch(id); const filter = qid ? `VALUES ?item { wd:${qid} }` : `?item rdfs:label "${id}"@en . ?item wdt:P31 wd:Q6256 .`; const results = await sparql(` SELECT ?item ?itemLabel ?headLabel ?capitalLabel ?population ?gdp ?tld ?callingCode ?memberOfLabel ?neighborLabel WHERE { ${filter} OPTIONAL { ?item wdt:P35 ?head . } OPTIONAL { ?item wdt:P36 ?capital . } OPTIONAL { ?item wdt:P1082 ?population . } OPTIONAL { ?item wdt:P2131 ?gdp . } OPTIONAL { ?item wdt:P78 ?tld . } OPTIONAL { ?item wdt:P474 ?callingCode . } OPTIONAL { ?item wdt:P463 ?memberOf . } OPTIONAL { ?item wdt:P47 ?neighbor . } SERVICE wikibase:label { bd:serviceParam wikibase:language "en" . } } LIMIT 50`); const seenHeads = new Set(); const seenMembers = new Set(); const seenNeighbors = new Set(); let propsSet = false; for (const r of results) { // Head of state/government if (r.headLabel?.value && !seenHeads.has(r.headLabel.value)) { seenHeads.add(r.headLabel.value); const hid = `person:${r.headLabel.value}`; nodes.push({ id: hid, label: r.headLabel.value, type: 'person', properties: { role: 'Head of State', source: 'Wikidata' } }); links.push({ source: rootId, target: hid, label: 'HEAD OF STATE' }); } // Capital city if (r.capitalLabel?.value && !propsSet) { const capId = `event:${r.capitalLabel.value}`; nodes.push({ id: capId, label: r.capitalLabel.value, type: 'event', properties: { role: 'Capital', source: 'Wikidata' } }); links.push({ source: rootId, target: capId, label: 'CAPITAL' }); } // Country properties (population, GDP, TLD, calling code) if (!propsSet) { const props = { source: 'Wikidata' }; if (r.population?.value) props.population = r.population.value; if (r.gdp?.value) props.gdp = r.gdp.value; if (r.tld?.value) props.tld = r.tld.value; if (r.callingCode?.value) props.calling_code = r.callingCode.value; nodes.push({ id: rootId, label: id, type: 'country', properties: props }); propsSet = true; } // Member of (UN, NATO, EU, etc.) if (r.memberOfLabel?.value && !seenMembers.has(r.memberOfLabel.value)) { seenMembers.add(r.memberOfLabel.value); const mid = `company:${r.memberOfLabel.value}`; nodes.push({ id: mid, label: r.memberOfLabel.value, type: 'company', properties: { role: 'Organization', source: 'Wikidata' } }); links.push({ source: rootId, target: mid, label: 'MEMBER OF' }); } // Neighboring countries if (r.neighborLabel?.value && !seenNeighbors.has(r.neighborLabel.value)) { seenNeighbors.add(r.neighborLabel.value); const nid = `country:${r.neighborLabel.value}`; nodes.push({ id: nid, label: r.neighborLabel.value, type: 'country', properties: { source: 'Wikidata' } }); links.push({ source: rootId, target: nid, label: 'NEIGHBOR' }); } } } catch (e) { console.warn('[INTEL] Wikidata country error:', e.message); } addSanctionsToGraph(id, rootId, nodes, links); const result = dedup(nodes, links); wdCacheSet(`country:${id}`, result); return result; } const RESOLVERS = { aircraft: resolveAircraft, vessel: resolveVessel, company: resolveCompany, person: resolvePerson, ip: resolveIP, country: resolveCountry }; const ALLOWED_TYPES = new Set(Object.keys(RESOLVERS)); // ════════════════════════════════════════════════════ // §6 — RATE LIMITER // ════════════════════════════════════════════════════ const rateMap = new Map(); function isRateLimited(ip, limit = 30, windowMs = 60000) { const now = Date.now(); for (const [k, v] of rateMap) { if (now > v.resetAt) rateMap.delete(k); } const entry = rateMap.get(ip); if (!entry || now > entry.resetAt) { rateMap.set(ip, { count: 1, resetAt: now + windowMs }); return false; } entry.count++; return entry.count > limit; } // ════════════════════════════════════════════════════ // §7 — EXPRESS ROUTES // ════════════════════════════════════════════════════ app.get('/health', (_req, res) => { res.json({ status: 'ok', sanctions_entries: sanctionsIndex.entries.length, sanctions_loaded_at: sanctionsIndex.fetchedAt ? new Date(sanctionsIndex.fetchedAt).toISOString() : null, wikidata_cache_size: wdCache.size, uptime_seconds: Math.floor(process.uptime()), }); }); app.get('/resolve', async (req, res) => { const ip = req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.ip || 'unknown'; if (isRateLimited(ip)) return res.status(429).json({ error: 'Rate limit exceeded' }); const type = (req.query.type || '').toLowerCase().trim(); const rawId = (req.query.id || '').trim(); if (!type || !ALLOWED_TYPES.has(type)) { return res.status(400).json({ error: `Invalid type. Allowed: ${[...ALLOWED_TYPES].join(', ')}` }); } if (!rawId || rawId.length < 2 || rawId.length > 200) { return res.status(400).json({ error: 'Invalid id (2-200 chars)' }); } const id = sanitizeId(rawId); if (id.length < 2) return res.status(400).json({ error: 'ID contains too many invalid characters' }); try { const resolver = RESOLVERS[type]; // Pass extra properties for aircraft resolution (registration, model, etc.) const props = {}; if (req.query.registration) props.registration = sanitizeId(req.query.registration); if (req.query.model) props.model = sanitizeId(req.query.model); if (req.query.icao24) props.icao24 = sanitizeId(req.query.icao24); const result = await resolver(id, props); res.set('Cache-Control', 'public, s-maxage=3600, stale-while-revalidate=7200'); res.json({ nodes: result.nodes, links: result.links, entity: { type, id }, source: 'OSIRIS Intelligence Layer', sanctions_index_size: sanctionsIndex.entries.length, wikidata_cache_hits: wdCache.size, timestamp: new Date().toISOString(), }); } catch (e) { console.error('[INTEL] Resolve error:', e); res.status(500).json({ error: 'Resolution failed', nodes: [], links: [] }); } }); // ════════════════════════════════════════════════════ // §8 — STARTUP // ════════════════════════════════════════════════════ async function boot() { console.log('[INTEL] OSIRIS Intelligence Layer starting...'); await loadSanctions(); // Refresh sanctions every 24h setInterval(() => loadSanctions(), SDN_REFRESH_MS); app.listen(PORT, '0.0.0.0', () => { console.log(`[INTEL] Intelligence Layer ready on port ${PORT}`); console.log(`[INTEL] Sanctions: ${sanctionsIndex.entries.length} entities indexed`); console.log(`[INTEL] Resolve endpoint: GET /resolve?type=&id=`); }); } boot().catch(e => { console.error('[INTEL] Fatal:', e); process.exit(1); });