Regex pattern hay dùng + cảnh báo ReDoS | Test trước khi ship

(Cập nhật: 19 tháng 7, 2026 ) regex biểu thức chính quy validation ReDoS JavaScript pattern
Kết luận

Regex sắc — tuyệt cho “lấy ticket id trong log”, tệ cho “validate email RFC”. Giữ pattern nhỏ có neo ^/$, bộ fixture good/bad, và cấm quantifier lồng trên input user (ReDoS). Email/URL chỉ là xấp xỉ; HTML/CSV → parser. Số điện thoại VN chỉ dùng pattern format 10 số di động (có ghi chú giới hạn), không phải “số thật”. Trước khi merge: thử trên Regex tester Kawa (chạy trình duyệt, không gửi server).

Developer Việt paste một “email regex hoàn hảo 200 ký tự” từ Stack Overflow vào form đăng ký. QA fail nửa danh sách: [email protected], TLD .info, hoặc corp mail có subdomain. Nửa còn lại thì production reject khách trả tiền. Bài này là cheat sheet pattern thật sự tái sử dụng + cảnh báo backtracking + quy trình test — không phải encyclopedia lý thuyết.

Ai nên đọc

  • Frontend / backend viết validation form, sanitize nhẹ, parse log
  • Fresher hay copy regex mà chưa có bộ chuỗi “phải fail”
  • Ai từng thấy tab Chrome treo khi RegExp.test trên chuỗi dài

Neo, flag, Unicode

/^\d{4}-\d{2}-\d{2}$/   // cả chuỗi giống ngày
/\berror\b/i            // biên từ, không phân biệt hoa thường
/\p{L}+/u               // chữ cái Unicode — cần flag u

Quên ^/$ → khớp substring ngoài ý muốn (12 khớp trong x12y). Flag m đổi nghĩa ^/$ thành đầu/cuối dòng. Làm việc với tiếng Việt / emoji: ưu tiên u; \p{L} bắt cả chữ có dấu khi engine hỗ trợ.


Bảng pattern hay dùng

