Một diff chỉ thêm vài hàm nhỏ thường là lúc mình dễ chủ quan nhất. Test vẫn xanh, code nhìn gọn, nhưng một phép nối chuỗi vào SQL hay một đường dẫn thiếu kiểm tra containment vẫn có thể lọt qua. Vì vậy mình thử GitHub Copilot security review như một lượt rà nhanh trước commit, với điều kiện kết quả phải được đối chiếu bằng một bộ lỗi có đáp án trước.

Mình không dùng repository thật, secret thật hay đẩy fixture lên GitHub. Bài ghi lại hai lượt chạy trên local diff: trước và sau khi sửa. Kết quả hữu ích, nhưng không biến một lệnh AI thành máy quét toàn năng.

Vì sao mình muốn thêm một lượt review trước commit?

Test trả lời câu hỏi code có chạy theo hành vi mình đã mô tả hay không. Nó không tự biết hành vi đó an toàn. Trong fixture của mình, test đầu tiên còn cố tình chứng minh chuỗi ' OR 1=1 -- đã lọt vào câu SQL; ba test vẫn pass dù code đang có SQL injection, path traversal và MD5 cho reset token.

Đó là khoảng trống mình muốn lấp: sau test và lint, trước khi mở pull request, có một bước đọc đúng phần thay đổi đang làm. GitHub mô tả /security-review là lượt kiểm tra nhẹ cho “in-flight code changes”, trả finding có severity, confidence và gợi ý sửa. Hãng cũng nói rõ nó bổ sung cho code scanning, Dependabot và secret scanning chứ không thay thế các lớp đó.

Cách nhìn này giống kết luận mình từng rút ra khi thử Laravel Boost với code AI: test xanh là nền, context và review mới quyết định code có đáng tin hay không.

/security-review thực sự chạy ở đâu?

Có ba thứ dễ bị gọi lẫn nhau. Copilot app là ứng dụng làm việc theo session; public preview ngày 14/07/2026 cho phép gõ /security-review trong một session đang có thay đổi. Copilot CLI có cùng slash command từ ngày 10/06/2026. Còn Copilot code review là sản phẩm review code trên pull request, GitHub.com, IDE hoặc một số client được hỗ trợ.

Máy mình không cài Copilot app, nên mình không giả vờ chụp giao diện app. Mình tải Copilot CLI 1.0.79 vào thư mục tạm, bật chế độ experimental rồi chạy đúng /security-review trên repository local. GitHub nói tính năng trong app mang cùng cơ chế AI-driven scanning đã có ở CLI, nhưng trải nghiệm giao diện và policy có thể khác.

Changelog ghi preview của app dành cho Free, Pro, Business và Enterprise. Tài khoản tổ chức vẫn phụ thuộc policy Copilot CLI và quyền đăng nhập; mình không suy diễn mọi tài khoản sẽ thấy lệnh giống hệt.

Mình dựng fixture có đáp án trước

Môi trường là Windows 11 Pro build 26200, Node.js 22.22.3 và Copilot CLI 1.0.79. Mình commit baseline an toàn trước rồi thêm positive control vào working tree để lệnh chỉ nhìn phần diff cần kiểm tra.

Bốn lỗi mình chủ động cài vào diff

export const demoConfig = { apiToken: "NOT_A_REAL_TOKEN" };

export function buildUserQueryUnsafe(name) {
  return `SELECT id, name FROM users WHERE name = '${name}'`;
}

export function resolveAvatarUnsafe(baseDir, fileName) {
  return path.join(baseDir, fileName);
}

export function hashResetTokenUnsafe(token) {
  return crypto.createHash("md5").update(token).digest("hex");
}

Bốn case là nối chuỗi SQL, ghép đường dẫn không kiểm tra thoát khỏi thư mục gốc, MD5 cho token và một chuỗi trông giống credential. Chuỗi cuối được đặt tên rõ NOT_A_REAL_TOKEN để không thể nhầm với secret thật. Mình ghi kỳ vọng trước khi chạy nhằm tránh kiểu thấy output rồi mới sửa tiêu chí cho khớp.

