# Masking personal data before it reaches an LLM: under Vietnam's new law, swapping names for labels may not be enough

> A PII filter written in a hurry can still let personal data slip through. A mapping table kept for convenience can make it hard to argue that the whole system is de-identified.

Original: https://fdetimes.net/en/guides/pii-redaction-before-llm-vietnam-law/

In your first week at a financial-services client, you are asked to build an LLM feature that summarises customer-support tickets. The best model runs behind an API hosted abroad. At the review meeting, the head of legal asks: "Do the names, phone numbers and passwords that customers read out to call-centre agents leave Vietnam?"

The question comes from Decree 356, which took effect on 1 January 2026, replaced Decree 13/2023 and changed the rules.

If sensitive data that has not been thoroughly de-identified still passes through an API hosted abroad, the decree imposes stricter cross-border transfer procedures and requires encryption, anonymisation or equivalent security measures.

Many engineers will answer that the team has already masked everything. That answer holds only if you know exactly how far the masking goes, because the law draws a fairly sharp line between "masking to reduce exposure" and "de-identification". The gap between the two determines how many obligations the client still carries.

## How much masking before data stops being personal data?

Start with the definition. The Personal Data Protection Law 2025 defines personal data as information that identifies a specific person or helps to identify that person. The second half, "helps to identify", matters most: delete the name but keep "teacher, Hoi An branch, born 1990", and a reader can still trace it back to exactly one person.

The law also offers a clear exit. Data that has been de-identified is no longer treated as personal data, where de-identification means altering or deleting information to produce a new dataset that no longer points to anyone.

Decree 356 itself requires data to be de-identified before it is traded on a data exchange, so this is a measure the law formally recognises.

Engineers tend to trip up in two places. Under the law, encrypted data is still personal data, so encryption is not an exit.

And Article 14, clause 6 prohibits re-identifying de-identified data, which means genuine de-identification has to be one-way. Any pipeline that needs to attach real names back onto the LLM's answer is hard to place in the de-identification category from the outset.

| Approach | Example | Under the law |
|---|---|---|
| Encryption | Send the ciphertext of the phone number | Still personal data |
| Labels with a mapping table | `PERSON_001` ↔ "Nguyễn Thị Lan" | Reduces risk, but a route back to the real name remains, so it is hard to call de-identification |
| Genuine de-identification | Delete the name and any details that help infer identity, keep no route back | No longer personal data |

An analysis of GDPR by Wavect reaches a similar conclusion for the middle row: renaming someone `PERSON_001` reduces the risk of exposure, but the processing flow remains within the scope of GDPR.

Treat this as a signal to be careful, not as a legal conclusion for Vietnam. Which row applies to the client's system is for their lawyers to decide; your job is to help them see clearly which row the system sits in.

**Key point:** If a reader can still guess who it is after the name is masked, the data is still personal data.

## A ticket through the filter

Consider a hypothetical ticket:

> Chị Nguyễn Thị Lan, SĐT 0912345678, giáo viên ở Hội An, báo không đăng nhập được tài khoản định danh điện tử, mật khẩu cũ là Lan@1990.

(Ms Nguyễn Thị Lan, phone 0912345678, a teacher in Hoi An, reports she cannot log in to her electronic identification account; her old password is Lan@1990.)

The ticket contains four kinds of information that need handling. The name and phone number are the most obvious. The password for an electronic identification account is classed by Decree 356 as sensitive data, so catching it is the top priority. The phrase "a teacher in Hoi An" does not directly identify anyone but can help identify her.

To summarise the ticket, the LLM only needs to know the type of incident. The desired output is therefore just this:

> [NGƯỜI_DÙNG], SĐT [ĐIỆN_THOẠI], [NGHỀ_NGHIỆP] ở [ĐỊA_ĐIỂM], báo không đăng nhập được tài khoản định danh điện tử, mật khẩu cũ là [BÍ_MẬT].

(The labels stand for user, phone, occupation, location and secret.)

There are no sequence numbers and no mapping table. The internal system still routes the result back to the right person via the ticket ID, but the LLM never sees that ID.

To be blunt, though, the ticket ID is also a link back to the user. This approach sharply reduces the amount of information leaving the system; it does not turn the whole system into genuine de-identification, and you should describe it to the client in exactly those terms.

Below is a sketch of the skeleton. The regexes are examples only; adapt them to the client's actual data formats:

```python
import re

RULES = [
    ("SECRET", r"(password|passcode|pwd)\s*(old|new)?\s*(is|:)\s*\S+"),
    ("PHONE", r"\b0\d{9}\b"),
    ("EMAIL", r"[\w.+-]+@[\w-]+\.[\w.]+"),
]

class PIIFilterError(Exception):
    pass

def redact(text: str, ner) -> str:
    for label, pattern in RULES:
        text = re.sub(pattern, f"[{label}]", text, flags=re.IGNORECASE)
    # Person names, places, occupations: need an NER model (e.g. Presidio + a Vietnamese model)
    for span in sorted(ner(text), key=lambda s: -s.start):
        text = text[:span.start] + f"[{span.label}]" + text[span.end:]
    return text

def build_prompt(ticket: str, ner) -> str:
    try:
        clean = redact(ticket, ner)
    except Exception as e:
        # Fail closed: if the filter breaks, send NOTHING outside
        raise PIIFilterError("PII filter unavailable") from e
    return f"Summarize the following incident:\n{clean}"
```

