Khi agent có quyền đọc file, chạy shell, sửa code và ship PR, một dòng dặn trong prompt là chưa đủ. Cần một lớp kiểm tra trước khi tool thật sự chạy.
Bạn giao cho agent một việc nhỏ: sửa validation trong một module. Nó muốn chắc hơn nên quét thêm vài thư mục, mở file config nhạy cảm, rồi tiện tay gom luôn vài cleanup không liên quan vào cùng một PR.
Nhìn qua thì mọi thứ vẫn ổn. Test vẫn pass. Nhưng context đã lẫn file rác, secret có thể đã đi vào transcript (toàn bộ bản ghi hội thoại giữa bạn và agent), còn review thì bị loãng vì phần cần sửa và phần "tiện tay" nằm chung một PR.
cat .env — secret vào transcript.env — privacy-block hỏi userAgent không tự nhiên cẩn thận hơn. Chỉ là trước khi nó làm gì đó, có một lớp đứng ra hỏi: việc này có nên được phép chạy không?
Hook là lớp chặn thật.
Guard rail không làm model thông minh hơn. Nó kiểm tra hành động trước khi tool chạy. Rule, hard-gate và guard skill là các lớp định hướng thêm, hữu ích nhưng vẫn phụ thuộc model hoặc user có gọi hay không.
I. Khái niệm
Guard rail là lớp đứng giữa agent và hành động: việc rủi ro thì chặn, việc đáng nghi thì cảnh báo. Nó giống lan can ở mép cầu thang: không làm bạn đi giỏi hơn, nhưng giảm khả năng một cú bước hụt thành tai nạn. Khác với lời dặn trong prompt, tức prompt instruction, guard rail chạy ở tầng điều phối tool, không phụ thuộc model có "nhớ" hay không.
| Guard rail | Lời dặn | |
|---|---|---|
| Chạy ở đâu | Harness, ngoài model | Trong context, model đọc |
| Ép buộc | Code chặn thật | Model tự nguyện tuân |
| Context dài | Vẫn chạy | Dễ quên / trượt |
| Model hiểu lệch | Vẫn chặn (nếu không crash) | Lệch theo model |
| Sửa đổi | Đụng code/config | Sửa text là xong |
Harness là lớp runtime bọc quanh model. Model chỉ quyết định "tôi muốn đọc file này" hoặc "tôi muốn chạy lệnh này"; harness mới là bên nhận tool call, quản quyền, gọi hook, cho tool chạy hoặc chặn, rồi trả kết quả lại cho model. Hook là script nhỏ harness tự gọi tại các thời điểm cố định trong vòng đời một tool call — lúc nhận prompt, trước tool, sau tool — để xen vào kiểm tra. Vì mọi hành động thật đều qua harness, đây là chỗ gắn guard rail.
Đọc thêm nền khái niệm: Harness Engineering là gì? của Duy /zuey/.
Chi tiết ClaudeKit Hooks có thể xem thêm tại VividKit Guides.
Hook không nằm trong model. Nó nằm ở lifecycle của Claude Code: settings.json quyết định hook nào được gọi, .ck.json/ENV quyết định runtime behavior, còn output của hook có thể cho chạy, chặn, inject context hoặc ghi state/artifact sau khi tool hoặc session kết thúc.
Điểm mấu chốt: pre-tool hook trả exit code. Exit 0 cho tool chạy, exit 2 chặn và đẩy lý do về model. Ba điểm chèn chính trong flow có tên kỹ thuật tương ứng: UserPromptSubmit (lúc nhận prompt), PreToolUse (trước tool), PostToolUse (sau tool). Riêng Stop/SubagentStop chạy khi session hoặc subagent kết thúc, nên nằm ở bản đồ lifecycle phía trên.
Exit code là số process trả về khi kết thúc. Claude Code đọc nó để quyết tool có chạy không:
exit 0 — OK, tool chạy. stdout đọc làm JSON output.exit 2 — chặn. Bỏ stdout, đẩy stderr về model làm thông báo lỗi.exit 1 (hoặc mã khác) — lỗi nhưng KHÔNG chặn. Báo lỗi rồi tool vẫn chạy tiếp.Chỉ exit 2 mới chặn thật. Hook muốn enforce policy phải dùng đúng exit 2 — dùng nhầm exit 1 theo thói quen Unix là guard tưởng bật mà thực ra mở.
Hook exit 2, tool không chạy. Nhưng hook crash → Claude Code cho qua (fail-open). Bug ở hook làm guard tắt âm thầm.
Thêm text vào context (rule, hard-gate). Model phải tự tuân. Mạnh hay yếu phụ thuộc model.
.env, quét quá rộng, hoặc kéo cleanup ngoài scope vào PRII. Hiện thực trong CK
Sau một fresh CK install, file-access guard nằm ở PreToolUse hook như scout-block và privacy-block. Nếu phiên Claude Code đang chạy ở mode bỏ qua permission prompt, các hook này vẫn là lớp chặn chính của CK. Nhưng hook CK là fail-open: crash hoặc bị disable thì tool call có thể đi tiếp.
| Nhóm | Cơ chế | Ví dụ | Ép buộc |
|---|---|---|---|
| Chặn file/path | PreToolUse | scout-block | code · fail-open |
| Chặn sai bước | UserPromptSubmit | simplify-gate | code |
| Thêm context | UserPromptSubmit | dev-rules-reminder | code |
| Giữ tên sạch | Pre/Post/Stop | descriptive-name | code · nhắc |
| Hard-gate skill | XML markdown | <HARD-GATE> | lời dặn |
| Rule lời dặn | CLAUDE.md | review-audit | lời dặn |
| Guard skill | User gọi | ck:security-scan | user |
scout-block, privacy-block · Section 05-06simplify-gate, workflow gate · Section 07-08dev-rules-reminder inject text vào prompt · Section 10descriptive-name · hook grid trong Section 04<HARD-GATE> · Section 09CLAUDE.md rules là nội dung được inject · Section 10ck:security-scan, ck:ship · Section 11| Tình huống | Lớp xử lý |
|---|---|
Đọc node_modules/react/ | scout-block, exit 2 |
Đọc .env check key | privacy-block, exit 2 + approval prompt |
Glob **/*.ts ở root | scout-block broad-pattern |
| Prompt ship khi diff đã phình to | simplify-gate, nếu gate.enabled=true |
| Bắt đầu code khi chưa có plan/review | ck:cook HARD-GATE |
| Đổi threshold user đã chốt | review-audit rule: hỏi lại trước khi đổi |
| File mới tên mơ hồ | descriptive-name, PreToolUse(Write) |
| Plan dùng link/text sai format | plan-format-kanban, PostToolUse(Edit/Write/MultiEdit) |
| Hai teammate đụng cùng file | team-coordination rule, chỉ trong Agent Team |
Hai lớp config quyết định guard nào chạy: settings.json gắn hook vào lifecycle của Claude Code; .ck.json bật/tắt từng hook và chỉnh threshold.
# Global scope
~/.claude/settings.json
~/.claude/.ck.json
~/.claude/hooks/*.cjs
# Project scope
.claude/settings.json
.claude/.ck.json
.claude/hooks/*.cjs
Snippet dưới đây chỉ minh hoạ một lát cắt PreToolUse trong fresh CK install: CK cung cấp scout-block và privacy-block, rồi wire chúng vào lifecycle để Claude Code gọi trước khi tool chạy. Full hook config trên máy đã cài còn nhiều lifecycle event hơn; nơi cần xem là settings.json của Claude Code.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Glob|Grep|Read|Edit|Write",
"hooks": [
{ "command": "node \".claude/hooks/scout-block.cjs\"" },
{ "command": "node \".claude/hooks/privacy-block.cjs\"" }
]
}
]
}
}
settings.json là bản đồ lifecycle: khi user gửi prompt thì hook nào chạy, trước khi tool chạy thì hook nào được gọi, sau khi tool xong thì hook nào xử lý tiếp. Với global install, CLI đổi command tương đối trong template thành dạng node "$HOME/.claude/hooks/scout-block.cjs"; với project install, path thường giữ ở dạng .claude/hooks/....
Đừng gom mọi thứ thành "mặc định bật/tắt". Trạng thái của một hook có ba lớp khác nhau:
| Nhãn | Nghĩa khi audit |
|---|---|
script file | File .cjs có nằm trong .claude/hooks/. Có file chưa có nghĩa là hook đang chạy. |
wired | settings.json đã gắn script vào lifecycle event của Claude Code. Không wire thì Claude Code không tự gọi hook đó. |
runtime flag | Khi hook đã được gọi, code bên trong đọc .ck.json/DEFAULT_CONFIG/ENV để quyết định có chạy tiếp không. Một vài guard còn có công tắc con như privacyBlock hoặc simplify.gate.enabled. |
Bảng dưới đây là snapshot danh sách hook theo stable claudekit-engineer@2.19.1. Update 2026-06-09: claudekit-engineer@2.19.2-beta đang chuẩn bị remove task-completed-handler và teammate-idle-handler; khi upstream ClaudeKit release new version, danh sách này có thể còn 14 hook.
session-initwiredProject/env setup khi startup, resume, clear, compact.
usage-quota-cache-refreshwiredKhông đọc hook flag riêng; chạy khi được wire.
simplify-gategate offScript được gọi, nhưng simplify.gate.enabled mặc định false.
dev-rules-reminderwiredInject dev rules/context.
subagent-initwiredInject context cho subagent.
descriptive-namewiredNhắc đặt tên file/script rõ nghĩa.
scout-blockwiredChặn heavy dirs và glob quá rộng.
privacy-blockwiredChặn secret path, hỏi approval.
plan-format-kanbanwiredKhông đọc hook flag riêng; warn format plan.
session-statewiredGhi trạng thái session/task.
cook-after-plan-reminderwiredKhông có key riêng trong DEFAULT_CONFIG.hooks, nhưng isHookEnabled() chỉ tắt khi flag là false.
workflow-artifact-gatenot wiredOpt-in cho artifact gate: cần wire hook và bật flag/gate config.
task-completed-handlerremovingAgent Teams task completed; không nên coi là hook bền vững sau 2.19.1.
teammate-idle-handlerremovingAgent Teams teammate idle; không nên coi là hook bền vững sau 2.19.1.
team-context-injectnot wiredKhông có key riêng trong DEFAULT_CONFIG.hooks; chỉ có ý nghĩa khi workflow/team layer gọi tới.
usage-context-awarenessnot wiredUsage/context injection hook; không nằm trong settings.json template.
Điểm dễ sót: isHookEnabled() chỉ tắt khi hooks.<name> là false. Key thiếu thường được xem là enabled. Nhưng vài guard còn có công tắc riêng: privacy-block vẫn đọc key cũ privacyBlock=false, còn simplify-gate có simplify.gate.enabled và ENV CK_SIMPLIFY_DISABLED=1.
Muốn chỉnh nhanh, sửa .ck.json ở scope cần tác động. Project config ưu tiên hơn global khi key được set rõ; key thiếu thì tiếp tục inherit/default. Với các hook đã được wire trong settings.json, đặt false là tắt, đặt true là bật lại sau khi scope cao hơn đã tắt. Tên hook trong cột Hook bên dưới cũng là key dùng trong hooks.<name>.
// .claude/.ck.json hoặc ~/.claude/.ck.json
{
"hooks": {
"scout-block": false,
"privacy-block": true
},
"privacyBlock": true,
"simplify": {
"gate": {
"enabled": true
}
}
}
| Muốn làm | Chỉnh ở đâu | Lưu ý |
|---|---|---|
| Tắt một hook đã wire | .ck.json {"hooks":{"scout-block":false}} | Hook script vẫn còn, nhưng runtime cho qua. |
| Bật lại hook bị tắt ở global | project .ck.json {"hooks":{"scout-block":true}} | Key local rõ ràng override global. |
| Tắt privacy guard | .ck.json {"hooks":{"privacy-block":false}}.ck.json (key cũ) {"privacyBlock":false} | privacyBlock=false là key cũ nhưng vẫn có hiệu lực. |
| Bật simplify-gate chặn thật | .ck.json {
"hooks": {
"simplify-gate": true
},
"simplify": {
"gate": {
"enabled": true
}
}
} | Thiếu simplify.gate.enabled=true thì gate chưa bật chế độ chặn. |
| Tắt simplify-gate theo session/scope | settings.json {
"env": {
"CK_SIMPLIFY_DISABLED": "1"
}
} | Env override mạnh hơn config gate. |
| Bật workflow-artifact-gate | settings.json wire UserPromptSubmit / PreToolUse(Bash).ck.json {"hooks":{"workflow-artifact-gate":true}} | Fresh install chưa wire sẵn, nên chỉ sửa <code>.ck.json</code> là chưa đủ. |
Muốn biết hook có được trigger không, nhìn lifecycle event chứ không nhìn tên file. Cùng một hook script chỉ chạy khi event/matcher trong settings.json khớp với hành động hiện tại. Bảng dưới đây là cách kiểm nhanh từng hook; hook not wired cần wire trước hoặc gọi bằng manual CLI nếu script hỗ trợ.
| Hook | Trigger thủ công | Ghi chú |
|---|---|---|
session-init | Mở, resume, clear hoặc compact session Claude Code. | SessionStart(startup|resume|clear|compact). |
usage-quota-cache-refresh | Mở session, gửi prompt mới, hoặc cập nhật Task/Todo. | Cache usage; không đọc hook flag riêng. |
simplify-gate | Gửi prompt có ý định ship/merge/pr/deploy/publish khi diff đủ lớn. | Cần simplify.gate.enabled=true; default không block. |
dev-rules-reminder | Gửi prompt mới. | UserPromptSubmit; inject rules theo TTL. |
subagent-init | Start subagent bằng Task/agent flow. | SubagentStart. |
descriptive-name | Để Claude định tạo file bằng Write. | PreToolUse(Write); nhắc tên file rõ. |
scout-block | Để Claude đọc node_modules, dist, hoặc glob quá rộng. | PreToolUse trên Bash/Glob/Grep/Read/Edit/Write. |
privacy-block | Để Claude đọc .env, key file, secret path. | PreToolUse; có thêm privacyBlock. |
plan-format-kanban | Để Claude Edit/Write/MultiEdit plan file. | PostToolUse; warn format, không đọc hook flag riêng. |
session-state | Cập nhật Task/Todo, kết thúc subagent, hoặc kết thúc turn. | PostToolUse, SubagentStop, Stop. |
cook-after-plan-reminder | Cho Plan subagent kết thúc. | SubagentStop(Plan); key thiếu vẫn enabled. |
workflow-artifact-gate | Wire vào UserPromptSubmit/PreToolUse(Bash), hoặc chạy script với --stage. | Fresh install chưa tự trigger hook mode. |
task-completed-handler | Hoàn tất task trong Agent Teams. | TaskCompleted; chuẩn bị remove trong claudekit-engineer@2.19.2-beta. |
teammate-idle-handler | Để teammate trong Agent Teams hết việc và đi idle. | TeammateIdle; chuẩn bị remove trong claudekit-engineer@2.19.2-beta. |
team-context-inject | Wire vào SubagentStart, rồi start team subagent. | Script có ý nghĩa khi agent id thuộc team. |
usage-context-awareness | Wire vào event mong muốn, hoặc dùng hook cache refresh đang wired sẵn. | Wrapper legacy quanh usage quota cache refresh. |
// ~/.claude/settings.json hoặc .claude/settings.json
{
"env": {
"CK_SIMPLIFY_DISABLED": "1"
}
}
Agent hay đọc node_modules/, glob **/*.ts ở root (glob là mẫu khớp đường dẫn; **/*.ts nghĩa là mọi file .ts ở mọi thư mục con), cat dist/index.js. Mỗi lần là vài chục nghìn token nhét vào context, kéo cost và chất lượng turn sau xuống. scout-block.cjs đăng ký PreToolUse, chạy trước mọi Read/Bash/Glob/Grep.
# Baseline .ckignore — pattern-matcher.cjs xử lý
node_modules
dist
build
.next
.nuxt
__pycache__
.venv
venv
vendor
target
.git
coverage
Các lệnh build được cho qua. Những lệnh như npm build, go build, make, docker build vẫn chạy, kể cả khi quá trình build chạm node_modules hoặc dist. Nếu chặn cả nhóm này, các flow ship/test của CK dễ tự vấp ngay từ bước build.
Fail-open: parse error đều dẫn tới exit 0, tool được cho qua. Hook có bug → guard lớp đó tắt âm thầm.
Agent đụng .env, id_rsa, *.pem, credentials.yaml — secret lộ vào transcript. Một khi vào context, có thể bị log, quote lại trong reviewer output, paste vào PR.
@@PRIVACY_PROMPT_START@@
{ "type": "PRIVACY_PROMPT", "question": {...}, "options": [...] }
@@PRIVACY_PROMPT_END@@
Claude parse JSON, gọi AskUserQuestion. Nếu user duyệt, hook message hướng dẫn đọc file bằng đường approved, ví dụ cat ".env" sau khi đã hỏi; code cũng hỗ trợ prefix APPROVED:. Điểm chính: phải có approval rõ ràng, không để model tự diễn dịch consent.
privacy-block cho Bash đi qua, chỉ cảnh báo. Bất kỳ ai có quyền chạy Bash đều đọc secret mà không qua approval flow.
Privacy-block chỉ chặn READ. Rotate credential ngay, audit conversation log, check git history xem secret có bị commit không.
Một task nhỏ có thể phình ra thành PR đụng quá nhiều file: sửa validation, thêm cleanup, đổi format, chỉnh vài helper lân cận. Khi agent nói "OK ship", simplify-gate.cjs đăng ký UserPromptSubmit, đọc git diff HEAD, chỉ xét khi prompt có: ship merge pr deploy publish.
Các ngưỡng mặc định của simplify.threshold: tổng dòng thêm+xóa, số file bị đụng, và dòng thêm lớn nhất trong một file. Gate chỉ chặn khi simplify.gate.enabled=true; các số này có thể override trong .ck.json.
hooks.simplify-gate = true chỉ cho Claude Code gọi script. Muốn script thật sự chặn PR phình quá scope, phải bật thêm simplify.gate.enabled = true. Fresh install chưa bật flag này, nên gate chỉ chạy kiểm tra nhẹ và không block.
// project .ck.json — bật thật sự
{ "simplify": { "gate": { "enabled": true } } }
matchedSeverity() bỏ qua "don't ship", "ship on" → "ship on Friday" cũng đi qua. Muốn tắt gate cho mọi session ở scope đó, đặt CK_SIMPLIFY_DISABLED=1 trong settings.json dưới key env.
Full pipeline → mỗi phase để lại file JSON ghi quyết định, như hoá đơn sau mỗi bước. Hook này được thiết kế để gắn vào UserPromptSubmit + PreToolUse(Bash), nhưng không nằm trong hook set chạy sẵn sau fresh install; muốn dùng phải opt-in.
| Artifact | Phase | Nội dung |
|---|---|---|
context-snippets.json | scout/plan | Snippet code đã đọc |
risk-gate.json | predict | High-risk flag, auto-stop |
verification.json | fix/cook | 5-point checklist |
review-decision.json | code-review | Reviewer verdict |
adversarial-validation.json | adversarial | Adversarial pass |
Gate này có hai mức xử lý. Ở bước rủi ro như ship, push, PR hoặc deploy, hook có thể dừng flow ngay và trả lý do về cho model (emitBlock()). Ở bước nhẹ hơn như finalize hoặc commit, hook cho flow đi tiếp nhưng thêm cảnh báo vào context để model tự sửa (emitSoft()).
workflow-artifact-gate là lớp kiểm artifact mạnh nhất, nhưng fresh install chưa tự gọi nó. Muốn dùng, phải wire hook trong settings.json và bật config trong .ck.json; nếu thiếu bước này, ship/push/PR/deploy không bị gate chặn.
Hook chỉ chặn tool call. Có lỗi không phải tool call mà là trình tự: code trước plan, fix trước scout. CK gắn <HARD-GATE> vào skill markdown.
<HARD-GATE> Do NOT write implementation code until a plan exists and has been reviewed. Exception: --fast skips research but still requires a plan step. User override: if user explicitly says 'just code it', respect their instruction. </HARD-GATE>
ck:cook và ck:fix mỗi skill có 4 hard-gate (plan/scout-first, exact-req hoặc root-cause, no-side-effects).
Chữ "hard" dễ hiểu nhầm — không phải chặn bằng code. Là lời dặn trong prompt. Model bỏ qua → không có exit 2 chặn. Cách bọc XML tạo signal mạnh hơn rule thường để model khó hợp lý hoá việc đi tắt.
dev-rules-reminder.cjs đăng ký UserPromptSubmit, đưa rules text vào mỗi prompt. Ở đây có hai lớp cần tách rõ: hook là cơ chế inject context, còn rule trong CLAUDE.md là nội dung được inject. TTL 5 phút theo (sessionId, baseDir) để tránh đốt token.
| File | Chống lại | Đặc trưng |
|---|---|---|
review-audit-self-decision | Audit reverse decision đã confirm | verified sticky |
development-rules | Skip test, fake data | YAGNI/KISS/DRY |
team-coordination-rules | Hai agent cùng edit | ownership glob |
commit-messages | Commit body dài | single-line |
orchestration-protocol | Pass full history | context isolation |
Rules là text trong context, không phải code. Claude có thể lệch nếu prompt user đè. Bù lại rất dễ thêm/sửa. Phù hợp quy ước phong cách hơn guard bảo mật.
| Skill | Khi dùng | Bắt cái gì |
|---|---|---|
ck:security-scan | Pre-release | Secret, CVE, SQLi, XSS, path traversal |
ck:predict | Trước feature rủi ro | 5 persona, GO/CAUTION/STOP |
ck:scenario | Pre-impl | 12-dimension edge case sweep |
ck:ship | Pre-PR | Stop on test fail, never force-push |
code-reviewer agent chạy checklist 9 mục: concurrency, error boundary, API contract, backwards compat, input validation, auth/authz, N+1, data leak, fact-check. Guard cuối trước merge.
III. Giới hạn & thực hành
scout-block/privacy-block, không phải một danh sách permissions.deny riêng cho secret/heavy dirsAgent mất kiểm soát có thể call hàng nghìn Read/Glob/Bash. Hook CK không đếm số tool call, không tự dừng khi gần hết quota. Giới hạn quota/cost nằm ở Claude Code, provider hoặc account billing, không phải ở guard rail này.
gate.enabled = true.ckignore override.env: đọc kỹ, đừng auto-yessettings.json đang gắn hook nào vào lifecycle nào?.ck.json đã disable hook nào? (outer + inner config).ckignore override? node_modules có bị ! allowlist?| Thuật ngữ | Nghĩa gọn |
|---|---|
| harness | Lớp điều phối giữa model và máy: gửi lệnh tool, quản quyền, chạy hook. Là chỗ gắn guard rail. |
| hook | Script nhỏ harness tự gọi tại thời điểm cố định trong vòng đời prompt/tool/session để xen vào kiểm tra hoặc ghi state. |
| lifecycle event | Các mốc hook được gọi: UserPromptSubmit (nhận prompt), PreToolUse (trước tool), PostToolUse (sau tool), Stop/SubagentStop (kết thúc session/subagent). |
| exit code | Số process trả khi kết thúc. 0 cho qua, 2 chặn, 1 báo lỗi nhưng không chặn. |
| fail-open | Khi hook lỗi/crash, harness cho tool chạy tiếp thay vì chặn. Guard tắt âm thầm. |
| transcript | Toàn bộ bản ghi hội thoại giữa user và agent trong session. |
| context | Vùng thông tin model "thấy" khi suy luận. Đầy file rác → cost cao, chất lượng giảm. |
| glob | Mẫu khớp đường dẫn. **/*.ts = mọi file .ts ở mọi thư mục con. |
| secret | Thông tin nhạy cảm: API key, private key, credential. Lộ vào transcript là rủi ro. |
| LOC | Lines of code — số dòng code, dùng đo độ lớn diff. |
| artifact | File JSON mỗi phase pipeline để lại, ghi quyết định để phase sau kiểm tra. |
Khi agent có quyền rộng, đừng kết luận "CK đã cài là an toàn". Trước khi tin guard rail, kiểm tra hai nơi: settings.json đang wire hook nào, .ck.json đang bật/tắt gì.
Mỗi lớp bảo vệ một kiểu rủi ro: hook chặn tool call, rule nhắc hành vi, hard-gate giữ quy trình, guard skill chỉ chạy khi được gọi. Biết lớp nào đang chạy và lớp nào chỉ là lời dặn giúp audit đúng chỗ, trước khi lỗi lọt vào PR và đi tiếp lên PROD.
Nếu bạn đang build hệ thống guardrails, có thể tham khảo các pattern đã áp dụng trong ClaudeKit. Nếu cân nhắc mua ClaudeKit để dùng luôn bộ hooks, skills và workflow guard rails thay vì tự ghép từng mảnh, bạn có thể mua qua referral link này để được giảm 20%.
Nhận giảm 20%