---
title: "How we reverse-engineered OFAC's sanctions screening algorithm"
summary: "Our new name matcher emulates the public OFAC Sanctions List Search tool, with mismatch qualifiers layered on top."
url: "https://www.opensanctions.org/articles/2026-07-01-ofac-matcher/"
date_created: "2026-07-01T09:13:00.000Z"
date_updated: "2026-07-01T09:21:12.958Z"
tags: ["OFAC","Name matcher"]
image: "https://www.opensanctions.org/assets/7e1d14ae-abc1-4e48-b311-58a97f2647c2/"
---

<img class="img-fluid" src="/assets/82dd8a88-279f-43df-b241-b69a169c0e30">
<p class="img-caption">
</p>

The US Treasury's Office of Foreign Assets Control (OFAC) publishes the core sanctions lists everyone screens against: the Specially Designated Nationals and Blocked Persons List ([SDN](https://www.opensanctions.org/datasets/us_ofac_sdn/)), and the Consolidated Non-SDN List ([Cons](https://www.opensanctions.org/datasets/us_ofac_cons/)). 

The compliance world largely orbits around these two lists. Despite this, guidance on how regulated firms should screen against them is limited. OFAC operates a [public search interface](https://sanctionssearch.ofac.treas.gov/) that produces score results for name matches. It also published a high-level description of how these scores are calculated, listed as [OFAC FAQ #249](https://ofac.treasury.gov/faqs/249).

Over the years, that FAQ item has acquired strange regulatory gravity,  producing an industry obsession with phonetic matching and a specific string comparison algorithm, Jaro-Winkler. But this was simply because the FAQ item was all anyone really had to go on. 

## A clue in the scraping logs 

Until now, OpenSanctions has been offering two screening matchers, called `name-based` and `name-qualified`, that aim to emulate the OFAC score. However, using modern Python versions of the Jaro-Winkler algorithm just never produced quite the right scores, and we couldn’t figure out why. 

So, we decided to investigate. We used an LLM agent to sample a few hundred name-match scores from OFAC's public search tool and attempted to reproduce them with our own implementation. The agent got close, up to 90% equivalence – but not quite close enough for a compliance product. 

Eventually, we found an extra clue in the agent’s own scraping logs. Claude had used workarounds to crawl ancient Microsoft ASP.NET sites. The score-filter slider bar uses AjaxControlToolkit, a web script that was widely used between 2007 and 2012. So: if you were building an ASP.NET site in those years, how would you implement Jaro-Winkler? Probably using SimMetrics.NET, a C# port of SimMetrics-Java.

Turns out SimMetrics.NET skipped a step in the algorithm used in the original Jaro-Winkler paper. An extra threshold factor would apply a bonus for matching name prefixes only when the base Jaro score is already greater than 0.7. Modern string comparison libraries (rapidfuzz, jellyfish) all apply this threshold. With a custom implementation of Jaro-Winkler that always applies the scoring bonus, the numbers suddenly added up, and we'd got ourselves a 96% accurate OFAC screening emulator! 

## From weekend hack to production

What started as a problem we attempted to solve one weekend has become a product – the emulator will ship in the OpenSanctions matching API as a new algorithm, [ofac](/matcher/#ofac). It pairs the parity-tuned name score with a set of mismatch qualifiers — penalties applied when a candidate's country, date of birth, gender, or registration/tax identifier contradict the query — so you get OFAC-comparable name scoring with the obvious disqualifiers layered on top.

This also enables us clean up some history. We have two older algorithms, `name-based` (phonetic plus Jaro-Winkler, loosely based on FAQ #249) and `name-qualified` (the same, with mismatch penalties), neither of which actually reaches OFAC parity. In a couple of months, [both will start redirecting to ofac](/changelog/46/). If you're calling either of these today, you don't need to do anything — you'll simply start getting scores that track the real OFAC tool more closely. And if you want to opt in early, you can request `ofac` by name right away.
