Skip to content

fix(country): block FATF-listed countries CD, KW, PG, BA and IQ - #4425

Open
joshuakrueger-dfx wants to merge 2 commits into
developfrom
fix/block-fatf-listed-countries
Open

fix(country): block FATF-listed countries CD, KW, PG, BA and IQ#4425
joshuakrueger-dfx wants to merge 2 commits into
developfrom
fix/block-fatf-listed-countries

Conversation

@joshuakrueger-dfx

Copy link
Copy Markdown
Collaborator

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/country on 2026-07-28, all five return
ibanAllowed=true today:

ISO-2 Country FATF status fatfEnable dfxEnable nationalityStepEnable
CD Democratic Republic of the Congo Increased monitoring true → false already false true → false
KW Kuwait Added February 2026 true → false already false true → false
PG Papua New Guinea Added February 2026 true → false already false true → false
BA Bosnia and Herzegovina Added June 2026 true → false true → false true → false
IQ Iraq Added June 2026 true → false already false true → false

Three columns per country, matching the established production pattern: every one of the 27
countries currently blocked has kycAllowed=false and nationalityAllowed=false as well — zero
exceptions in the live /v1/country response. bankEnable and checkoutEnable are deliberately
not touched: they cannot be reconstructed from the public DTO once dfxEnable/fatfEnable is
false, 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 second UPDATE.

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 separate
compliance decision and belongs in its own reviewed migration.

Consequence worth stating explicitly: after this change both Congos are blockedCD newly and
correctly, CG still over-blocked. The CG/CD mix-up described in the issue is therefore fixed on
the risk side only.

Panama (PA) is untouched. It is not FATF-listed; its kycAllowed=false comes from dfxEnable and
is 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; BA and IQ added, DZ and NA removed.
Union: 25 ISO-2 codes, recorded in src/shared/models/country/fatf-policy.ts together with the
source URLs and the update procedure. The FATF website is never queried at runtime.

Migration

migration/1785229100000-BlockFatfListedCountries20260619.js — one PL/pgSQL DO block, one
transaction.

  • Audit before snapshot, as CONTRIBUTING.md requires: previous and next values for all three
    flags, plus country id, symbol, effective date, source URL and migration id, are written to the
    append-only log table (system='Compliance', subsystem='CountryPolicy',
    category='FatfSnapshot20260619') before the UPDATE. Same pattern as
    1783808567932-CompleteRealUnitConfirmFlow and 1783780521095-PurgeRealUnitRegistrationSteps.
  • Idempotent: audit INSERT and UPDATE share the predicate
    symbol = ANY(targets) AND (fatfEnable OR dfxEnable OR nationalityStepEnable). A second run
    matches nothing, writes no duplicate audit row and leaves updated alone.
  • Fail closed: every target symbol must resolve to exactly one row; an audit/update count
    mismatch and a failed post-condition each RAISE EXCEPTION and roll the whole migration back.
  • Empty-table guard: on a fresh local database, migrations run before npm run seed. A count of
    zero is a clean no-op with a RAISE NOTICE instead of an identity-check abort, so the migration
    does not depend on seed order.
  • down() is an intentional no-op. Rolling this back would silently restore an outdated, laxer
    country list. A reversal needs its own compliance-reviewed forward migration.

Blast radius

fatfEnable drives ibanAllowed and cardAllowed in CountryDtoMapper, and both FATF branches in
AmlHelperService (VERIFIED_COUNTRY_NOT_ALLOWED, IBAN_COUNTRY_FATF_NOT_ALLOWED).

Flipping dfxEnable affects BA only — the other four already had it false. Beyond
kycAllowed/bankAllowed in the country DTO, two further consumers are affected and were traced:

  • transaction-helper.ts:1051QuoteError.COUNTRY_NOT_ALLOWED on the quote path
  • fiat-dto.mapper.ts:27BA drops out of a fiat's allowed IBAN-country list
  • kyc.service.ts:1786KycError.COUNTRY_NOT_ALLOWED
  • CountryService.getCountriesByKycType(DFX) no longer returns BA

All 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.csv previously initialised every country to fatfEnable=TRUE, so a fresh
local 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=FALSE for the three
call-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.js are untouched.

Tests

  • block-fatf-listed-countries.migration.spec.ts — 10 cases against real PostgreSQL
    (MIGRATION_TEST_PG, schema-isolated): all five blocked, BA as the only dfxEnable flip, one
    audit 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/CG and
    unrelated columns byte-stable, down() writes nothing.
  • fatf-policy.spec.ts — pins the 25-code snapshot and asserts every FATF-listed symbol is fully
    blocked in the seed. The reverse is deliberately not asserted, since DFX may block beyond FATF.
  • country-dto.mapper.spec.tsBA, 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
nationalityStepEnable from the UPDATE, removing BA from the target set, removing the
previous block from the audit JSON and removing the empty-table guard each turn exactly the
responsible 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.

@joshuakrueger-dfx
joshuakrueger-dfx marked this pull request as ready for review July 28, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant