OGuardAI
GuidesCase Studies

Financial Compliance

How a bank uses AI for sanctions screening and AML checks without exposing customer financial identifiers to the language model

How a bank uses AI for sanctions screening and AML checks without exposing customer financial identifiers to the language model.


Note: This scenario relies on detecting sender/recipient names. Person and company detection requires the Python NER sidecar (GUARDAI_DETECTOR_URL, with detector.mode: both or advanced); in builtin-only mode names are not detected (IBAN, BIC, and national/tax IDs still are, via regex). Set detection.required_for: [person] in the policy to fail closed if the sidecar is down. See Detector Capabilities.

The Situation

A mid-size European bank processes 1,500 wire transfer requests per day. Each request contains the sender's name, IBAN, recipient IBAN, BIC/SWIFT code, and sometimes a national ID or tax number in the payment reference. The compliance team wants to use AI to pre-screen transfers against sanctions lists and flag suspicious patterns, reducing manual review from 45 minutes to under 5 minutes per case.

The regulator's position is clear: customer financial identifiers (IBANs, account numbers, national IDs) must not leave the bank's infrastructure to reach a third-party AI provider. The bank's information security team adds a further constraint: even internal AI models running in the bank's cloud must not receive raw IBANs in logs or training data.


The Solution

OGuardAI sits between the bank's transfer processing system and the compliance AI model. Financial identifiers are tokenized before reaching the model. The model receives enough context to make compliance decisions (country codes, transfer amounts, payment purpose) without seeing raw account numbers.


Step-by-Step: A Wire Transfer Review

A customer submits a wire transfer to a recipient in a high-risk jurisdiction.

Step 1: Transfer Request Arrives

Sender: Hans Mueller
Sender IBAN: DE89 3704 0044 0532 0130 00
Recipient: Dmitri Volkov
Recipient IBAN: RU02 0445 2080 0000 0640 2357
BIC: SABRRUMM
Amount: EUR 48,500.00
Reference: Invoice INV-2026-0891, Tax ID 7712345678
Purpose: Consulting services Q1 2026

Step 2: OGuardAI Transforms the Request

curl -X POST http://localhost:3000/v1/transform \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $GUARDAI_API_KEY" \
  -d '{
    "input": "Sender: Hans Mueller\nSender IBAN: DE89 3704 0044 0532 0130 00\nRecipient: Dmitri Volkov\nRecipient IBAN: RU02 0445 2080 0000 0640 2357\nBIC: SABRRUMM\nAmount: EUR 48,500.00\nReference: Invoice INV-2026-0891, Tax ID 7712345678\nPurpose: Consulting services Q1 2026",
    "policy": "financial-compliance"
  }'

Detected entities:

Original ValueEntity TypeTokenMetadata
Hans Muellerperson{{person:p_001:083e771529f3}}role: sender, country: DE
DE89 3704 0044 0532 0130 00iban{{iban:ib_001:2f9c04d1be67}}country: DE, bank: Commerzbank
Dmitri Volkovperson{{person:p_002:6a5b83e0d94c}}role: recipient, country: RU
RU02 0445 2080 0000 0640 2357iban{{iban:ib_002:91d3f7a2c05e}}country: RU
SABRRUMMbic*{{custom:bic:x_001:3e8f60b2d7a1}}country: RU
7712345678tax_id*{{custom:tax_id:x_002:b47a1c92e05d}}country: RU

Entity types marked with * are custom types defined via policy rules, not built-in. See the Extending Entities guide.

The safe text sent to the AI:

Sender: `{{person:p_001:083e771529f3}}`
Sender IBAN: `{{iban:ib_001:2f9c04d1be67}}`
Recipient: `{{person:p_002:6a5b83e0d94c}}`
Recipient IBAN: `{{iban:ib_002:91d3f7a2c05e}}`
BIC: `{{custom:bic:x_001:3e8f60b2d7a1}}`
Amount: EUR 48,500.00
Reference: Invoice INV-2026-0891, Tax ID `{{custom:tax_id:x_002:b47a1c92e05d}}`
Purpose: Consulting services Q1 2026

The invoice number and purpose pass through: no detector matches them. The transfer amount is a detected money entity, so it stays raw here only because the policy whitelists it (see the whitelist block below): compliance analysis needs the exact figure to apply reporting thresholds. Without the whitelist entry the amount would be tokenized like every other detected value. The entity metadata tells the model that the recipient is in Russia (high-risk jurisdiction) and the sender is in Germany.

Step 3: AI Performs Compliance Analysis

The model receives the tokenized transfer with entity context and generates:

COMPLIANCE SCREENING REPORT

Transfer: `{{person:p_001:083e771529f3}}` (DE) -> `{{person:p_002:6a5b83e0d94c}}` (RU)
Amount: EUR 48,500.00

