
Ẩn dữ liệu nhạy cảm trong commit Git là một trong những sai lầm phổ biến nhất của lập trình viên: đẩy API key, mật khẩu hoặc token lên kho mã nguồn rồi tưởng xoá đi là xong. Thực tế dữ liệu vẫn nằm trong lịch sử commit và được mirror lên nhiều nơi. Bài viết này hướng dẫn quy trình phòng ngừa đúng, cách phát hiện và khắc phục khi bí mật đã lọt vào lịch sử.

Vì sao xoá file không đủ
Git lưu mọi phiên bản của mọi file trong lịch sử. Khi bạn chạy git rm secrets.env rồi commit, nội dung mật khẩu vẫn nằm nguyên trong blob của commit cũ. Ai cũng có thể xem lại bằng git log -p hoặc khôi phục bằng git checkout <commit> -- path.
Tệ hơn nữa, kho mã nguồn đã push lên GitHub, GitLab hay Bitbucket thường được fork, clone và mirror tự động. Ngay cả khi bạn dùng git filter-repo để viết lại lịch sử, các bản sao đã tải về từ trước vẫn giữ bí mật. Các bot quét kho công khai tìm chuỗi trông giống khoá API trong vòng vài phút, và khoá AWS bị lộ thường bị khai thác trong giờ đầu tiên.
Phòng ngừa: quy trình đúng từ đầu
Chiến lược duy nhất hiệu quả là không bao giờ cho bí mật vào commit. Các bước nên làm ngay từ dự án mới:
- Dùng file biến môi trường. Commit
.env.examplechứa danh sách biến rỗng, còn.envthật nằm ngoài Git. - Bật hook chặn lỗi. Cài
pre-commithook để quét trước mỗi lần commit, thay vì phát hiện sau khi đã push. - Không dùng biến môi trường cho tất cả bí mật. Biến môi trường dễ lộ qua log, lỗi stack trace và snapshot CI. Vault hoặc secret manager an toàn hơn.
- Tách nhánh công khai khỏi nhánh nội bộ. Một repo công khai không nên chứa cả mã nội bộ chỉ vì “tạm thời”.
- Sinh khoá tự động trong CI. Mỗi lần deploy sinh khoá mới, hạn dụng ngắn, tự thu hồi.

Công cụ phát hiện bí mật
Các công cụ dò quét mẫu bí mật phổ biến trong lịch sử kho mã, đều dùng biểu thức chính quy để khớp chuỗi đặc trưng:
| Công cụ | Cách dùng | Điểm mạnh |
|---|---|---|
| truffleHog | Quét lịch sử Git và nhiều nguồn khác | Kiểm tra khả năng hoạt động thực tế của khoá tìm được |
| Gitleaks | Quét nhanh, viết bằng Go | Chạy tốt trong CI, có sẵn hơn 150 mẫu |
| detect-secrets | Hook pre-commit của Yelp | Sinh file allowlist để quản lý ngoại lệ |
| git-secrets (AWS) | Chặn mẫu khoá của AWS | Nhẹ, tích hợp tốt cho pipeline |
Cài đặt nhanh Gitleaks qua hook pre-commit:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.4
hooks:
- id: gitleaks
Quét toàn bộ lịch sử kho hiện có:
gitleaks detect --source . --verbose
Với pre-commit, chỉ dò thay đổi chưa commit nên chạy rất nhanh. Trong pipeline CI, nên chạy thêm một lần quét toàn bộ lịch sử để bắt các bí mật đã tồn tại từ trước khi cấu hình hook có hiệu lực.
Quy trình khi bí mật đã lệch vào lịch sử
Thứ tự ưu tiên tuyệt đối là thu hồi khoá trước, dọn lịch sử sau. Viết lại lịch sử không làm vô hiệu hoá một khoá đã bị lấy. Các bước:
Bước 1. Thu hồi ngay lập tức. Vô hiệu hoá khoá trên dashboard của nhà cung cấp, tạo khoá mới, kiểm tra log truy cập để xem có dấu hiệu lạm dụng không. Với AWS, chạy aws iam delete-access-key và kiểm tra CloudTrail. Với GitHub token, dùng API thu hồi token.
Bước 2. Xoá khỏi lịch sử. Sao lưu repo trước, rồi dùng git filter-repo:
git clone --mirror https://github.com/user/repo.git repo-clean
cd repo-clean
git filter-repo --path secrets.env --invert-paths
git remote add origin https://github.com/user/repo.git
git push --force --mirror
Thay vì xoá theo đường dẫn, có thể thay thế nội dung cụ thể trong mọi commit bằng --replace-text:
cat > replacements.txt <<'EOF'
literal:AKIAIOSFODNN7EXAMPLE==>REMOVED
EOF
git filter-repo --replace-text replacements.txt
Bước 3. Bắt buộc cập nhật mọi clone. Thông báo tới đồng độp chạy git fetch && git reset --hard origin/main vì các bản sao cũ của họ vẫn giữ bí mật. Nếu dùng fork, cần liên hệ chủ sở hữu fork để họ đồng bộ lại.
Bước 4. Thêm hook chặn tái phạm. Cấu hình Gitleaks hoặc tương tự như ở phần trên, để lỗi tương lai bị chặn ở bước commit.

File gitignore cần ưu tiên gì
Không phải file nào cũng nên thêm vào .gitignore. Nguyên tắc là chỉ ignore thứ chứa bí mật hoặc thứ sinh ra được, không ignore thứ cần người khác tải về để build dự án:
# Biến môi trường và khoá
.env
.env.*
!.env.example
*.pem
*.key
id_rsa
secrets/
*.p12
credentials.json
# Kết quả build
node_modules/
dist/
build/
__pycache__/
*.pyc
.venv/
# Log chứa dữ liệu
*.log
logs/
Lưu ý dòng !.env.example: trong Git, thứ tự quy tắc có ý nghĩa, dòng phủ định sau cùng cho phép file mẫu được theo dõi dù .env.* đã bị ignore. Nếu thiếu dòng này, file mẫu cũng bị ẩn và người khác không biết cần đặt biến gì.
Thêm vào .gitignore chỉ có tác dụng với file chưa được track. Với file đã lỡ commit, phải chạy git rm --cached ten-file để gỡ khỏi chỉ mục rồi mới ignore có hiệu lực.
Dùng biến môi trường hay secret manager
| Phương án | Phù hợp với | Rủi ro còn lại |
|---|---|---|
| File .env ngoài Git | Dự án cá nhân, máy local | Bị commit nhầm, lộ qua log |
| Biến môi trường trên CI | Nhóm nhỏ, dự án vừa | Log debug in ra giá trị |
| Vault, Doppler, AWS Secrets Manager | Dự án lớn, nhiều môi trường | Chi phí vận hành, cấu hình phức tạp |
| Sinh khoá tự động | Không đặt được mật khẩu tĩnh | Không dùng được với hệ thống bên thứ ba cũ |
Điểm cần nhấn mạnh: biến môi trường chỉ giải quyết một phần. Nếu code in log toàn bộ os.environ khi khởi động, bí mật vẫn nằm trong log và log thường lưu lâu hơn, rộng hơn repo. Luôn chỉ log tên biến chứ không log giá trị.
Danh sách tự kiểm tra
- Kho mã công khai chưa từng chứa khoá thật, kể cả khoá đã vô hiệu hoá.
- Mọi bí mật đều lấy từ secret manager hoặc biến môi trường, không hardcode.
- Hook pre-commit chạy dò quét bí mật và đã thử nghiệm chặn đúng.
- Pipeline CI có một bước quét lịch sử đầy đủ, không chỉ quét thay đổi hiện tại.
- Có quy trình thu hồi khoá khẩn cấp và ai chịu trách nhiệm thực hiện.
- Log ứng dụng không in giá trị biến môi trường.
- Khoá được đặt hạn dụng ngắn và luân chuyển định kỳ.
Kết luận
Bí mật lọt vào Git không phải sự cố có thể sửa sau, vì lịch sử được sao chép ở nhiều nơi và mọi bản sao đều nằm ngoài tầm kiểm soát. Cách xử lý duy nhất là thu hồi khoá ngay lập tức, viết lại lịch sử, và bắt buộc cập nhật mọi bản sao. Nhưng thao tác tốn công nhất vẫn là ngăn lần thứ hai, nên hãy thiết lập hook dò quét bí mật ngay từ commit đầu tiên của dự án. Tham khảo tài liệu Gitleaks, truffleHog và hướng dẫn viết lại lịch sử Git.
