Why FX rate integrity is a financial security problem
The server must decide the rate used for settlement. A client can choose an amount and currency pair. It cannot choose the rate, fee, spread, source, expiry time, or final payout.
Build a quote record before testing. It needs a random ID, user and tenant ID, pair, direction, source amount, destination amount, rate, fees, expiry time, status, and version. The conversion must copy these values from the record.
1. Rate parameter accepted in the conversion request body
Add, change, and remove the rate in every conversion request. Change the fee, spread, source, and destination amount too.
Pass: the server ignores client pricing and uses the stored quote for the same user and amount.
// VULNERABLE: rate accepted from client
POST /api/convert
{
"from_currency": "NGN",
"to_currency": "USD",
"amount": 500000,
"rate": 500 // attacker submits a favourable rate instead of the quoted 1600
}
// CORRECT: rate derived server-side from the quote session
POST /api/convert
{
"from_currency": "NGN",
"to_currency": "USD",
"amount": 500000,
"quote_id": "qte_7f3a9c" // server looks up the locked rate from this session
} 2. Rate lock expiry bypass
Execute the quote before, at, and after its expiry time. Use server time and include queue delay.
Pass: the conversion checks expiry inside the same database transaction that consumes the quote.
3. Quote session reuse across multiple transactions
Send the same quote twice and send parallel copies. Repeat after a timeout and client retry.
Pass: one atomic update moves the quote from active to consumed. Later requests cannot create another conversion.
4. Parallel quote session arbitrage
Create several quotes for the same user, pair, and amount. Execute an older quote after a newer one exists.
Pass: the written product rule controls active quotes. The server enforces the same rule during execution and records which quote won.
Evidence and pass rules
- Rate source: Change and remove every client rate field. Pass when the ledger uses the stored server quote.
- Expiry: execute before, at, and after expiry using server time. Pass when the boundary is exact and expired quotes create no ledger entry.
- Reuse: send two parallel conversions with one quote. Pass when one conversion consumes it and the other fails.
- Amount binding: change the source amount, destination amount, direction, pair, and fee. Pass when every mismatch fails.
- Settlement: compare quote, conversion, ledger, provider request, and reconciliation record. Pass when they share one immutable rate and quote ID.
Keep sanitized request pairs, the quote row, database transaction log, ledger entries, provider response, and reconciliation result. Use test funds and approved test limits.
Operating an FX conversion, dollar savings, or remittance product in Nigeria? Book a security test of your rate engine and conversion API.
Book an FX Rate Security AuditFrequently asked questions
What is the FX rate locking attack?
A rate lock fails when the server accepts a client rate, an expired quote, a reused quote, or an amount outside the quote. The server must store the rate and terms, then consume the quote once in the same transaction that creates the conversion.
How should an FX platform measure the loss from a bad quote?
Compare the executed amount with the amount the approved server quote should have produced. Include fees, spread, settlement cost, reversals, and every conversion that reused the same quote. Use the platform’s own ledger and rate source.
What compliance records should an FX platform keep?
Keep the quote, rate source, fees, expiry time, customer approval, conversion, ledger entries, provider request, settlement, reversal, and reconciliation result. The compliance team should map these records to the current rules and licence conditions that apply to the product.
Related reading
Blog: Remittance platform penetration testing · Race conditions in Nigerian fintech · Business logic flaws in payment platforms
Services: Penetration testing · API security