#357·osiris

[Medium] Intel sanctions refresh replaces a populated index with an unvalidated empty/malformed CSV result

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

Severity: Medium (data integrity / silent capability loss) Confidence: Confirmed by an executed zero-network reproduction of the actual refresh function. Audited commit: bd4057567de8ee18d49a8b2744c9746bcd67f3e0 (master, 2026-09-13 audit).

The intelligence service's sanctions-list refresh treats any HTTP 200 response with two or more CSV rows as a successful load, even if the CSV has none of the expected columns. A schema-drifted or damaged upstream feed silently wipes the previously loaded, working sanctions index and replaces it with an empty one — reported as healthy — until the next successful refresh (every 24 hours) or a restart.

Evidence and mechanism

  • intel/server.js:75-85: only the physical row count is validated; a missing expected header yields column index -1 with no rejection.
  • Lines 90-113: rows lacking the expected name column are dropped one by one, and the resulting (possibly empty) index unconditionally replaces the live one.
  • Lines 115-119: the code path that would retain the previous good index only runs on a thrown exception, not on a "successful" but structurally invalid response.
  • Line 708-715: /health continues to report status: 'ok' regardless.

Reproduction and observed result

Executed the actual loadSanctions()/sanctionsSearch() functions in a zero-network harness:

  1. Load a valid fixture row; sanctionsSearch('Example Sanctioned Company') returns one match.
  2. Simulate the next scheduled refresh returning HTTP 200 with unexpected,columns\nx,y (no id/name columns).
  3. The same lookup now returns zero entries; the log states the index "loaded: 0 entities"; /health still reports ok.
json
{"case":"malformed CSV replaces loaded index","entries":0,"health":"ok","loggedAsLoaded":true}

Impact

A single malformed upstream response — schema drift, a CDN error page served with HTTP 200, a truncated download — silently erases sanctions-screening capability for up to 24 hours, with no operational signal that anything degraded.

Smallest correction and acceptance criteria

  • Validate that required columns/records are present and reject the refresh (retaining the previous index) before the atomic index-assignment step, the same way the existing exception-driven retention path already works.
  • Represent initial-load failure and stale-refresh state explicitly in availability/freshness metadata; do not report an unqualified "ok" over a rejected or degraded refresh.
  • Acceptance: a refresh missing id/name columns, or otherwise structurally invalid, must leave the previously loaded entries and successful-fetch timestamp intact and must not be logged as a successful load.

Prior-issue check: #33/#151 (closed) address the unrelated Next.js /api/health endpoint; #39 addresses data provenance elsewhere. No existing issue covers loss of the intel service's sanctions index on malformed CSV.