← Lập trình với Claude Code

Bài 15 · Nâng cao · 26 phút· Cập nhật 11/06/2026

Chuẩn nghề: Bảo mật & dữ liệu

Biên soạn bởi Nguyễn Anh Tuấn

Biến OWASP (Top 10/ASVS) thành subagent review bảo mật + luật CLAUDE.md; toàn vẹn dữ liệu: transaction, idempotency, optimistic vs pessimistic lock.

Claude rất giỏi cho ra code chạy được. Nhưng senior hỏi tiếp: an toàn khi có kẻ xấu chưa?đúng khi nhiều người dùng cùng lúc chưa? Mặc định AI tối ưu cho “xong việc”, nên BẠN phải mã hoá chuẩn nghề để Claude tự áp dụng.

  • CLAUDE.md = LUẬT luôn đúng (vd “truy vấn phải tham số hoá”) - hướng dẫn mặc định.
  • Skill/Subagent = quy trình REVIEW chuyên sâu (vd review bảo mật theo OWASP).
  • Hook = CHẶN cứng (vd không cho commit lộ bí mật).
  • Ba lớp này biến “bạn nhớ nhắc” thành “Claude tự làm đúng”.

Đây là mini-track “Chuẩn nghề”

Ba bài kế tiếp dạy cách đưa kỹ thuật senior vào Claude Code: bài này (bảo mật & dữ liệu), rồi chất lượng & khả năng truy cập, rồi kiến trúc & mẫu chịu lỗi. Phần lý thuyết SÂU của từng mảng có ở các khoá riêng (Clean Code, SOLID, Clean Architecture); ở đây ta tập trung mã hoá chúng thành công cụ cho Claude Code.

Cách hiệu quả nhất: một subagent review bảo mật chỉ-đọc, mang sẵn checklist OWASP trong system prompt. Claude giao việc cho nó trước khi bạn merge:

.claude/agents/security-reviewer.md

---
name: security-reviewer
description: Review code về lỗ hổng bảo mật theo OWASP. Dùng trước khi merge, hoặc khi đụng input người dùng, truy vấn DB, xác thực/phân quyền.
tools: Read, Grep, Glob
model: sonnet
---

Bạn là kỹ sư bảo mật. CHỈ đọc, không sửa. Soi theo OWASP Top 10:
- A01 Broken Access Control: kiểm quyền Ở SERVER cho mọi thao tác.
- A02 Crypto: không tự chế mã hoá; băm mật khẩu bằng bcrypt/argon2.
- A03 Injection: truy vấn THAM SỐ HOÁ; không nối chuỗi vào SQL/shell.
- A05 Misconfig & bí mật: không hardcode khoá; đọc từ biến môi trường.
- A07 Auth: chống brute force, session an toàn.
Trả lời tiếng Việt, nêu file:dòng, xếp theo mức nghiêm trọng, kèm cách vá.
claude - ~/cua-hang-x

Review @src/api/users.ts về bảo mật trước khi tôi merge.

⤷ giao cho subagent: security-reviewer (chỉ-đọc, OWASP)

✗ A03 Injection - users.ts:23: nối thẳng input vào câu SQL

✗ A01 Access Control - users.ts:41: thiếu kiểm tra quyền sở hữu bản ghi

Reviewer thấy 2 vấn đề nghiêm trọng. Sửa: dùng truy vấn tham số hoá (?, $1) và kiểm user.id == record.ownerId ở server. Tôi vá rồi chạy lại test nhé?

  • Reviewer CHỈ-ĐỌC (tools: Read/Grep/Glob) - nêu vấn đề, không tự sửa.
  • Checklist OWASP nằm trong system prompt → mỗi lần review đều bám đúng tiêu chí.
  • Commit file này vào repo để cả nhóm có cùng một “người gác cổng” bảo mật.

Có sẵn & chuyên sâu

Claude Code có lệnh review bảo mật dựng sẵn (vd /security-review) và plugin cộng đồng. Muốn sâu hơn nữa, tham chiếu OWASP Top 10ASVS - và đưa các tiêu chí hợp dự án bạn vào system prompt.

Rủi ro kinh điển: khoá API/mật khẩu lọt vào code hoặc commit. Ghi luật vào CLAUDE.md và chặn cứng bằng quyền/hook:

CLAUDE.md - mục bảo mật

