Skip to content

Use case

Personal data, in and out

The same personal data crosses twice, and most deployments only check one direction. All 26 target languages are covered by one model trained on them at once, not on English with translation bolted on.

entities foundIBAN, PERSON, PHONEraw textredacted

The situation

A customer writes in to ask why a direct debit failed and pastes the whole line from their statement: name, IBAN, the reference, a phone number to call back on. That text goes to the model as context, and the answer quotes it back so the customer can confirm it is the right one.

The personal data has now crossed the boundary twice. It went out to wherever the model runs, and it came back into a transcript that support staff can read and that your logging pipeline is about to archive.

What breaks without a check

Without a check on the way in, the IBAN reaches the model provider and whatever they retain. Without a check on the way out, it lands in a transcript store that was scoped for support notes rather than for account numbers, and it is now in a backup you cannot selectively delete.

Which detectors apply

The policy is data, not code. A reviewer who does not write Python should be able to read this and say whether it is right.

border-code.yaml
detectors:  pii:    on_fail: redact         # the user still gets an answer    threshold: 0.5    options:      entities: [CARD, DATE, EMAIL, IBAN, NATIONAL_ID, PERSON, PHONE]  output_leakage:    on_fail: redact    threshold: 0.5

The same case in code

The same personal data crosses twice, so the check runs twice. The one mistake to avoid is passing the original text onward after a redaction, which is why every line after the first scan uses crossing.text.

support_turn.py
from flowx_border import scan_input, scan_output, load_policy policy = load_policy("border-code.yaml") # Inbound. The IBAN is redacted before the text leaves you.crossing = scan_input(ticket_text, policy)answer = your_model.complete(crossing.text)   # not ticket_text # Outbound. The same entities, the other direction.out = scan_output(answer, policy)archive(out.evidence)      # hashes and spans found, never the IBANreturn out.text

What the stamp holds

Which entity types were found, the span count, the model revision and weight hash that found them, and the hash of the original text. Never the IBAN. A data protection officer reading it can tell that a redaction happened, which detector did it, and which version of which model, without the record itself becoming a second copy of the thing you redacted.

Read by a data protection officer, or whoever answers the next subject access request.

What this does not catch

  • Coverage is even but not uniform: the weakest languages by span F1 are French and Latvian, published in the model card rather than dropped.
  • It finds entities, not intent. A customer describing their neighbour’s financial situation is personal data about a third party, and the detector has no way to know that.
  • Checksum validation is real for most national identifiers but not for all of them. Maltese and Azerbaijani have no public checksum scheme, so those are format-valid only.
  • A redaction is not an erasure. The original text still existed in memory, and if you logged it before the scan, this changes nothing.

The full detector set, or the per-language numbers.