How we reverse-engineered OFAC's sanctions screening algorithm

Our new name matcher emulates the public OFAC Sanctions List Search tool, with mismatch qualifiers layered on top.

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), and the Consolidated Non-SDN List (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 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.

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. 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. 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.

Like what we're writing about? Keep the conversation going! You can follow us on LinkedIn, subscribe to our E-Mail newsletter or join the discussion forum to bring in your own ideas and questions. Or, check out the project documentation to learn more about OpenSanctions.

Published:

This article is part of OpenSanctions, the open database of sanctions targets and persons of interest.

How we reverse-engineered OFAC's sanctions screening algorithm - OpenSanctions