Risk Flags:
1. HIGH - Recipient jurisdiction (RU) is on enhanced due diligence list
2. MEDIUM - Amount exceeds EUR 15,000 threshold for enhanced monitoring
3. LOW - Payment reference contains tax identifier from high-risk jurisdiction

Sanctions Check:
- `{{person:p_001:083e771529f3}}`: No match found in OFAC/EU sanctions lists
- `{{person:p_002:6a5b83e0d94c}}`: No exact match. Partial name similarity to sanctioned
  entity "D. Volkov" (SDN List ref. 29847): requires manual verification.

Recommendation: HOLD for manual review. Escalate to Level 2 compliance
officer for recipient identity verification before processing.

Required Actions:
- Verify recipient identity against passport/ID documentation
- Confirm business relationship (consulting agreement on file?)
- Document enhanced due diligence findings

The AI made a meaningful compliance decision using country metadata and risk patterns, without ever seeing the actual IBANs or tax ID.

Step 4: Restore for Different Channels

Internal compliance system, full restore:

curl -X POST http://localhost:3000/v1/rehydrate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $GUARDAI_API_KEY" \
  -d '{
    "output": "<compliance report with tokens>",
    "session_state": "<encrypted-blob>",
    "output_channel": "internal_summary"
  }'

The compliance officer sees the full report with all IBANs and names restored for the manual review step.

Audit log, masked restore:

curl -X POST http://localhost:3000/v1/rehydrate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $GUARDAI_API_KEY" \
  -d '{
    "output": "<compliance report with tokens>",
    "session_state": "<encrypted-blob>",
    "output_channel": "log_safe"
  }'

The audit log records the screening decision, risk flags, and recommendation, with IBANs masked to their first and last character (use a reveal_last_n restore strategy for a last-four display) and names replaced with anonymized identifiers.


Policy Configuration

name: financial-compliance
version: "1.0"
rules:
  - entity_type: "person"
    protection_level: 2
    action: "tokenize"
    restore_mode: "full"
  - entity_type: "iban"
    protection_level: 1
    action: "tokenize"
    restore_mode: "masked"
  - entity_type: "bic"
    protection_level: 2
    action: "tokenize"
    restore_mode: "masked"
  - entity_type: "tax_id"
    protection_level: 2
    action: "tokenize"
    restore_mode: "none"
  - entity_type: "ssn"
    protection_level: 1
    action: "redact"
  - entity_type: "passport"
    protection_level: 1
    action: "redact"

# bic and tax_id are bank-defined custom types. They must be declared in
# custom_patterns below before any rule or channel references them, otherwise the
# policy loader aborts startup with "references unknown entity type".
custom_patterns:
  - entity_type: "bic"
    pattern: '(?i)\b(?:bic|swift)[ \t:#.-]{0,4}([A-Z]{6}[A-Z0-9]{2}(?:[A-Z0-9]{3})?)\b'
    confidence: 0.9
    value_group: 1
    context_words: ["bic", "swift"]
  - entity_type: "tax_id"
    pattern: '(?i)\b(?:tax id|tax-id|steuer-?id)[ \t:#.-]{0,4}(\d{9,11})\b'
    confidence: 0.9
    value_group: 1
    context_words: ["tax", "steuer"]

defaults:
  protection_level: 2
  action: "tokenize"
  restore_mode: "masked"

# The transfer amount is a detected money entity. Whitelisting the literal value
# is the only path that keeps a detected value raw across the boundary (an
# authored action: allow is rejected at load), so the model can apply reporting
# thresholds to the exact figure.
whitelist:
  - "EUR 48,500.00"

metadata_policy:
  expose_gender: true
  expose_formality: true
  expose_language: true
  expose_role: true

channel_rules:
  internal_summary:
    person: full
    iban: full
    bic: full
    tax_id: full
  log_safe:
    person: abstract
    iban: masked
    bic: masked
    tax_id: none
  customer_email:
    person: full
    iban: masked
    bic: none
    tax_id: none

What OGuardAI Made Possible

Regulatory compliance without crippling AI. IBANs and tax identifiers never leave the bank's trust boundary. The AI receives country-level metadata, enough to flag jurisdiction risk and check sanctions patterns.

Separation of screening and identity. The AI evaluates risk patterns (high-risk country, amount thresholds, name similarity) without accessing the actual financial identifiers. This satisfies both the regulator and the bank's infosec policy.

Channel-specific access control. Compliance officers see everything for manual review. Audit logs keep masked versions for regulatory record-keeping. Customer notifications reveal only what the customer should see.

Redacted entity types. SSN and passport numbers in payment references are redacted entirely (replaced with [REDACTED:type] and purged): they never reach the model, not even as tokens. The policy enforces this at the engine level.