Pattern thực chiến (JavaScript)
Mục đích Pattern / ý tưởng Lưu ý
Email (lỏng) /^[^\s@]+@[^\s@]+\.[^\s@]+$/ Không RFC. Cho phép +. Không chứng minh mailbox tồn tại.
URL trong text /https?:\/\/[^\s<>"']+/gi Detect thô. Validate thật: new URL(s).
Chỉ chữ số /^\d+$/ Tuổi, OTP đơn giản. Không phải số thập phân.
Alphanumeric ASCII /^[a-zA-Z0-9]+$/ Username ASCII. Không cover tiếng Việt.
Trim đầu/cuối /^\s+|\s+$/g Ưu tiên String.trim() khi có.
Gộp khoảng trắng /\s+/g' ' Normalize copy/paste từ Word.
Dòng trống /^\s*$/m Làm sạch log / Markdown.
UUID v1–v5 /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/i Format, không chứng minh phát hành đúng.
Jira / ticket /\b[A-Z][A-Z0-9]+-\d+\b/g PROJ-123 trong commit message.
ISO date thô /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?/g Extract log. Parse Date riêng.
Chữ Việt (xấp xỉ) /[A-Za-zÀ-ỹĐđ]+/u hoặc \p{L}+ Range Latin mở rộng — kiểm tra corpus thật.
SĐT di động VN (format) /^(0|\+84)(3|5|7|8|9)\d{8}$/ 10 số sau 2018. Không cover cố định đầy đủ.

🔍 Kiểm tra mẫu regex ngay tại đây

 

Chi tiết vài pattern hay bị hiểu sai

Email: giữ khiêm tốn

const EMAIL_ISH = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const good = [
  "[email protected]",
  "[email protected]",
  "[email protected]",
];
const bad = ["alice@", "@example.com", "alice example.com", "a@b"];

Không siết TLD {2,3} — sẽ reject .info, gTLD mới. Không cấm + — Gmail filter chết. Signup quan trọng: gửi mail xác nhận, không “regex hoàn hảo”.

URL: detect rồi giao cho WHATWG

const URL_ISH = /https?:\/\/[^\s<>"']+/gi;

function isHttpUrl(s) {
  try {
    const u = new URL(s);
    return u.protocol === "http:" || u.protocol === "https:";
  } catch {
    return false;
  }
}

Số điện thoại Việt Nam — chỉ khi đúng kỳ vọng

Sau chuyển đổi 2018, di động phổ biến 10 chữ số: 0 + đầu mạng 3|5|7|8|9 + 8 số. Quốc tế: +84 rồi bỏ 0.

// Format di động phổ biến — KHÔNG phải mọi số VN, KHÔNG verify nhà mạng
const VN_MOBILE = /^(0|\+84)(3|5|7|8|9)\d{8}$/;

// good (ví dụ giả): 0912345678, +84912345678
// bad: 012345678 (thiếu số), 0212345678 (đầu 2 — không khớp pattern di động này)

Giới hạn rõ trong PR: máy bàn (mã vùng), số cũ chưa chuẩn hóa, đầu số mới trong tương lai có thể lệch. OTP / thanh toán: tin nhà cung cấp SMS hoặc lib chuyên, không chỉ regex.

Log key=value và named group

const KV = /(\w+)=("(?:\\.|[^"])*"|\S+)/g;
const LINE = /^(?<level>INFO|WARN|ERROR)\s+(?<msg>.+)$/;
const m = "ERROR timeout db".match(LINE);
// m.groups.level === "ERROR"

Catastrophic backtracking (ReDoS) — đừng ship cái này

// NGUY HIỂM trên input user dài toàn chữ "a" rồi một ký tự khác
const bad = /^(a+)+$/;
const alsoBad = /^(a|a)*$/;
// Chuỗi "aaaaaaaaaaaaaaaaaaaa!" có thể làm engine thử tổ hợp mũ

Triệu chứng: tab đơ, API latency spike, worker CPU 100% khi attacker gửi payload crafted.

Cách tránh:

  1. Không lồng quantifier không giới hạn: (a+)+, (.*)*, (\w+)+$ trên raw user input
  2. Giới hạn độ dài chuỗi trước khi test / match
  3. Prefer possessive-style tư duy: fail sớm, pattern đơn giản, hoặc parser
  4. Review mọi new RegExp(userInput) — escape đúng; đừng nội suy raw
function safeTest(re, s, maxLen = 200) {
  if (typeof s !== "string" || s.length > maxLen) return false;
  return re.test(s);
}

Khi nào đặt regex xuống

Regex vs công cụ khác
Bài toán Nên dùng
HTML / XML DOMParser / parser chuyên
CSV / JSON Thư viện CSV / JSON.parse
Email thật sự tồn tại Confirmation email + DNS tùy nhu cầu
Routing path Router DSL (framework)
Validate form đơn giản Regex ngắn + mirror server
Extract id trong log Regex có neo / word boundary

Quy trình test trước khi ship

  1. Mở Regex tester — nhập pattern + cờ giống code (g/i/m)
  2. Dán cùng lúc chuỗi phải khớp và phải không khớp
  3. Copy vào module patterns.ts đặt tên rõ (EMAIL_SANITY, VN_MOBILE_FORMAT)
  4. Unit test:
function assertAll(re, good, bad) {
  for (const g of good) if (!re.test(g)) throw new Error(`should match: ${g}`);
  for (const b of bad) if (re.test(b)) throw new Error(`should not: ${b}`);
}

assertAll(EMAIL_ISH, good, bad);
  1. Không paste email / SĐT khách thật lên tool lạ — dùng fixture synthetic. Kawa xử lý trên trình duyệt phù hợp log nội bộ đã che PII.

Case: form đăng ký SaaS

Team dùng pattern TLD {2,3} → reject .museum và vài corp domain. Đổi sang EMAIL_ISH + verify mail; thêm test user+tag@. Thời gian debug support giảm vì hết “sao tôi không đăng ký được”.

Case: API gateway timeout

Endpoint validate “slug” bằng (a+)+$ “cho chắc”. Attacker gửi a × 30 + ! → event loop block. Fix: ^[a-z0-9-]{1,64}$ + max length middleware.


Checklist PR

  • Pattern có ^/$ đúng ý full-string?
  • Có fixture good bad trong test?
  • Đã thử trên /vi/tools/regex/ với đúng flag?
  • Không có quantifier lồng trên input user?
  • Email/URL/SĐT có comment giới hạn (không RFC / không verify số thật)?
  • Server dùng cùng quy tắc với client?

Kết

Steal pattern nhỏ cho id, ngày, trim, log. Thẳng thắn: email và URL regex chỉ là lọc hình dạng. ReDoS là lớp DoS thật — đừng lồng (a+)+ trên form. SĐT VN chỉ gắn khi team thống nhất “format di động 10 số”, kèm test. Bookmark bài này kèm tool regex: mỗi PR mới paste fixture 30 giây trước khi merge.