nspiration On February 11, 2024, a substation fire in southern New Mexico triggered a cascading grid event that briefly cut power to over 40,000 customers across three states. The cause wasn't a sophisticated cyberattack — it was a single bushing failure during a winter cold snap. But the incident response exposed a hard truth: even when SOC teams have access to world-class threat-intel feeds like Censys, MITRE ATT&CK, CISA KEV, NASA FIRMS, and USGS, those feeds live in silos. An analyst tracking a suspicious Modbus exposure on a Michigan substation had to manually cross-reference an Ontario wildfire dashboard, three different CVE databases, and a NOAA weather alert page before they could decide whether the asset was actually at elevated risk.

That gap — between raw signal and operational decision — is what Sentinel-OSINT was built to close. The inspiration wasn't a single dramatic event but a recurring pattern: critical-infrastructure defenders spend more time correlating feeds than acting on them. Every minute spent stitching together "is this CVE exploited? is there a wildfire nearby? is this asset on the CISA KEV list?" is a minute the adversary has to move. We wanted to compress that workflow from hours to seconds, and we wanted to do it without asking the operator to leave a single URL.

A second, quieter inspiration was the realization that the open intelligence community has already built most of the pieces — MITRE publishes ATT&CK under CC BY 4.0, USGS and NOAA are public domain, NASA FIRMS is freely usable, CISA KEV is a public catalog. The technical challenge wasn't access; it was fusion. Nobody had assembled these feeds into a single per-asset dossier that a SOC analyst could read in 90 seconds and act on. So we built it.

What it does Sentinel-OSINT turns a raw exposed-asset data point into a SOC-ready, national-security-grade intelligence dossier. The pipeline has four stages:

Ingest. The platform queries the Censys Platform API v3 for ICS/SCADA services — Modbus, Siemens S7, DNP3, EtherNet/IP, IEC-104, BACnet, and a dozen others — scoped to location.country_code: US, with cursor pagination that can walk through tens of thousands of exposed assets without rate-limit friction. When no Censys key is configured, it falls back to a mock feed so the rest of the pipeline remains demoable.

Analyze. For every asset the operator flags, a Tavily advanced search generates a strategic brief containing: a P1–P3 priority score (calibrated against MITRE ATT&CK technique prevalence + CISA KEV overlap + proximity to active physical threats), a citation list with source URLs, MITRE ATT&CK technique mapping with explicit confidence score + rationale + evidence snippets, and CISA KEV / NVD cross-references for every CVE on the asset. Every claim is evidence-linked; nothing is asserted without a source.

Geo-fuse. A cached GeoEvent store aggregates five independent feeds: GDACS (global disasters — wildfires, floods, storms, quakes), NASA FIRMS (VIIRS SNPP NRT active-fire pixels with severity derived from FRP), USGS (earthquakes ≥ M4.5 in the past 7 days), NOAA CAP (active US weather alerts via polygon centroids), and a Tavily radius news sweep for locale-scoped incident reporting. The cache refreshes every 15 minutes — fast enough to catch a developing wildfire, slow enough to avoid rate-limit risk.

Each asset brief surfaces nearby events with haversine distance + compass bearing. This is why an Ontario wildfire correctly registers as a cross-border threat to a Michigan grid asset — the system doesn't care about political borders, only physical proximity.

Operate. A bulk-analyze queue with configurable exponential backoff and retries processes watchlists of hundreds of assets. Snapshot diffing surfaces new and closed assets between runs. Slack/webhook notifications fire on P1 findings. An ATT&CK tactics heatmap with drill-down lets analysts see technique prevalence across the whole fleet. And every dossier can be exported as Markdown, PDF, JSON, CSV, or STIX 2.1 — so the same intelligence can flow into a SOC dashboard, a ticketing system, or a threat-intel platform without reformatting.

The result: one URL per asset, one dossier, every relevant signal fused.

How we built it The stack is intentionally modern and intentionally serverless-first — because critical-infrastructure tooling needs to be deployable in a SOC's existing cloud footprint, not shipped as a virtual appliance.

Framework. TanStack Start v1 on React 19, Vite 7, with Cloudflare Worker SSR. All third-party API calls happen server-side via createServerFn RPC — no secrets ever reach the browser. The same-origin RPC pattern means there are no public /api/public/* endpoints to enumerate or abuse.

Data layer. TanStack Query for client caching, in-worker caches for the geo-feed store (15-minute TTL keyed by feed type). Stateful watchlist data is localStorage-only — no PII collection, no end-user auth, no server-side session storage.

UI. Tailwind v4 with shadcn/ui primitives and lucide-react icons. The dark Mapbox GL map on /map uses server-gated tokens via the Lovable Mapbox connector — the public token is domain-restricted in the Mapbox dashboard, and the server-side gateway token never ships to the client.

Intel APIs. Tavily Search API (advanced mode) for strategic briefs and the radius news sweep. Censys Platform API v3 for ICS/SCADA discovery. Both keys live in Lovable Cloud and are injected at runtime only.

Geo feeds. GDACS (XML), NASA FIRMS (CSV via FIRMS MAP_KEY), USGS earthquakes (GeoJSON, public domain), NOAA CAP via api.weather.gov (public domain). Each feed has its own adapter that normalizes to a common GeoEvent shape with severity, geometry, timestamp, and source attribution.

Frameworks. MITRE ATT&CK (both ICS and Enterprise matrices) for technique mapping. CISA KEV catalog for exploitation-priority cross-reference. NVD for severity scoring. The OSINT Framework served as the initial taxonomy reference for OSINT tooling categories.

Architecture decision: fusion over collection. The hardest design choice was deciding what not to build. We considered building our own Shodan-style scanner, our own threat-intel enrichment API, our own news aggregator. We rejected all three. The value isn't in collecting more data — it's in fusing the data that already exists. Every hour spent re-collecting would have been an hour not spent on the per-asset fusion layer, which is the actual product.

The architecture diagram in the README captures the data flow: Censys v3 → listExposedAssets → Threat Matrix UI → analyzeAsset (with ATT&CK + KEV + NVD + Tavily fusion) → getProximityFeed (the cached geo-event store, fed by GDACS + FIRMS + USGS + NOAA + Tavily) → SOC Dossier with Markdown / PDF / CSV / JSON / STIX 2.1 export.

Challenges we ran into

  1. GitHub Push Protection blocked our first deploy. Midway through development, an automated Lovable commit pushed a .env file containing a Mapbox publishable token to a public repo. GitHub's secret scanner caught it and rejected the push — but the leak was already in the local commit history. We had to install git-filter-repo, purge the .env file from all 135 commits, prune the empty no-op commits that resulted, add .env to .gitignore, create an .env.example to document the env vars without leaking values, and force-push the cleaned history to overwrite the public default branch. Lesson: even pk.* publishable tokens trigger GitHub Push Protection, and history rewriting is the only safe recovery.

  2. The cross-border threat problem. The first version of the proximity feed only loaded US-bounded events. When we tested against a Detroit substation, the system missed a major Ontario wildfire that was 60 km across the border — even though a fire that close is a real physical threat to cross-border grid interconnects. We had to expand the GDACS and FIRMS queries to fetch global events and rely on haversine distance + compass bearing to surface relevant cross-border threats. This is now one of the product's signature features.

  3. Mapbox token security. Mapbox public tokens (pk.*) are designed for client-side use, but shipping them in the bundle still exposes them to scraping. We routed all map tile requests through a server-side gateway that injects the public token at runtime via the Lovable Mapbox connector, and we domain-restricted the token in the Mapbox dashboard so it can only be used from our deployed domain. The server-side gateway token never reaches the browser.

  4. Censys rate limits with cursor pagination. Censys Platform API v3 enforces a fairly aggressive request-per-minute limit. For watchlists of hundreds of assets, a naive query loop would hit the limit within seconds. We implemented cursor-based pagination with exponential backoff and retry — the bulk-analyze queue can now walk through thousands of results without exceeding the rate budget, and individual asset failures don't abort the batch.

  5. NOAA CAP polygon handling. NOAA weather alerts come as polygons, not points. The first implementation tried to use the polygon centroid as the location — but for elongated alerts (winter storm warnings that stretch across three states), the centroid can be hundreds of kilometers from the actual threat area. We settled on centroid for the v1 implementation, with a TODO to switch to bounding-box intersection in v2.

  6. The "what is a P1?" calibration problem. Assigning a P1 / P2 / P3 priority score sounds simple until you try to define the thresholds. Is a CISA KEV-listed CVE on an exposed Modbus port always a P1? What if the asset is in a low-population area with no nearby physical threats? What if it's a P3 CVE but the asset is across the street from a hospital? We ended up with a weighted scoring function that combines ATT&CK technique prevalence, KEV overlap, NVD severity, and proximity-weighted physical threat density. The weights are tuned heuristically and will need real-world calibration against actual incident data.

Accomplishments that we're proud of The 8-source fusion. Eight independent open intelligence feeds — Censys, Tavily, MITRE ATT&CK, CISA KEV, NASA FIRMS, GDACS, USGS, NOAA — unified into a single per-asset dossier. Nobody else in the OSINT tooling space has assembled these particular feeds into one SOC-ready output. The dossier is designed to be read in 90 seconds and acted on immediately.

The cross-border threat detection. The Ontario-wildfire-to-Michigan-grid example isn't a marketing bullet — it's a real feature that demonstrates the system thinks in physical distance, not political borders. This is the kind of insight that a human analyst could produce but rarely does, because it requires cross-referencing two dashboards they don't usually check at the same time.

The export matrix. Markdown, PDF, JSON, CSV, and STIX 2.1 — five export formats that map to five different downstream workflows. A SOC lead reads the PDF. A ticketing system ingests the JSON. A threat-intel platform ingests the STIX. A spreadsheet analyst works the CSV. A static-site threat-intel blog can render the Markdown. One dossier, five audiences.

The security posture. Zero public API endpoints. Zero secrets shipped to the browser. Zero PII collection. Zero end-user auth (because there's nothing to authenticate against). All third-party keys live server-side in Lovable Cloud and are injected at runtime. The Mapbox public token is domain-restricted. The watchlist is localStorage-only. We're proud of how much defensive capability we delivered with how small an attack surface.

The build itself. 135 commits across Lovable-managed and manual edits, shipped in a single weekend, with a 3-minute demo video and a 1280×720 thumbnail. The repo is now mirrored across GitHub (icohangar-ops + Cubiczan) and Codeberg (cubiczan) for redundancy, with the leaked .env purged from all three.

What we learned The OSINT community has already built the pieces. The hardest realization was that the technical barriers to building this have fallen. MITRE publishes ATT&CK under CC BY 4.0. USGS and NOAA are public domain. NASA FIRMS is freely usable. CISA KEV is a public catalog. Censys and Tavily have clean, well-documented APIs. The reason nobody had built Sentinel-OSINT before wasn't technical — it was that the people who knew the feeds existed didn't know how to fuse them, and the people who could fuse them didn't know the feeds existed.

Fusion is the product, not collection. Every hour we considered spending on building our own scanner or our own enrichment API was an hour we didn't spend on the per-asset fusion layer, which is the actual value. This generalizes: in 2026, the moat for threat-intel tooling is not data access, it's data correlation.

Serverless-first is the right default for SOC tooling. A virtual appliance means a procurement cycle, a security review, a network diagram, and a quarterly patch process. A Cloudflare Worker means a deploy URL. SOC teams are understaffed; asking them to install anything is a non-starter. The same-origin RPC pattern (no public /api/* endpoints) keeps the attack surface minimal without sacrificing capability.

git-filter-repo is essential infrastructure. History rewriting used to be a black art. git-filter-repo made it a 4-second operation. Every team shipping public code should have it installed before they need it, because the moment you need it, you need it urgently.

Per-glyph font fallback is real. A smaller, sillier lesson: matplotlib 3.9+ supports per-glyph font fallback, so a single font.sans-serif list with ['DejaVu Sans', 'Noto Sans SC'] will render Latin glyphs from DejaVu and CJK glyphs from Noto Sans SC without manual per-text-element font switching. This matters more than you'd think for internationalized dashboards.

What's next for Sentinel-OSINT — Cyber-Physical Threat Matrix STIX 2.1 bundle export. The current STIX export emits per-indicator objects. The next version will emit a complete STIX bundle per asset — observed-data, indicator, vulnerability, attack-pattern, identity, and relationship objects — so the export can be ingested directly into a TAXII server or a SIEM that supports STIX 2.1 natively.

NOAA polygon intersection. Replace the centroid-based location for NOAA weather alerts with proper polygon intersection against the asset's coordinates. This will eliminate the false-negative cases where an asset is inside a winter-storm warning polygon but far from the polygon's centroid.

Real-world P1 calibration. The current priority weights are heuristically tuned. The next milestone is to calibrate them against a labeled dataset of historical ICS incidents — CISA's annual risk reports, ICS-CERT advisories, and public incident retrospectives — to produce a defensible scoring model rather than an intuitive one.

Multi-tenant SOC deployment. The current architecture is single-tenant. For SOC teams to adopt this internally, we need per-organization watchlist isolation, SSO via SAML or OIDC, audit logging of who queried which asset and when, and role-based access control for P1 findings (which may be export-controlled under CISA's Cyber Incident Reporting for Critical Infrastructure Act requirements).

Cross-asset correlation. Today, each asset dossier is independent. The next major release will introduce cross-asset correlation: if two assets on the same watchlist both show ATT&CK technique T0817 (Drive-by Compromise) within a 24-hour window, the system should flag this as a potential coordinated campaign rather than two independent findings.

Self-hostable Docker image. While the Cloudflare Worker deployment is the right default, some SOC environments require fully air-gapped deployment. We'll ship a Docker image that runs the full stack (TanStack Start server + bundled geo-feed adapters + a local SQLite cache for the GeoEvent store) so it can be deployed in a classified environment with no outbound internet.

Open-source the fusion layer. Longer term, we want to extract the analyzeAsset + getProximityFeed fusion logic into a standalone MIT-licensed library that other OSINT tooling can consume. The UI is a thin shell over the fusion engine; the engine itself is the durable value, and it should be open.

Built With

  • bun
  • censys-api
  • cisa-kev
  • cloudflare
  • cloudflare-workers
  • css
  • gdacs
  • html
  • javascript
  • lucide-react
  • mapbox-gl
  • mitre
  • nasa-firms
  • noaa-api
  • nvd
  • radix-ui
  • react
  • shadcn/ui
  • tailwind-css
  • tanstack-query
  • tanstack-start
  • tavily-api
  • typescript
  • usgs-earthquake-api
  • vite
Share this project:

Updates