fix(country): block FATF-listed countries CD, KW, PG, BA and IQ - #4425
Open
joshuakrueger-dfx wants to merge 2 commits into
Open
fix(country): block FATF-listed countries CD, KW, PG, BA and IQ#4425joshuakrueger-dfx wants to merge 2 commits into
joshuakrueger-dfx wants to merge 2 commits into
Conversation
joshuakrueger-dfx
marked this pull request as ready for review
July 28, 2026 10:12
joshuakrueger-dfx
requested review from
TaprootFreak and
davidleomay
as code owners
July 28, 2026 10:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes part of #4306.
What this does
Blocks the five countries that are on the current FATF lists but that production still allows.
Measured against
GET https://api.dfx.swiss/v1/countryon 2026-07-28, all five returnibanAllowed=truetoday:fatfEnabledfxEnablenationalityStepEnableCDKWPGBAIQThree columns per country, matching the established production pattern: every one of the 27
countries currently blocked has
kycAllowed=falseandnationalityAllowed=falseas well — zeroexceptions in the live
/v1/countryresponse.bankEnableandcheckoutEnableare deliberatelynot touched: they cannot be reconstructed from the public DTO once
dfxEnable/fatfEnableisfalse, so there is no evidence for a target value, and the derived DTO fields go false anyway.
Scope
Only these five rows change in production. The migration's target set is a single
ARRAY['BA', 'CD', 'IQ', 'KW', 'PG']; there is no other symbol literal, no secondUPDATE.Countries the API blocks although they are no longer on the FATF lists (
BF,MZ,NG,ZA,DZ,NA,CG) are not unblocked here. Over-blocking is fail-safe; unblocking is a separatecompliance decision and belongs in its own reviewed migration.
Consequence worth stating explicitly: after this change both Congos are blocked —
CDnewly andcorrectly,
CGstill over-blocked. TheCG/CDmix-up described in the issue is therefore fixed onthe risk side only.
Panama (
PA) is untouched. It is not FATF-listed; itskycAllowed=falsecomes fromdfxEnableandis out of scope.
FATF snapshot
Effective date 2026-06-19 (June 2026 plenary, Paris). Call for action:
IR,KP,MM(unchanged). Increased monitoring: 22 jurisdictions;
BAandIQadded,DZandNAremoved.Union: 25 ISO-2 codes, recorded in
src/shared/models/country/fatf-policy.tstogether with thesource URLs and the update procedure. The FATF website is never queried at runtime.
Migration
migration/1785229100000-BlockFatfListedCountries20260619.js— one PL/pgSQLDOblock, onetransaction.
CONTRIBUTING.mdrequires: previous and next values for all threeflags, plus country id, symbol, effective date, source URL and migration id, are written to the
append-only
logtable (system='Compliance',subsystem='CountryPolicy',category='FatfSnapshot20260619') before theUPDATE. Same pattern as1783808567932-CompleteRealUnitConfirmFlowand1783780521095-PurgeRealUnitRegistrationSteps.INSERTandUPDATEshare the predicatesymbol = ANY(targets) AND (fatfEnable OR dfxEnable OR nationalityStepEnable). A second runmatches nothing, writes no duplicate audit row and leaves
updatedalone.mismatch and a failed post-condition each
RAISE EXCEPTIONand roll the whole migration back.npm run seed. A count ofzero is a clean no-op with a
RAISE NOTICEinstead of an identity-check abort, so the migrationdoes not depend on seed order.
down()is an intentional no-op. Rolling this back would silently restore an outdated, laxercountry list. A reversal needs its own compliance-reviewed forward migration.
Blast radius
fatfEnabledrivesibanAllowedandcardAllowedinCountryDtoMapper, and both FATF branches inAmlHelperService(VERIFIED_COUNTRY_NOT_ALLOWED,IBAN_COUNTRY_FATF_NOT_ALLOWED).Flipping
dfxEnableaffectsBAonly — the other four already had it false. BeyondkycAllowed/bankAllowedin the country DTO, two further consumers are affected and were traced:transaction-helper.ts:1051→QuoteError.COUNTRY_NOT_ALLOWEDon the quote pathfiat-dto.mapper.ts:27→BAdrops out of a fiat's allowed IBAN-country listkyc.service.ts:1786→KycError.COUNTRY_NOT_ALLOWEDCountryService.getCountriesByKycType(DFX)no longer returnsBAAll of this is the intended blocking effect. The country repository caches for 5 minutes, and the
migration runs at boot, so no stale-cache step is needed.
Seed
migration/seed/country.csvpreviously initialised every country tofatfEnable=TRUE, so a freshlocal database could not reproduce production behaviour at all. It now carries the same blocked set
as production: the 27 already blocked plus these 5, with
ipEnable=FALSEfor the threecall-for-action jurisdictions only. Verified against the live endpoint: the seed blocks nothing that
is neither already blocked in production nor a target of this PR. 251 lines, ids, symbols and row
order unchanged; the local-only safety guards in
seed.jsare untouched.Tests
block-fatf-listed-countries.migration.spec.ts— 10 cases against real PostgreSQL(
MIGRATION_TEST_PG, schema-isolated): all five blocked,BAas the onlydfxEnableflip, oneaudit row per changed country with correct previous/next, idempotency, already-blocked rows
skipped, missing target row aborts transactionally, empty table is a no-op,
PA/CH/CGandunrelated columns byte-stable,
down()writes nothing.fatf-policy.spec.ts— pins the 25-code snapshot and asserts every FATF-listed symbol is fullyblocked in the seed. The reverse is deliberately not asserted, since DFX may block beyond FATF.
country-dto.mapper.spec.ts—BA,CD,CG,PA, the three call-for-action jurisdictions,and both derived conjunctions.
aml-helper.service.spec.ts— both FATF AML errors and the negative case.Full suite: 5561 passing, 0 failures. Counter-checks were run: reverting the seed, dropping
nationalityStepEnablefrom theUPDATE, removingBAfrom the target set, removing thepreviousblock from the audit JSON and removing the empty-table guard each turn exactly theresponsible tests red.
Open
Compliance approval for the policy questions raised in #4306 is not part of this PR and still
needs to be recorded. The pre- and post-deploy verification described in the issue can only be
done against real environments after deployment.