Một agent chạy trên máy cá nhân thì ít giá trị. Sức mạnh thật đến khi nó tự chạy trên mỗi pull request, comment thẳng vào dòng code có lỗi, và chặn merge khi có P0. Bài này biến agent thành người gác cổng tự động.
1. Nguyên tắc vàng: chỉ quét DIFF
Quét toàn repo mỗi PR vừa chậm vừa tốn token, lại tạo nhiễu về code cũ chưa ai đụng. Chỉ quét phần thay đổi (kèm vài dòng ngữ cảnh):
git diff --unified=8 origin/main...HEAD -- '*.php' '*.py' '*.js' > changes.diff
--unified=8 cho 8 dòng ngữ cảnh quanh mỗi thay đổi để agent hiểu bối cảnh mà vẫn gọn.
2. Pipeline CI (ví dụ GitHub Actions)
name: sast-agent
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Get diff
run: git diff --unified=8 origin/${{github.base_ref}}...HEAD
-- '*.php' '*.py' > changes.diff
- name: Run agent
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
run: python -m agent.scan --diff changes.diff --out report.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: report.sarif }
- name: Gate
run: python -m agent.gate report.sarif --fail-on P0
3. Xuất chuẩn SARIF để comment inline
SARIF là định dạng chuẩn để GitHub/GitLab hiển thị cảnh báo ngay trên dòng code trong tab Files changed. Map finding của agent sang SARIF:
def to_sarif(findings):
return {"version": "2.1.0", "runs": [{
"tool": {"driver": {"name": "SAST-LLM-Agent"}},
"results": [{
"ruleId": f["type"],
"level": "error" if f["priority"] >= 250 else "warning",
"message": {"text": f'{f["why"]}\nFix: {f["fix"]}'},
"locations": [{"physicalLocation": {
"artifactLocation": {"uri": f["file"]},
"region": {"startLine": f["line"]}}}]
} for f in findings]}]}
4. Cổng chặn (gate) hợp lý
Đừng chặn merge vì mọi cảnh báo — sẽ gây phẫn nộ. Chính sách nên là:
- P0 (CONFIRMED, confidence cao) → chặn merge (fail CI).
- P1/P2 → comment cảnh báo, KHÔNG chặn.
- UNCERTAIN → comment nhẹ "cần review thủ công", không chặn.
def gate(findings):
blockers = [f for f in findings
if f["priority"] >= 250 and f["verdict"] == "CONFIRMED"]
if blockers:
print(f"BLOCKED: {len(blockers)} lỗ hổng P0")
sys.exit(1)
5. Chống nhiễu lặp lại (dedup + baseline)
Đừng báo lại lỗi cũ ở mỗi lần push. Lưu fingerprint (hash của file+dòng+loại) và bỏ qua cái đã được đánh dấu "đã xử lý" hoặc "chấp nhận rủi ro":
fp = hashlib.sha1(f"{f['file']}:{f['type']}:{f['snippet']}".encode())
if fp.hexdigest() in accepted_baseline:
continue # bỏ qua, đã được duyệt trước đó
6. Kiểm soát chi phí và thời gian
- Chỉ quét file mã nguồn (bỏ test, vendor, node_modules).
- Gộp finding theo file trước khi phản biện để giảm số lời gọi.
- Đặt timeout và ngân sách token tối đa mỗi PR.
- Cache kết quả theo hash nội dung file — file không đổi thì không quét lại.
Các bước triển khai
- Thêm job CI chạy trên
pull_request. - Lấy diff với ngữ cảnh, lọc theo đuôi file.
- Chạy finder → verifier → rank.
- Xuất SARIF, upload để hiện inline.
- Gate chỉ chặn P0 CONFIRMED.
- Áp baseline để bỏ lỗi đã duyệt.
- Đặt trần token + timeout.
Checklist tích hợp
- [ ] Chỉ quét diff, không toàn repo?
- [ ] Xuất SARIF để comment inline?
- [ ] Gate chỉ chặn P0 CONFIRMED?
- [ ] Có baseline/dedup chống lặp?
- [ ] Secret key qua CI secrets, không hardcode?
- [ ] Có trần token và timeout?
Sai lầm thường gặp
- Chặn merge vì mọi cảnh báo: dev nổi loạn, tắt tool.
- Quét toàn repo mỗi PR: chậm, tốn tiền, đầy nhiễu code cũ.
- Không dedup: cùng một lỗi báo lại 10 lần qua các commit.
- Không giới hạn token: một PR khổng lồ đốt sạch ngân sách.
- Log secret vào output CI: rò key API.