M2 là bảng kê của riêng bạn. M3 biến nó thành luật chung có ba lớp: văn bản, máy, dấu vết. Việc gì máy kiểm được thì đừng để văn bản gánh. M2 was your own inventory. M3 turns it into shared rules in three layers: paper, machine, trace. If a machine can check it, don't make a document carry it.
Rủi ro nằm ở người kém cẩn thận nhất vào ngày bận nhất: dán .env vào ChatGPT cá nhân, thêm package AI gợi ý mới xuất hiện trên npm 3 ngày, duyệt 400 dòng code AI sinh mà không có test. Những việc này không cần thêm kiến thức. Chúng cần quy trình.
Risk lives with the least careful person on their busiest day: pasting .env into personal ChatGPT, adding an AI-suggested package that appeared on npm 3 days ago, approving 400 lines of untested AI code. None of these needs more knowledge. They need process.
AI usage policy 1–2 trang.A 1–2 page AI usage policy.
Secret scanning + kiểm tra dependency trong pipeline.Secret scanning + dependency checks in the pipeline.
Mục AI-assisted trong PR template.An AI-assisted section in the PR template.
Ba lớp bù lỗ cho nhau. Thiếu lớp nào, rủi ro lọt qua đúng chỗ đó.The layers cover each other's holes. Drop one and risk slips through exactly there.
Repo shop-web (Next.js) và shop-api (Node), triển khai bằng GitHub Actions. Team lead muốn: một văn bản ngắn để mọi người ký nhận, CI chặn lỗi phổ biến nhất, và cách để reviewer biết PR nào có code AI sinh.
Repos shop-web (Next.js) and shop-api (Node), deployed with GitHub Actions. The team lead wants a short document everyone signs off on, CI that blocks the common mistakes, and a way for reviewers to know which PRs contain AI code.
permissions.deny cho .env*Org account, permissions.deny on .env*Policy 10 trang thì không ai đọc. 1–2 trang đọc trong 5 phút và nhớ được phần chính. Chi tiết (pattern secret, cấu hình CI) để ở file cấu hình. Một policy tốt trả lời đủ 7 câu hỏi: Nobody reads a 10-page policy. 1–2 pages take 5 minutes and stick. Details (secret patterns, CI config) live in config files. A good policy answers 7 questions:
Dùng cho mọi loại code, trừ dữ liệu cấm.Any code, except banned data.
Chỉ cho việc không chạm code/dữ liệu công ty, hoặc khi đã bật setting bắt buộc.Only for work off company code/data, or with a required setting on.
Không đạt tiêu chí tối thiểu.Fails the minimum bar.
Danh sách chỉ có "được" và "không được" sẽ đẩy người ta sang dùng chui. Bốn tiêu chí trên lấy thẳng từ cột "chính sách lưu dữ liệu" của bảng kê M2. MCP server cũng là một công cụ: ghi rõ nó đọc gì, ghi gì, ai đã duyệt.A bare allowed/not-allowed list pushes people into shadow use. The four criteria come straight from the "retention policy" column of the M2 inventory. An MCP server is a tool too: note what it reads, writes, and who reviewed it.
| MàuColour | LoạiKind | Ví dụExamples | Quy tắcRule |
|---|---|---|---|
| CấmBanned | Secret | API key, token, mật khẩu DB, private key, nội dung .env, connection stringAPI keys, tokens, DB passwords, private keys, .env contents, connection strings | Không bao giờ, kể cả công cụ được duyệtNever, even with approved tools |
| CấmBanned | PII khách hàng (thông tin định danh cá nhân)Customer PII (personally identifiable information) | Tên + SĐT + địa chỉ giao hàng, email, đơn hàng thật, dump bảng customersName + phone + delivery address, email, real orders, a customers dump | Không bao giờ. Cần dữ liệu mẫu thì dùng fakerNever. Need sample data? Use faker |
| CấmBanned | Mã nguồn mậtConfidential source | Thuật toán định giá, cấu hình chống gian lận, code đối tác theo NDAPricing algorithm, anti-fraud config, partner code under NDA | Không công cụ nào, trừ khi team lead đồng ý bằng văn bảnNo tool, unless the team lead agrees in writing |
| Cẩn thậnCareful | Code thường của công tyOrdinary company code | Component UI, API CRUD, testUI components, CRUD APIs, tests | Chỉ công cụ được duyệtApproved tools only |
| Tự doFree | Kiến thức chungGeneral knowledge | "useEffect cleanup chạy khi nào?", code mẫu từ docs công khai"When does a useEffect cleanup run?", sample code from public docs | Công cụ nào cũng đượcAny tool |
Test chạy trong CI và thất bại khi bỏ code mới. AI viết code "trông đúng" rất giỏi; test là cách rẻ nhất để biết nó đúng thật.Tests run in CI and fail without the new code. AI is great at code that "looks right"; tests are the cheapest proof it is.
Tồn tại trên registry, không quá mới, có người duy trì, license cho phép. Chống slopsquatting (M2: ~20% package AI gợi ý không tồn tại).Exists on the registry, not too new, maintained, allowed licence. Guards against slopsquatting (M2: ~20% of AI-suggested packages don't exist).
Không giải thích được thì không merge. AI là công cụ; người mở PR vẫn là người chịu trách nhiệm.Can't explain it, don't merge it. AI is a tool; the PR author stays accountable.
Auth, thanh toán, phân quyền, xử lý PII: thêm một reviewer, ghi trong CODEOWNERS.Auth, payments, permissions, PII handling: one extra reviewer, set in CODEOWNERS.
Chọn công cụ, dữ liệu cấm và quy tắc review. Bên phải là policy đầy đủ 7 mục và PR template tương ứng. Thước đo cho biết policy còn nằm trong 1–2 trang không. Mọi thay đổi lưu trong trình duyệt của bạn. Pick tools, banned data and review rules. On the right: the full 7-section policy and the matching PR template. The meter shows whether it still fits in 1–2 pages. Everything is saved in your browser.
Secret scanning có bốn tầng. Chặn càng sớm càng rẻ, nhưng chỉ tầng CI là không ai bỏ qua được.Secret scanning has four tiers. The earlier the cheaper, but only the CI tier can't be skipped.
gitleaks git --staged. Rẻ nhất vì secret chưa rời máy, nhưng dev tắt hook được.gitleaks git --staged. Cheapest, the secret hasn't left the machine, but hooks can be disabled.
GitHub: miễn phí cho repo public; repo private của tổ chức cần gói trả phí (Secret Protection). Kiểm tra lại giá.GitHub: free for public repos; org private repos need a paid plan (Secret Protection). Check current pricing.
tuỳ góiplan-dependentLưới an toàn: ai tắt hook thì CI vẫn chặn. Đặt là required check (bắt buộc qua mới được merge).The safety net: hook off, CI still blocks. Make it a required check.
bắt buộcmustSecret cũ đã nằm trong lịch sử git, quét diff không thấy. Chạy 1 lần rồi định kỳ.Old secrets sit in git history where a diff scan can't see them. Run once, then on a schedule.
1 lầnoncegitleaks-action cần GITLEAKS_LICENSE cho repo của tổ chức, và bản v2 đã ngừng chạy. Chạy thẳng binary gitleaks (MIT) thì đơn giản hơn. Cảnh báo nhầm (false positive) là thứ giết chết secret scanning: đưa file test và key mẫu vào allowlist (danh sách bỏ qua) trong .gitleaks.toml, mỗi dòng ghi lý do.gitleaks-action needs a GITLEAKS_LICENSE for org repos, and v2 has stopped running. Running the gitleaks binary (MIT) directly is simpler. False positives kill secret scanning: allowlist test files and sample keys in .gitleaks.toml, each line with a reason.
Bắt CVE trong dependency trực tiếp và gián tiếp.Catches CVEs in direct and transitive dependencies.
Rủi ro đặc thù của code AI sinh. Package độc hại đăng hôm qua chưa có CVE, nên npm audit báo sạch. Phải hỏi registry: tồn tại? tuổi? lượt tải? license?The risk specific to AI code. A malicious package published yesterday has no CVE, so npm audit is clean. Ask the registry: exists? age? downloads? licence?
import { execSync } from 'node:child_process';
const base = process.argv[2] ?? 'origin/main';
const MIN_AGE_DAYS = 30, MIN_WEEKLY = 1000;
const LICENSES = new Set(['MIT', 'ISC', 'Apache-2.0', 'BSD-2-Clause', 'BSD-3-Clause']);
const deps = (pkg) => ({ ...pkg.dependencies, ...pkg.devDependencies });
const before = deps(JSON.parse(execSync(`git show ${base}:package.json`, { encoding: 'utf8' })));
const after = deps(JSON.parse(execSync('cat package.json', { encoding: 'utf8' })));
const added = Object.keys(after).filter((n) => !(n in before));
let fail = 0;
for (const name of added) {
const meta = await fetch(`https://registry.npmjs.org/${name}`);
if (meta.status === 404) { console.error(`✗ ${name}: not on npm`); fail++; continue; }
const m = await meta.json();
const ageDays = (Date.now() - new Date(m.time.created)) / 864e5;
const dl = await (await fetch(`https://api.npmjs.org/downloads/point/last-week/${name}`)).json();
const lic = m.license ?? m.versions?.[m['dist-tags'].latest]?.license;
const problems = [];
if (ageDays < MIN_AGE_DAYS) problems.push(`${ageDays.toFixed(0)} days old`);
if ((dl.downloads ?? 0) < MIN_WEEKLY) problems.push(`${dl.downloads ?? 0} downloads/week`);
if (!LICENSES.has(lic)) problems.push(`licence ${lic}`);
if (problems.length) { console.error(`✗ ${name}: ${problems.join(', ')}`); fail++; }
else console.log(`✓ ${name}`);
}
process.exit(fail ? 1 : 0);name: guardrails
on: pull_request
jobs:
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: gitleaks (PR diff)
run: |
curl -sSL https://github.com/gitleaks/gitleaks/releases/download/v8.24.0/gitleaks_8.24.0_linux_x64.tar.gz | tar xz
./gitleaks git --log-opts="origin/${{ github.base_ref }}..HEAD" --redact --exit-code 1
deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci --ignore-scripts
- name: known vulnerabilities
run: npx --yes osv-scanner@latest --lockfile=package-lock.json
- name: new packages
run: node scripts/check-new-deps.mjs origin/${{ github.base_ref }}Kiểm tra bản mới nhất của gitleaks và các action trước khi dùng; pin theo version hoặc commit SHA. Script báo lỗi không có nghĩa là "cấm" mà là "cần người xem": reviewer kiểm tra rồi thêm vào deps-allowlist.txt kèm lý do.Check the latest gitleaks and action versions before use; pin to a version or SHA. A failing script doesn't mean "banned" but "a human should look": the reviewer checks, then adds it to deps-allowlist.txt with a reason.
postinstall. Cài không chạy script là giảm rủi ro ngay trong CI.Malicious packages usually run code in postinstall. Installing without scripts cuts that risk in CI.continue-on-error: true. Đo cảnh báo nhầm, chỉnh allowlist mà không chặn ai.continue-on-error: true. Measure false positives, tune the allowlist, block no one.Mục này phục vụ reviewer, để biết phải nhìn kỹ chỗ nào. Nó không dùng để chấm điểm người viết. Thử tick bên dưới để xem job ai-label phản ứng thế nào.This section serves the reviewer, telling them where to look hard. It does not score the author. Tick boxes below to see how the ai-label job reacts.
| Lựa chọnChoice | Lý doReason |
|---|---|
| Ghi file/hàm, không ghi "% code AI"Files/functions, not "% AI code" | Reviewer cần biết chỗ nào để đọc kỹ; con số % không đo được, không dùng đượcThe reviewer needs to know where to read closely; a % can't be measured or used |
| Checkbox, không hỏi mởCheckboxes, not open questions | Điền trong 20 giây. Mục điền lâu sẽ bị bỏ trống20 seconds to fill. Slow sections get left blank |
| Có ô "Không dùng AI"A "No AI used" box | Phân biệt "không dùng" với "quên điền"; CI báo lỗi khi cả hai ô trốngTells "didn't use" from "forgot"; CI fails when both are empty |
| Không phán xétNo judgemental wording | Khai báo bị phạt thì người ta khai thiếu. Dấu vết chỉ có giá trị khi trung thựcPunish disclosure and people under-declare. A trace is only worth something when honest |
Nhãn ai-assisted tự độngAutomatic ai-assisted label | Cuối sprint, lọc theo nhãn là có số liệu: tỷ lệ PR dùng AI, số vòng review, bug sau mergeAt sprint end, filter by label for numbers: share of AI PRs, review rounds, post-merge bugs |
Để được duyệt, gửi team lead một trang tóm tắt, đừng gửi một bản nháp trống. Duyệt xong thì ghi tên + ngày vào cuối policy và commit vào docs/ai-usage-policy.md.To get approval, send the team lead a one-page brief, not a bare draft. Once approved, record name + date at the bottom and commit it as docs/ai-usage-policy.md.
.gitleaks.toml, allowlistLog every alert: real or false? Tune .gitleaks.toml, allowlist| Chỉ sốMetric | Cách lấySource | Để làm gìPurpose |
|---|---|---|
PR có nhãn ai-assisted / tổng PRPRs labelled ai-assisted / total | Lọc nhãnLabel filter | AI được dùng nhiều đến đâuHow much AI is used |
| % PR điền mục AI-assisted% PRs with the section filled | Job ai-label không báo lỗiai-label job passing | Mức tuân thủCompliance |
| gitleaks chặn (đúng / nhầm)gitleaks blocks (real / false) | Log CI + ghi chép tuần 1CI logs + week-1 notes | Chỉnh allowlist; chứng minh giá trịTune allowlist; prove value |
Package mới bị deps-check giữNew packages held by deps-check | Log CICI logs | Thấy rủi ro slopsquatting thậtSee real slopsquatting risk |
| Thời gian CI tăng thêmExtra CI time | So trước/sauBefore/after | Kiểm chứng phần "tốn gì"Check the "cost" claim |
| Trước (sau M2)Before (after M2) | Sau M3After M3 | |
|---|---|---|
| Công cụTools | Mỗi người tự chọnEveryone picks their own | 3 mức, có tiêu chí, có quy trình đề xuất3 tiers, criteria, a proposal process |
| Dữ liệu trong promptData in prompts | "Cẩn thận nhé""Be careful" | Bảng đỏ/vàng/xanh có ví dụRed/amber/green table with examples |
| Secret | Tuỳ ý thức từng ngườiIndividual discipline | Pre-commit + CI chặn + quét lịch sửPre-commit + CI block + history scan |
| Package mớiNew packages | Reviewer tự để ý (nếu nhớ)Reviewer notices (if they remember) | deps-check hỏi registrydeps-check asks the registry |
| Code AI sinhAI code | Không ai biết PR nào cóNobody knows which PRs | Nhãn ai-assisted + file cụ thểai-assisted label + specific files |
| Sự cốIncidents | Không có kịch bảnNo playbook | Thu hồi key trong 1 giờ, không đổ lỗiRevoke within 1 hour, no blame |
Có secret, PII khách hàng
hoặc mã nguồn mật?
├─ Có → KHÔNG. Dùng dữ liệu giả
│ hoặc mô tả bằng lời.
└─ Không
└─ Code/dữ liệu của công ty?
├─ Có → Chỉ công cụ ĐƯỢC DUYỆT
└─ Không → Công cụ nào cũng được
Contains a secret, customer PII
or confidential source?
├─ Yes → NO. Use fake data
│ or describe it in words.
└─ No
└─ Company code/data?
├─ Yes → APPROVED tools only
└─ No → Any tool
Code AI sinh sắp vào PR?
├─ Thêm package mới?
│ → deps-check phải qua
├─ Chạm auth/payment/admin?
│ → cần 2 approve
└─ Luôn luôn
→ có test + điền AI-assisted
AI code about to go into a PR?
├─ Adds a new package?
│ → deps-check must pass
├─ Touches auth/payment/admin?
│ → 2 approvals
└─ Always
→ tests + fill in AI-assisted
| Anti-pattern | Vì sao hỏngWhy it fails | Làm thayDo instead |
|---|---|---|
| Policy 15 trang copy mẫu trên mạng15-page policy copied from the web | Không ai đọc, không khớp công cụ thậtNobody reads it; doesn't match real tools | 1–2 trang, viết từ bảng kê M21–2 pages, from the M2 inventory |
| Cấm hết AIBan all AI | Người ta dùng chui bằng tài khoản cá nhânPeople use personal accounts in secret | Ba mức duyệt, có kênh đề xuấtThree tiers, a proposal channel |
| Policy chỉ là văn bảnPolicy is paper only | Ngày bận nhất, quy tắc bị quênForgotten on the busiest day | Việc máy kiểm được → pipelineMachine-checkable → pipeline |
| Bật chặn ngay ngày đầuBlock from day one | Cảnh báo nhầm làm team đòi tắt CIFalse positives → team wants CI off | 1 tuần cảnh báo, rồi chặn1 week warn, then block |
| Chỉ có pre-commit hookOnly a pre-commit hook | Tắt được; clone mới không có hookCan be disabled; fresh clones lack it | Hook + CI required checkHook + CI required check |
Chỉ chạy npm auditOnly npm audit | Package độc hại mới đăng chưa có CVEFresh malicious packages have no CVE | Thêm kiểm tra package mớiAdd a new-package check |
| Hỏi "% code do AI viết"Ask "% written by AI" | Không đo được, không dùng đượcUnmeasurable, unusable | Hỏi file/hàm cụ thểAsk for files/functions |
| Phạt người khai báoPunish disclosure | Lần sau họ khai thiếuNext time they under-declare | Khai báo là thông tin cho reviewerDisclosure is info for the reviewer |
| Lộ secret thì chỉ xoá commitLeaked secret → delete the commit | Vẫn nằm trong lịch sử, fork, log AIStill in history, forks, AI logs | Thu hồi key trước, dọn sauRevoke first, clean up after |
| Duyệt xong rồi bỏ đóApprove and forget | Công cụ AI đổi chính sách vài tháng một lầnAI tools change policy every few months | Xem lại mỗi quý, có số phiên bảnQuarterly review, versioned |
Ngắn thì người ta đọc và nhớ. Pattern secret, ngưỡng tuổi package, allowlist đặt trong file cấu hình (.gitleaks.toml, deps-allowlist.txt, workflow CI).Short gets read and remembered. Secret patterns, age thresholds and allowlists live in config (.gitleaks.toml, deps-allowlist.txt, the CI workflow).
Hook chạy trên máy dev, có thể bị tắt hoặc chưa cài (clone mới). CI với required check thì không ai bỏ qua được.Hooks run locally and can be disabled or missing (fresh clone). A CI required check can't be skipped.
npm audit sạch, sao package mới vẫn nguy hiểm?npm audit is clean — why can a new package still be dangerous?npm audit chỉ biết CVE đã công bố. Package mới đăng (slopsquatting) chưa có CVE. Phải kiểm tra tồn tại, tuổi, lượt tải, license.It only knows published CVEs. A fresh package (slopsquatting) has none. Check existence, age, downloads, licence.
Reviewer cần biết chỗ nào để đọc kỹ. Con số % không đo được và không giúp review.The reviewer needs to know where to read closely. A % can't be measured and doesn't help.
.env vào ChatGPT. Việc đầu tiên?Pasted .env into ChatGPT. First move?Coi như đã lộ: thu hồi và tạo key mới ngay (trong 1 giờ), báo team lead, ghi lại sự cố. Xoá đoạn chat không đủ.Treat it as leaked: revoke and reissue keys now (within 1 hour), tell the team lead, log it. Deleting the chat isn't enough.
Để đo và chỉnh cảnh báo nhầm trước. Bị chặn sai vài lần, team sẽ đòi tắt CI.To measure and tune false positives first. A few wrong blocks and the team wants CI off.