Bài 3 — Output Guardrails: chặn rò rỉ PII, secret và nội dung độc hại
Dù input đã lọc, output vẫn có thể rò rỉ dữ liệu nhạy cảm hoặc chứa nội dung hại. Output guardrail là lớp kiểm tra CUỐI CÙNG trước khi phản hồi rời hệ thống — cả khi trả cho người dùng lẫn khi truyền sang tool/agent khác.
Vì sao cần lọc đầu ra?
Ví dụ thật: agent tra cứu đơn hàng, model vô tình đưa cả số thẻ và địa chỉ khách hàng vào câu trả lời hiển thị cho một khách khác. Hoặc agent debug và in nguyên OPENAI_API_KEY=sk-... ra chat. Input hoàn toàn "sạch", nhưng output làm rò rỉ.
Ba loại rủi ro đầu ra:
- Rò rỉ bí mật hệ thống: API key, connection string, system prompt, đường dẫn nội bộ.
- Rò rỉ PII: số điện thoại, CMND/CCCD, email, số thẻ của người khác.
- Nội dung độc hại / lệch chính sách: lời khuyên nguy hiểm, giọng điệu xúc phạm, hoặc trả lời ngoài phạm vi cho phép.
Ví dụ: bộ lọc output theo lớp
import re
SECRET_PATTERNS = {
"api_key": r"(sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16})",
"conn_str": r"(mongodb|postgres|mysql)://[^\s]+",
"jwt": r"eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+",
}
PII_PATTERNS = {
"vn_phone": r"\b(0|\+84)\d{9}\b",
"cccd": r"\b\d{12}\b",
"email": r"\b[\w.+-]+@[\w-]+\.[\w.-]+\b",
"card": r"\b\d{4}[ -]?\d{4}[ -]?\d{4}[ -]?\d{4}\b",
}
def redact(text: str):
findings = []
for name, pat in {**SECRET_PATTERNS, **PII_PATTERNS}.items():
if re.search(pat, text):
findings.append(name)
text = re.sub(pat, f"[REDACTED_{name.upper()}]", text)
return text, findings
def guard_output(text: str):
clean, findings = redact(text)
# Secret bị lộ = sự cố nghiêm trọng: chặn hẳn, không chỉ che
if any(f in SECRET_PATTERNS for f in findings):
return {"action": "block", "text": "Xin lỗi, phản hồi bị chặn vì lý do an toàn.",
"alert": findings}
return {"action": "allow", "text": clean, "redacted": findings}
Với nội dung độc hại/lệch chính sách, thêm một LLM judge nhỏ chấm output theo policy trước khi trả:
JUDGE = (
"Chấm câu trả lời theo policy. Trả JSON: "
'{"safe": true/false, "reason": "..."}. '
"Vi phạm nếu: lộ dữ liệu người khác, lời khuyên nguy hiểm, ngoài phạm vi hỗ trợ."
)
Các bước triển khai
- Định nghĩa "nhạy cảm" cho domain của bạn: liệt kê loại PII/secret cụ thể (ở VN có CCCD 12 số, số điện thoại 10 số...).
- Chạy regex redaction trước — nhanh, bắt các mẫu chuẩn.
- Phân biệt che vs chặn: PII của chính người đang chat có thể che một phần; secret hệ thống thì chặn hẳn và bắn cảnh báo.
- Thêm LLM judge cho các rủi ro ngữ nghĩa mà regex không bắt được (giọng điệu, lời khuyên hại).
- Áp guardrail cả trên output-tới-tool, không chỉ output-tới-người-dùng. Agent chuyền dữ liệu bẩn sang agent khác cũng nguy hiểm.
- Fail closed cho secret: nếu bộ lọc lỗi/timeout khi nghi có secret → mặc định chặn.
- Log sự cố kèm loại phát hiện để điều tra nguồn rò rỉ.
Checklist review đầu ra
- [ ] Có che/chặn PII và secret không?
- [ ] Secret lộ ra có tạo cảnh báo (alert) cho on-call không?
- [ ] Judge có kiểm tra output nằm trong phạm vi cho phép (scope) không?
- [ ] Guardrail có chạy trên cả đường agent→agent không?
- [ ] Khi filter lỗi, hệ thống fail closed hay fail open?
Sai lầm thường gặp
- Chỉ lọc output cho người dùng, quên output nội bộ. Dữ liệu rò rỉ giữa các bước/agent cũng là rò rỉ.
- Che PII nhưng vẫn để lộ trong log. Redact ở phản hồi mà log thô lưu nguyên văn thì PII vẫn rò qua hệ thống logging.
- Fail open khi filter lỗi. Nhiều hệ thống "cho qua khi lỗi" để tránh gián đoạn — với secret thì đây là thảm họa.
- Tin regex là đủ. Số thẻ viết bằng chữ, PII trong ngữ cảnh gián tiếp thì regex không bắt. Cần judge ngữ nghĩa.
- Quên rủi ro model in lại system prompt. Nếu để lộ system prompt, kẻ tấn công biết chính xác hàng rào của bạn để né.
Học xong bài này rồi?
Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu,
và học hết khóa thì có chứng chỉ.
Lưu tiến độ của tôi