Developer guide

EU financial sanctions data in Node.js: inspect an official XML file

Inspect an official EU sanctions XML snapshot in Node.js, retain its hash and provenance, and keep structural parsing separate from screening approval.

On this page

Choose the official-data path deliberately

Use the European Commission’s financial sanctions database and official EU dataset page to locate the current downloadable resource. The data portal, legal acts, and normalized third-party coverage are different resources. Do not copy a legacy XML endpoint or schema from an old tutorial and assume it is still the current interface.

SanctionsKit’s published coverage currently describes this source as reference-only. This guide does not invent a selectable API source ID or promise a production screening endpoint for it. It provides a complete local snapshot-inspection utility, not a matching engine or a finished ingestion service.

Download and record the actual provenance

Follow the official authority page to its current XML download. Save the original bytes as snapshot.xml without editing them. Record the exact official HTTPS URL in SOURCE_URL and the actual download time in DOWNLOADED_AT using a UTC ISO timestamp such as YYYY-MM-DDTHH:MM:SSZ.

The script reads a local file and does not fetch arbitrary URLs. A source URL and SHA-256 hash make the captured artifact traceable; neither proves that the operator downloaded authentic data or that the file is complete and current.

Install the parser dependency

Use Node.js 22 or later for this separate data utility, and install the XML dependency below. Save the client as inspect.mjs. Keep the parser dependency in your own project environment, not the SanctionsKit content folder.

The utility accepts UTF-8 XML, limits input to 50 MiB, rejects DTD and entity declarations, and refuses obvious HTML or error document roots. Other encodings or larger approved feeds need a deliberately reviewed adapter rather than silently bypassing the checks.

npm install fast-xml-parser@5

Inspect the snapshot without activating it

Successful output has status parsed_not_approved. It records the bytes, SHA-256, root element, and supplied provenance, but intentionally does not produce potential_match or no_match. A file that parses successfully can still be stale, incomplete, or the wrong source schema.

// Node.js 22+. npm install fast-xml-parser@5
// Structural inspection only: not a sanctions matcher or an approved ingestion pipeline.
import { readFileSync, statSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { XMLParser, XMLValidator } from 'fast-xml-parser';
try {
  const [authority, filename] = process.argv.slice(2);
  if (!['eu', 'un'].includes(authority) || !filename) throw new Error('Usage: node inspect.mjs eu|un snapshot.xml');
  const source = new URL(process.env.SOURCE_URL ?? '');
  const timestamp = process.env.DOWNLOADED_AT ?? '';
  const allowed = authority === 'un'
    ? ['scsanctions.un.org', 'main.un.org'].includes(source.hostname)
    : (source.hostname === 'europa.eu' || source.hostname.endsWith('.europa.eu'));
  if (source.protocol !== 'https:' || source.username || source.password || !allowed) throw new Error('Use an approved official HTTPS source URL.');
  if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(timestamp) || !Number.isFinite(Date.parse(timestamp))) {
    throw new Error('Record the actual UTC download timestamp as DOWNLOADED_AT.');
  }
  if (statSync(filename).size > 50 * 1024 * 1024) throw new Error('Snapshot exceeds this example’s 50 MiB limit.');
  const raw = readFileSync(filename);
  if (raw.length === 0 || raw.length > 50 * 1024 * 1024 || raw.includes(0)) throw new Error('Empty, oversized, or unsupported-encoding snapshot.');
  const xml = new TextDecoder('utf-8', { fatal: true }).decode(raw);
  if (/<!DOCTYPE|<!ENTITY/i.test(xml)) throw new Error('DTD and entity declarations are not permitted.');
  if (XMLValidator.validate(xml) !== true) throw new Error('XML is not structurally well formed.');
  const parsed = new XMLParser({ ignoreAttributes: false, parseTagValue: false, parseAttributeValue: false }).parse(xml);
  const roots = Object.keys(parsed).filter((name) => !name.startsWith('?') && !name.startsWith('#'));
  if (roots.length !== 1 || /^(html|error)$/i.test(roots[0])) throw new Error('Unexpected document root.');
  console.log(JSON.stringify({
    authority, sourceUrl: source.href, downloadedAt: timestamp,
    sha256: createHash('sha256').update(raw).digest('hex'), byteLength: raw.length,
    rootElement: roots[0], status: 'parsed_not_approved',
    checksStillRequired: ['source authenticity', 'current source schema', 'record identifiers', 'completeness', 'normalization', 'matching tests'],
  }, null, 2));
} catch {
  console.error('Snapshot inspection failed. Keep the previous approved dataset active.');
  process.exitCode = 1;
}

Run and retain the inspection manifest

Set SOURCE_URL and DOWNLOADED_AT to the actual recorded values, then run the command below. Save the manifest with the original snapshot. Keep the previously approved dataset active if a new file fails inspection; a failed refresh must not publish an empty sanctions dataset.

node inspect.mjs eu snapshot.xml > snapshot-manifest.json

Define source-specific normalization before matching

For an EU adapter, review the current source structure for record identifiers, names and aliases, dates, addresses, regulation references, and source language. Document namespace handling and preserve the original reference to the legal measure. This tutorial deliberately does not invent a current EU XML schema or claim a complete production normalizer.

Before activating an ingestion adapter, validate its current schema, check required identifiers, compare counts and changes with the prior approved version, and test normalization on representative records. Keep the full original snapshot for reconstruction under your approved handling policy.

Build the next controls explicitly

Matching, review, freshness enforcement, and legal interpretation remain separate work. A successful parser cannot determine whether a customer is the listed identity or whether a proposed activity is authorized. See the data-freshness guide for the difference between downloaded, parsed, approved, and active data.

  • Test empty files, truncated XML, HTML responses, and forbidden declarations.
  • Test namespaces, Unicode names, partial dates, and repeatable snapshot hashes.
  • Document the expected schema before accepting a new source version.
  • Keep refresh failures visible and preserve the prior approved dataset.
  • Do not report unsupported API coverage as a successful screening.

Official references