Ẩn dữ liệu nhạy cảm trong commit Git: Công cụ và quy trình đúng

Ẩ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ử.

Giao diện dòng lệnh hiển thị lệnh cURL xử lý yêu cầu mạng, ví dụ về việc xử lý khoá bí mật trên terminal

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:

  1. Dùng file biến môi trường. Commit .env.example chứa danh sách biến rỗng, còn .env thật nằm ngoài Git.
  2. Bật hook chặn lỗi. Cài pre-commit hook để quét trước mỗi lần commit, thay vì phát hiện sau khi đã push.
  3. 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.
  4. 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”.
  5. 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.
Sơ đồ vòng đời của tệp trong Git từ vùng làm việc qua staging area rồi tới kho lịch sử

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.

Cấu trúc nội bộ đối tượng Git gồm blob, tree và commit, nơi mọi nội dung tệp được lưu vĩnh viễn trong lịch sử

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.

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

TOTP là gì? Xác thực hai lớp bảo vệ tài khoản an toàn hơn SMS

TOTP (Time-based One-Time Password) là thuật toán sinh mật khẩu dùng một lần dựa trên thời gian hiện tại, được chuẩn hoá thành RFC 6238 của IETF. Đây là xương…

Xem thêm

HAProxy là gì: Cân bằng tải và reverse proxy cho hệ thống

HAProxy là gì: Cân bằng tải và reverse proxy cho hệ thống HAProxy là phần mềm cân bằng tải và reverse proxy mã nguồn mở chạy ở tầng TCP và…

Xem thêm
Man hinh tmux voi nhieu khung chia va cua so

tmux là gì: Quản lý phiên terminal cho lập trình viên

tmux là gì? tmux là trình quản lý phiên terminal mã nguồn mở, cho phép bạn tạo nhiều cửa sổ và khung chia nhỏ trong cùng một terminal, đồng thời…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất