3.13 — Mini case study
Sự cố Git tốn kém nhất và cũng dễ xảy ra nhất: một file appsettings.json chứa chuỗi kết nối database production bị commit và push lên một repository public. Điều quan trọng nhất phải hiểu ngay: xoá commit không đủ. Kể từ giây phút nó được push, phải coi như bí mật đã bị lộ — GitHub đã lưu vào nhật ký sự kiện, các dịch vụ quét bí mật tự động đã đọc được, và bất kỳ ai fork hay clone đều có bản sao. Thứ tự xử lý quyết định mức thiệt hại: đổi mật khẩu trước, dọn lịch sử sau. Làm ngược lại là dành 30 phút viết lại lịch sử trong khi mật khẩu vẫn đang bị dùng.
Mục tiêu bài học
Sau bài này bạn có thể:
- Xử lý sự cố lộ bí mật theo đúng thứ tự ưu tiên.
- Hiểu vì sao xoá commit không xoá được bí mật.
- Viết lại lịch sử Git một cách an toàn.
- Ngăn sự cố lặp lại bằng ba lớp phòng thủ.
Nội dung bài học
3.13.1 — Tình huống
14:32 Lập trình viên mới commit "chore: add environment config"
14:33 git push origin main
14:35 Nhận email từ GitHub: "Possible valid secrets found in your repository"
14:36 Kiểm tra: appsettings.json chứa chuỗi kết nối SQL Server production
kèm tài khoản sa và mật khẩu
Repository là public. Bí mật đã ở trên mạng 3 phút.
Phản ứng đầu tiên của hầu hết mọi người — và là phản ứng sai:
# SAI — làm việc này đầu tiên
git rm --cached appsettings.json
git commit -m "chore: remove config file"
git push
Việc này mất 2 phút và không bảo vệ được gì: commit cũ vẫn nằm trong lịch sử, và mật khẩu vẫn dùng được.
3.13.2 — Thứ tự đúng
Bước 1 (ngay lập tức) — đổi mật khẩu.
ALTER LOGIN sa WITH PASSWORD = 'mat-khau-moi-rat-manh';
Đây là việc duy nhất thật sự chặn được thiệt hại. Mọi thứ khác chỉ là dọn dẹp.
Nếu bí mật là khoá API, thu hồi khoá đó ở nhà cung cấp. Nếu là khoá ký JWT, đổi khoá — chấp nhận việc mọi người dùng bị đăng xuất, vì đó rẻ hơn nhiều so với để khoá bị lộ.
Bước 2 — kiểm tra xem đã bị dùng chưa.
-- SQL Server: xem các kết nối gần đây
SELECT login_name, host_name, program_name, login_time
FROM sys.dm_exec_sessions
WHERE login_name = 'sa'
ORDER BY login_time DESC;
Tìm kết nối từ địa chỉ IP lạ. Nếu có, phạm vi sự cố rộng hơn nhiều so với "lộ mật khẩu" — phải kiểm tra dữ liệu đã bị đọc hay sửa gì.
Bước 3 — xoá file khỏi lịch sử.
# git-filter-repo — công cụ được khuyến nghị hiện nay
pip install git-filter-repo
git filter-repo --path appsettings.json --invert-paths
git push origin --force --all
git push origin --force --tags
git filter-repo thay thế git filter-branch cũ — nhanh hơn rất nhiều và ít cạm bẫy hơn.
Cảnh báo: đây là viết lại lịch sử. Mọi người trong đội phải clone lại, và các nhánh đang mở sẽ gặp vấn đề. Thông báo cho cả đội trước khi chạy.
Bước 4 — liên hệ GitHub. Ngay cả sau force push, commit cũ vẫn truy cập được qua URL trực tiếp trong một thời gian. Mở ticket với GitHub Support để yêu cầu xoá cache.
Bước 5 — kiểm tra fork. Nếu repository đã bị fork, các fork đó vẫn giữ lịch sử cũ và bạn không xoá được chúng. Đây là lý do bước 1 quan trọng hơn mọi bước còn lại.
3.13.3 — Vì sao xoá commit không đủ
14:33 Push commit chứa bí mật
14:33 GitHub ghi vào event log (công khai qua API)
14:33 Các dịch vụ quét bí mật tự động đọc thấy
14:34 Bot thử kết nối bằng thông tin vừa lấy được
14:35 Bạn nhận cảnh báo
14:40 Bạn xoá commit <- MUỘN 7 phút
Các bot quét GitHub public hoạt động liên tục và phản ứng trong vòng vài giây. Khoảng thời gian từ lúc push tới lúc bạn phát hiện, dù chỉ vài phút, là quá đủ.
Kể cả với repository private, vẫn phải đổi mật khẩu: bất kỳ ai có quyền đọc repo đều thấy được, kể cả người đã rời công ty nhưng quyền chưa bị thu hồi.
Quy tắc: bí mật đã vào Git là bí mật đã lộ. Không có ngoại lệ.
3.13.4 — Ba lớp phòng thủ
Lớp 1 — .gitignore:
appsettings.Development.json
appsettings.Production.json
appsettings.Local.json
*.env
.env.*
secrets.json
*.pfx
*.key
Chỉ commit appsettings.json chứa giá trị mặc định vô hại, không chứa bí mật nào.
Lớp 2 — pre-commit hook chặn tại máy:
# .git/hooks/pre-commit
#!/bin/bash
if git diff --cached | grep -qiE "(password|secret|api[_-]?key|connectionstring)\s*[:=]\s*['\"][^'\"]{8,}"; then
echo "CẢNH BÁO: có thể chứa bí mật trong thay đổi này"
echo "Kiểm tra lại, hoặc dùng --no-verify nếu chắc chắn an toàn"
exit 1
fi
Dùng Gitleaks sẽ tốt hơn hook tự viết — nó có sẵn hàng trăm mẫu nhận dạng cho khoá của các dịch vụ phổ biến.
Lớp 3 — quản lý bí mật đúng cách:
# Môi trường phát triển: user secrets — lưu NGOÀI thư mục dự án
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;..."
// Production: biến môi trường hoặc vault
var connectionString = builder.Configuration.GetConnectionString("Default");
// Đọc từ biến môi trường ConnectionStrings__Default
Với user-secrets, bí mật nằm ở thư mục người dùng — không thể vô tình commit được, vì nó không nằm trong cây thư mục dự án (bài 15.5).
3.13.5 — Hai sự cố Git khác đáng biết
Mất commit sau git reset --hard:
# Tưởng đã mất, nhưng reflog còn giữ
git reflog
# a3f8b2c HEAD@{0}: reset: moving to HEAD~3
# 7c1d9e4 HEAD@{1}: commit: feat: add revenue report <- commit "đã mất"
git reset --hard 7c1d9e4 # khôi phục
git reflog ghi lại mọi lần HEAD di chuyển và giữ khoảng 90 ngày. Gần như mọi thứ "mất" trong Git đều tìm lại được ở đây.
Commit nhầm vào main:
git branch feature/revenue-report # tạo nhánh tại commit hiện tại
git reset --hard origin/main # đưa main về trạng thái trên remote
git checkout feature/revenue-report # tiếp tục làm trên nhánh đúng
Thứ tự quan trọng: tạo nhánh trước, reset sau. Ngược lại là mất công việc thật.
3.13.6 — Rà lại code của bạn
Danh sách rà soát an toàn Git
- •File cấu hình chứa bí mật nằm trong .gitignore.
- •Môi trường phát triển dùng user-secrets, không sửa trực tiếp appsettings.
- •Production đọc bí mật từ biến môi trường hoặc vault.
- •Có công cụ quét bí mật chạy trước khi commit.
- •CI có bước quét bí mật trong toàn bộ lịch sử.
- •Đội biết quy trình xử lý khi lộ bí mật: đổi mật khẩu trước.
- •Biết git reflog tồn tại và dùng được để khôi phục.
- •Hiểu rằng bí mật đã vào Git là bí mật đã lộ, kể cả repo private.