#361·osiris

[High] SCM Supplier Risk Overlay's fire and conflict signals are dead in every real deployment (hardcoded loopback URL)

Author: jsawyerdevCreated Sep 13, 2026Updated Sep 15, 2026
LabelsTriage

Severity: High (silently dead risk signals presented as live) Confidence: High — confirmed by source inspection against this repository's own documented serverless (Vercel) deployment target; no other route in the codebase uses this pattern. Audited commit: bd4057567de8ee18d49a8b2744c9746bcd67f3e0 (master, 2026-09-13 audit).

The SCM Supplier Risk Overlay is supposed to raise a supplier's risk level when wildfires or armed conflicts are detected nearby, but two of its three signal sources are fetched from a hardcoded http://127.0.0.1:3000/... URL that does not exist in any real deployment of this Next.js app. Every supplier's fire- and conflict-based risk elevation is dead in production; only the directly-fetched earthquake signal (a real external URL) actually works.

Evidence and mechanism

  • src/app/api/scm-suppliers/route.ts:61: fetch('http://127.0.0.1:3000/api/fires', ...).
  • Line 75: fetch('http://127.0.0.1:3000/api/gdelt', ...).
  • Line 43 by contrast fetches earthquakes directly from https://earthquake.usgs.gov/... — the only one of the three signal sources that queries the real upstream instead of routing through the app's own loopback address.
  • Lines 61-90: both loopback fetches are individually try/caught at the outer level (line 88), so a connection failure is swallowed and the route still returns HTTP 200 with every supplier at whatever risk level the (working) earthquake check alone produced — no error surfaces to the client, and the response gives no indication that the fire/conflict signals didn't run.
  • This is the only route in the codebase that calls itself through a hardcoded absolute loopback URL instead of either importing the sibling route's logic directly or querying the same upstream (USGS/GDACS) its sibling routes use — confirmed by grepping the whole src/app/api tree for the same pattern.

Why this is dead in production

Next.js apps deployed on Vercel (this repo's documented target — see #250, #163) run each API route as an isolated serverless function invocation; there is no persistent process bound to localhost:3000 that a sibling function's fetch can reach. A request to http://127.0.0.1:3000/api/fires from inside /api/scm-suppliers will fail to connect on every real deployment, not just intermittently.

Impact

The Supply Chain Risk Monitor — covering major semiconductor, automotive, and battery manufacturing sites — silently never reflects nearby wildfire or armed-conflict activity in production, while still returning a confident-looking risk_level/active_threats payload with Cache-Control: no-store (i.e. presented as always fresh). A user has no way to tell the difference between "no fire/conflict risk nearby" and "the fire/conflict check never ran."

Smallest correction and acceptance criteria

  • Replace both loopback fetches with either a direct call to the same upstream the sibling routes use (USGS FIRMS fires, GDACS via the existing /api/gdelt logic), or an internal function call/shared module rather than an HTTP round-trip to the app's own address.
  • If a signal source fails, surface that in the response (e.g. a degraded_signals field) rather than silently proceeding as if only earthquakes exist.
  • Acceptance: deploying the route in a stateless serverless environment (or any environment without a service bound to 127.0.0.1:3000) must still produce working fire- and conflict-based risk elevation; a failed signal source must be visible in the response.

Prior-issue check: no existing issue covers this route. #110 (closed) covers a related but distinct class of Vercel-multi-isolate bug (module-level mutable cache) in other routes.