(The Vietnamese comments note that names, places and occupations need an NER model, such as Presidio with a Vietnamese model, and that if the filter breaks, nothing is sent out. The prompt reads "Summarise the following incident".)

The `except` block is the part that matters most. Wavect advises that a filter must never silently "fail open" just because the detector is slow or down. In practice, the damage usually starts with a line such as "if it times out, just send the original so users aren't affected".

## However good the detector, measure it yourself

Microsoft Presidio is a sensible open-source library for the detection layer, because it can identify and anonymise private entities in both text and images.

Even so, Presidio's own introduction page states that there is no guarantee the library will find all sensitive information. With Vietnamese names written without diacritics, abbreviated or misspelt, you cannot predict the miss rate in advance.

So before going to production, Wavect recommends measuring on the client's own data: the miss rate, task quality, latency, and how the filter handles text that mixes Vietnamese and English.

Take a hypothetical figure for illustration. You hand-label 200 tickets and count 412 entities that need masking; the filter catches 375. Recall is 375/412, roughly 91%, which means 37 pieces of personal data still go out.

A 91% figure sounds high, but with thousands of tickets a day, a great many pieces of data leak every day. Break the results down by entity type, because missing a password is far more serious than missing the name of a district. Every time you add a rule or change the model, re-run this test set as you would a regression test.

## Five steps at the client

Step one: sit down with the client to classify the data. You need to know which fields fall into the sensitive category under Decree 356 and what the LLM task actually needs. Most summarisation or classification tasks do not need to know anyone's name.

Step two: build a layered filter. Regex handles structured items; NER handles names, places and occupations. Step three: wrap the LLM call in a fail-closed mechanism and log every blocked request.

Step four: measure recall on labelled data, broken down by entity type. Step five: write a one-page description of the data flow, stating which row of the table above the system sits in and which links back to individuals remain, then hand it to the legal team to decide.

Masking the data does not end your obligations. The law lists encryption, decryption and de-identification as personal data processing activities, so the masking step running on the client's infrastructure must comply like any other processing step.

## Common mistakes

The most common mistake is masking direct identifiers while ignoring details that help establish identity. Next comes treating encryption or reversible tokens as de-identification.

The third goes hand in hand with the second: keeping a placeholder ↔ real name mapping table and then telling the client the data has been de-identified.

Read together, the "helps to identify" definition, the rule that encrypted data remains personal data and the ban on re-identification make it hard to classify a set-up that keeps a route back to real names as de-identification.

The final call belongs to the client's lawyers, but they need to know the mapping table exists.

Two more mistakes remain. One is testing the filter only with the sample English sentences from the documentation. The other is assuming the filter will always run.

## How to put this skill on your CV

Do not just write "used Presidio". Write that you measured PII-masking recall on Vietnamese data and designed a fail-closed mechanism, with numbers if you have them.

When reading a job description for an FDE role, phrases such as "data residency", "PII" or "compliance review" signal that they need exactly this skill.

## This week's exercise

Take the sample ticket above, write 20 variants (without diacritics, abbreviated, phone numbers with dots, a password following the word "pass") and run them through your filter. Count how many entities slip through. That number is what you should bring to the meeting with the legal team.

**Try this week:**

- Take 50 real records you are permitted to use, hand-label every piece of identifying information, then run your current filter and count the entities it misses
- Find every place in the code that calls an LLM API and check whether the request is still sent when the PII filter times out
- List everywhere a placeholder ↔ real name mapping table is stored, along with every ID that links back to a user, and bring that list to a meeting with the client's legal team

## Sources

- [Điều 2 Luật Bảo vệ dữ liệu cá nhân 2025](https://hethongphapluat.com/luat-bao-ve-du-lieu-ca-nhan-2025/dieu-2)

- [Mã hóa dữ liệu cá nhân và khử nhận dạng dữ liệu cá nhân là gì?](https://luatvietnam.vn/linh-vuc-khac/ma-hoa-du-lieu-ca-nhan-va-khu-nhan-dang-du-lieu-ca-nhan-la-gi-883-106460-article.html)

- [EY Vietnam – Nghị định số 356/2025/NĐ-CP quy định chi tiết một số điều và biện pháp thi hành Luật Bảo vệ dữ liệu cá nhân](https://www.ey.com/vi_vn/technical/tax/tax-and-law-updates/nghi-dinh-so-356-2025-nd-cp-quy-dinh-chi-tiet-mot-so-dieu-va-bien-phap-thi-hanh-luat-bao-ve-du-lieu-ca-nhan)

- [Presidio – Data Protection and De-identification SDK](https://presidio.dataprivacystack.org/)

- [PII Redaction Before LLM Prompts: Presidio, Privacy Filter, and a Production Pipeline](https://wavect.io/blog/pii-redaction-before-llm-prompts/)