Hai đoạn an toàn để kiểm tra báo nhầm

Negative control gồm một query dùng placeholder $1 với mảng values, và một hàm gọi path.resolve() rồi kiểm tra target phải nằm trong root. Nếu lệnh báo hai đoạn này là injection hoặc traversal thì đó là cảnh báo thừa đáng chú ý.

Ba unit test được chạy trước review. Cả ba đều pass trong khoảng 86 ms. Chỗ này nghe hơi ngược đời nhưng rất quan trọng: fixture chứng minh test xanh không đồng nghĩa với code an toàn; một test hoàn toàn có thể đang xác nhận đúng hành vi nguy hiểm mà người viết vô tình mong đợi.

Lần chạy đầu bắt được gì?

Mình yêu cầu lệnh chỉ review active local diff, không sửa file, và trả mỗi finding một lần. Sau khoảng 38 giây, Copilot nêu đúng ba lỗ hổng thật và tách chuỗi giả thành một ghi chú mức thấp. Hai negative control không bị báo.

CaseKỳ vọngKết quảSeverityConfidence
Nối chuỗi SQLPhải bắtBắt đúngHighHigh
Path traversalPhải bắtBắt đúngHighHigh
MD5 cho reset tokenPhải bắtBắt đúngMediumHigh
Chuỗi token giảCần nhận ra là giảNhắc như anti-pattern, nói rõ không phải secret thậtLowHigh
Query tham số hóaKhông báoKhông finding
Path containmentKhông báoKhông finding

Với mình, severity xếp thứ tự tác động giả định, còn confidence cho biết Copilot tự tin đến đâu với bằng chứng. Đây không phải điểm CVSS và không thay việc mở code kiểm chứng. Finding dẫn đúng hàm, nêu data flow và gợi ý dùng lại pattern an toàn trong cùng file.

Ma trận sáu case trước và sau khi sửa theo hai lượt security review
Ba lỗi thật biến mất sau patch; hai negative control không bị báo ở cả hai lượt. Chuỗi token giả chỉ được nhắc như placeholder.

Mình sửa rồi chạy lại cùng một diff

Mình không bấm chấp nhận patch hàng loạt. SQL được đổi sang query tham số hóa; đường dẫn được resolve rồi kiểm tra prefix với path.sep; MD5 đổi thành SHA-256 cho fingerprint; object config chỉ giữ tên biến môi trường DEMO_API_TOKEN, không giữ giá trị token.

export function buildUserQueryReviewed(name) {
  return {
    text: "SELECT id, name FROM users WHERE name = $1",
    values: [name],
  };
}

export function resolveAvatarReviewed(baseDir, fileName) {
  const root = path.resolve(baseDir);
  const target = path.resolve(root, fileName);
  if (target !== root && !target.startsWith(`${root}${path.sep}`)) {
    throw new Error("Path escapes avatar directory");
  }
  return target;
}

Ba test tiếp tục pass, lần này khoảng 90 ms. Lượt review thứ hai mất khoảng 40 giây và kết luận không còn lỗ hổng có thể khai thác trong diff. Nó vẫn đưa hai ghi chú Info: hai hàm “Reviewed” đang trùng logic với hàm “Safe” cũ, và chuỗi DEMO_API_TOKEN chỉ nên là tên biến môi trường, đừng biến nó thành nơi chứa secret thật. Hai ghi chú này hợp lý nhưng thuộc code quality hơn là blocker bảo mật.

Lệnh đã bỏ sót hoặc báo thừa gì?

Trong sáu case đã khai báo, mình không thấy false negative: ba lỗi thật được bắt, chuỗi giả được nhận diện đúng là placeholder, hai đoạn an toàn không bị gắn nhãn lỗ hổng. Mình cũng không tính nhiều câu diễn giải của cùng SQL injection thành nhiều finding.

Tuy vậy, kết quả này không phải benchmark tỷ lệ phát hiện. Fixture chỉ có JavaScript đơn giản, data flow ngắn và tên hàm khá rõ. Mình chưa thử framework phức tạp, code sinh động, nhiều package, permission theo nghiệp vụ hay luồng đi qua nhiều service. Copilot cũng được chính GitHub cảnh báo có thể bỏ sót vấn đề, tạo false positive hoặc đề xuất bản sửa chưa đúng; feedback vẫn phải được con người xác minh.

Một giới hạn nữa là lần này mình chạy CLI chứ không chạy app. Vì thế mình chỉ kết luận về output của command trên CLI 1.0.79, không kết luận rằng app sẽ có thời gian chạy, model hoặc cách hiển thị giống hệt.

Khác gì CodeQL, Dependabot và secret scanning?

Lớp kiểm traPhạm vi chínhThời điểm hợp lýKhông nên kỳ vọng
/security-reviewActive local changes, ưu tiên finding bảo mật có confidence caoTrước commit hoặc trước PRAudit toàn repository hay kết quả tất định
Copilot code reviewCode thay đổi trong PR/IDE cùng context repositoryKhi review pull requestThay reviewer chịu trách nhiệm cuối
CodeQL/code scanningPhân tích codebase bằng query và workflowPR, CI và lịch quétQuản lý dependency hoặc secret pattern
DependabotDependency graph, advisory và bản cập nhậtLiên tục trên repositoryBắt mọi lỗi logic ứng dụng
Secret scanningCredential pattern trong Git history và nội dung được hỗ trợPush protection và giám sát repositoryHiểu toàn bộ business logic

Nói gọn: slash command đọc nhanh cái mình đang làm; các scanner trong PR/CI nhìn rộng và lặp lại được; con người chịu trách nhiệm nối finding với kiến trúc và rủi ro thật. Nếu workflow còn có GitHub Actions, checklist về workflow đáng ngờ vẫn là một lớp riêng. Với runtime đang chờ bản vá, quy trình inventory, canary và rollback trong bài Node.js security release cũng không thể bị thay bằng một lượt AI review.

Workflow mình sẽ dùng và giới hạn cần nhớ

Sáu bước từ test và lint đến security review, PR scanners và human review
Mình đặt slash command giữa test/lint và PR; CodeQL, Dependabot, secret scanning cùng review con người vẫn là các lớp độc lập.

Thứ tự mình thấy hợp lý là: chạy test/lint, gọi /security-review, đọc bằng chứng, tự sửa, chạy lại, rồi mới mở PR để các scanner và reviewer tiếp tục. Nếu app hoặc CLI không khả dụng, policy tổ chức chặn, code chứa dữ liệu khách hàng hoặc repository không được phép gửi vào dịch vụ AI, mình sẽ bỏ bước này chứ không tìm cách lách policy.

Sau hai lượt, đánh giá của mình là lệnh đáng giữ như một “cổng nhắc bài” trước commit. Nó bắt đúng fixture nhỏ và việc chạy lại sau sửa tạo vòng phản hồi rất nhanh. Nhưng giá trị nằm ở chỗ finding dẫn mình đến đoạn code cần kiểm tra, không nằm ở nhãn High hay chữ AI. Bản sửa vẫn phải qua test, scanner phù hợp và review con người rồi mới yên tâm merge.

Nguồn kiểm chứng: GitHub Changelog về security review trong Copilot app ngày 14/07/2026; GitHub Changelog về command trong Copilot CLI ngày 10/06/2026; tài liệu cài đặt và policy Copilot CLI; tài liệu Copilot code review; tổng quan GitHub security features; và tài liệu secret scanning. Mình kiểm tra lại toàn bộ nguồn ngày 11/08/2026.