## Bảo mật (bắt buộc)
- KHÔNG hardcode bí mật. Đọc khoá/mật khẩu từ biến môi trường.
- Mọi truy vấn DB phải THAM SỐ HOÁ (không nối chuỗi input).
- Kiểm tra quyền Ở SERVER cho mọi endpoint sửa/đọc dữ liệu nhạy cảm.
- Băm mật khẩu bằng bcrypt/argon2; không lưu mật khẩu thô.
- Thông báo lỗi không lộ chi tiết nội bộ (stack trace, câu SQL).
  • Chặn đọc bí mật: deny Read(./.env), Read(./secrets/**) trong settings.json.
  • Hook PreToolUse có thể chặn commit chứa khoá (tự dò mẫu khoá) - bài Hooks.
  • Bí mật để trong biến môi trường / trình quản lý bí mật, KHÔNG trong repo.

Dữ liệu “đúng” không chỉ là ghi được, mà là không bao giờ rơi vào trạng thái sai. Ghi các luật này để Claude viết code đúng đắn:

CLAUDE.md - mục toàn vẹn dữ liệu

## Toàn vẹn dữ liệu (bắt buộc)
- Thao tác nhiều bước phải nằm trong MỘT transaction (toàn bộ hoặc không gì).
- Đặt ràng buộc Ở DB: NOT NULL, UNIQUE, FOREIGN KEY, CHECK - đừng chỉ tin tầng app.
- Thao tác có thể bị LẶP (tạo đơn, thanh toán) phải IDEMPOTENT:
  nhận idempotency key, lưu kết quả theo key, request trùng key → trả kết quả cũ.
- Validate đầu vào trước khi ghi; từ chối dữ liệu không hợp lệ rõ ràng.
  • Transaction = nguyên tử: chuyển tiền phải trừ A & cộng B cùng lúc, hoặc huỷ cả hai.
  • Ràng buộc ở DB là “lưới cuối” - chặn dữ liệu rác kể cả khi app có bug.
  • Idempotency cứu bạn khỏi “trừ tiền 2 lần” khi mạng gửi lại request.

Thử trực tiếp - chọn chiến lược khoá và idempotency key để thấy kết quả thay đổi:

Hai kịch bản thực tế về toàn vẹn dữ liệu - chọn chiến lược để thấy kết quả thay đổi:

Demo 1 - Tồn kho: 2 người mua cùng lúc

Tồn kho ban đầu: 10. Hai người mua 1 đơn cùng lúc. Kết quả đúng phải là 8.

Tồn kho còn: 9 LOST UPDATE

2 buyer cùng đọc tồn=10, cùng ghi 9 → lost update!

Demo 2 - Bấm "Mua" 2 lần (double-submit)

Mạng chậm, mèo con bấm Thanh toán 2 lần. Server tính phí mấy lần?

Tính phí: 2 lần

Server xử lý mỗi request độc lập → khách bị trừ tiền 2 lần. Không idempotent!

Khi nhiều người sửa cùng một bản ghi, kẻ thù là lost update (cập nhật bị mất). Hai chiến lược khoá kinh điển:

Optimistic (lạc quan)Pessimistic (bi quan)
Ý tưởngGiả định ÍT đụng độ; kiểm lúc ghiGiả định HAY đụng độ; khoá trước khi sửa
Cách làmCột version; UPDATE … WHERE version = XSELECT … FOR UPDATE (giữ khoá)
Đụng độ thì0 dòng đổi → báo & thử lạiNgười khác CHỜ tới khi mở khoá
Hợp khiWeb, tranh chấp thấp, không giữ khoáTranh chấp cao, thao tác tới hạn
Đánh đổiPhải xử lý retry khi đụng độGiảm thông lượng, rủi ro deadlock
claude - ~/cua-hang-x

Hàm capNhatTonKho đọc rồi ghi lại số lượng - có an toàn khi nhiều người mua cùng lúc?

Chưa. Đây là “lost update”: A và B cùng đọc tồn=1, cùng trừ → ghi tồn=0 hai lần, bán 2 vé cho 1 chỗ.

Theo luật trong CLAUDE.md, tôi sẽ dùng optimistic lock (kiểm version) hoặc UPDATE … WHERE ton > 0 nguyên tử. Bạn chọn cách nào?

CLAUDE.md - mục đồng thời

## Đồng thời (bắt buộc)
- Cập nhật theo kiểu đọc-rồi-ghi phải chống lost update:
  ưu tiên optimistic lock (cột version) HOẶC cập nhật nguyên tử ở DB
  (vd UPDATE ton = ton - 1 WHERE id = ? AND ton > 0).
- Tài nguyên tới hạn, tranh chấp cao (vé/tồn kho cuối): pessimistic lock
  (SELECT … FOR UPDATE) hoặc khoá ở DB; không bao giờ bán quá số lượng.
- Giữ giao dịch NGẮN; tránh gọi mạng khi đang giữ khoá (dễ deadlock).

Tổng kết bài

Bạn vừa biến ba chuẩn - OWASP, toàn vẹn dữ liệu, khoá đồng thời - thành subagent + luật CLAUDE.md để Claude tự áp dụng. Bài kế tiếp: chất lượng (clean code) & khả năng truy cập (WCAG) - để code không chỉ đúng mà còn dễ đọc và ai cũng dùng được.

Câu hỏi thường gặp

“Chạy được” chỉ nghĩa là đúng trong điều kiện thuận lợi (happy path). Senior lo điều xảy ra khi có KẺ XẤU (injection, vượt quyền) và khi có ĐỒNG THỜI (hai người sửa cùng lúc, request bị gửi lại). Mặc định Claude tối ưu cho “làm xong việc”, nên BẠN phải mã hoá chuẩn nghề (luật + skill review) để nó tự áp dụng - đó là nội dung bài này.

Ba lớp bổ trợ nhau: (1) CLAUDE.md - luật LUÔN đúng (vd “truy vấn phải tham số hoá”); (2) Skill/Subagent - quy trình review chuyên sâu (vd subagent review bảo mật theo OWASP); (3) Hook - chặn cứng (vd PreToolUse chặn commit có khoá bí mật). Dùng cả ba để vừa hướng dẫn, vừa kiểm, vừa chặn.

OWASP Top 10 là danh sách 10 nhóm rủi ro web phổ biến nhất (injection, broken access control, mật khẩu yếu…). ASVS (Application Security Verification Standard) là bộ tiêu chí kiểm bảo mật chi tiết hơn để “chấm điểm” ứng dụng. Bạn nhúng các điểm kiểm cốt lõi của chúng vào system prompt của subagent review bảo mật.

Idempotency = làm một thao tác NHIỀU LẦN cho kết quả GIỐNG làm MỘT LẦN. Quan trọng vì mạng hay lặp request (timeout rồi thử lại): nếu “tạo đơn/trừ tiền” không idempotent, khách bị trừ tiền hai lần. Cách làm: client gửi một idempotency key; server lưu kết quả theo key, lần sau trùng key thì trả lại kết quả cũ thay vì làm lại.

Optimistic (lạc quan): giả định ít đụng độ - gắn cột version, khi ghi kiểm version chưa đổi; đụng độ thì báo và thử lại. Hợp web, ít tranh chấp, không giữ khoá. Pessimistic (bi quan): khoá bản ghi trước khi sửa (SELECT … FOR UPDATE) để người khác chờ. Hợp khi tranh chấp cao hoặc thao tác tới hạn (vd trừ tồn kho cuối cùng). Đánh đổi: pessimistic an toàn hơn nhưng giảm thông lượng & dễ deadlock.

Cả hai. Claude Code có lệnh review bảo mật dựng sẵn (vd /security-review) và có plugin/skill cộng đồng. Nhưng tự viết một subagent theo ĐÚNG ngữ cảnh dự án bạn (ngôn ngữ, framework, mối đe doạ riêng) thường hiệu quả hơn - và bạn commit nó vào repo để cả nhóm dùng.

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Vì sao “chạy được” chưa phải “đạt chuẩn nghề” về bảo mật & dữ liệu?

  1. 1

    Subagent review bảo mật

    Tạo .claude/agents/security-reviewer.md (chỉ-đọc) với checklist OWASP trong system prompt. Nhờ nó review một file xử lý input/truy vấn DB.

    Hoàn thành khi: Subagent chỉ-đọc; nó nêu được ít nhất một rủi ro cụ thể (file:dòng) theo nhóm OWASP, kèm cách vá.

  2. 2

    Luật bảo mật vào CLAUDE.md

    Thêm vào CLAUDE.md vài luật: truy vấn tham số hoá, không hardcode bí mật, kiểm tra quyền ở server. Quan sát Claude bám theo.

    Hoàn thành khi: Lần sau Claude tự viết truy vấn tham số hoá & đọc khoá từ biến môi trường mà bạn không phải nhắc.

  3. 3

    Chặn lộ bí mật

    Thêm deny Read(./.env)Read(./secrets/**); (nâng cao) thêm hook PreToolUse chặn commit chứa khoá. Thử nhờ Claude đọc .env.

    Hoàn thành khi: Claude bị chặn khi đọc .env; bí mật không lọt vào ngữ cảnh hay commit.

  4. 4

    Idempotency

    Bằng lời (hoặc code): thiết kế một endpoint “tạo đơn hàng” idempotent. Khách bấm 2 lần (hoặc mạng lặp request) thì sao?

    Hoàn thành khi: Có idempotency key; request trùng key trả lại đơn cũ - không tạo 2 đơn / không trừ tiền 2 lần.

  5. 5

    Chọn loại khoá

    Hai tình huống: (a) sửa hồ sơ cá nhân của chính mình; (b) trừ 1 vé cuối cùng khi 100 người cùng mua. Mỗi cái nên optimistic hay pessimistic?

    Hoàn thành khi: (a) optimistic (ít tranh chấp); (b) pessimistic hoặc khoá ở DB (tranh chấp cao, không được bán quá số vé). Bạn giải thích được vì sao.