---
id: case-secret
title: "Secret đã lên Git"
shortTitle: "Secret lộ"
track: cases
trackTitle: "Tình huống vận hành"
order: 4
level: core
status: published
estimatedMinutes: 30
prerequisites:
  - git-recovery
next: case-stale
reviewedAgainst: 2026-10-12
sources:
  - title: "Removing sensitive data from a repository"
    url: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository
  - title: "git-filter-repo"
    url: https://github.com/newren/git-filter-repo
---

# Secret đã lên Git

Một secret đã nằm trong commit thì coi như đã lộ: lộ với mọi người từng fetch repo, với các fork, với cache của CI, và với bản backup của host Git. Thêm một commit mới để xóa nó không đóng được cửa. Việc đầu tiên là thu hồi secret ngay tại hệ thống đã cấp nó.

## Để làm gì

Bạn cần vô hiệu hóa credential đang dùng được trước, rồi mới dọn history. Bạn cũng cần giải thích được vì sao một commit "xóa file env" là chưa đủ.

## Gồm những gì

- Thứ tự xử lý: thu hồi, cấp secret mới cho app, rồi lọc history.
- Dùng `git log -S` để tìm một chuỗi trong lịch sử local.
- Giới hạn của việc rewrite history: người khác vẫn giữ bản cũ cho tới khi họ xóa clone.

Lab không dùng secret thật. Bạn sẽ dùng chuỗi `lab-token-not-real`.

## Dùng khi nào

Ai đó commit nhầm `.env`, PAT hoặc connection string, hoặc secret scanner của GitHub gửi cảnh báo. Thời gian lộ được tính từ lúc push, không phải từ lúc bạn đọc tin nhắn.

Đừng mở đầu bằng `git filter-repo` trên repo công ty khi app production vẫn đang dùng chính secret đó. Dọn Git không làm key cũ hết hiệu lực: bạn có thể mất cả buổi rewrite history, trong khi attacker vẫn dùng key cũ cho tới khi nó tự hết hạn.

## Kiến thức cốt lõi

Tài liệu của GitHub nói rõ: xóa file trong một commit mới không xóa nó khỏi history, và secret vẫn còn trong fork cũng như cache. Cách xử lý đúng là coi secret đã bị công khai. Vào hệ thống đã cấp secret để revoke: PAT thì xóa token, access key thì vô hiệu hóa, password database thì đổi. Sau đó cập nhật nơi app thật sự đọc secret, thường là secret store chứ không phải một file trong repo.

Thứ tự ba bước, và lý do của từng bước:

1. **Revoke secret cũ.** Bước này đứng đầu vì nó là bước duy nhất chặn được attacker. Mọi việc dọn Git đều không làm key cũ hết hiệu lực.
2. **Cấp secret mới cho app đang chạy.** Bước này phải xong trước khi đụng tới history. Nếu bạn force-push history trước, app có thể không khởi động được, mà key cũ vẫn chưa chết nếu bạn quên revoke.
3. **Lọc history.** `git filter-repo` viết lại commit, nên mọi người phải clone lại hoặc reset về history mới. Force-push lên nhánh dùng chung là việc của người có quyền, và phải báo trước cho team. Lệnh cụ thể đã có ở bài recovery; bài này chỉ nhấn mạnh thứ tự.

Bạn xác nhận sự cố đã đóng khi: gọi API bằng secret cũ bị từ chối, app chạy bình thường với secret mới, và `git log -S` trên một bản clone mới không còn thấy chuỗi đó.

`git log -S 'lab-token-not-real'` tìm những commit đã thêm hoặc bớt chuỗi đó. Lệnh này không tìm trong file chưa commit.

## Lệnh và code mẫu

Lab local, không push:

```bash
mkdir -p ~/devops-lab/secret-case && cd ~/devops-lab/secret-case
git init -b main
printf 'token=lab-token-not-real\n' > .env
git add .env && git commit -m "oops"
printf 'token=\n' > .env
git add .env && git commit -m "xoa token"
git log -S 'lab-token-not-real' --oneline
```

`git log -S` vẫn tìm thấy commit đầu tiên. File ở HEAD không còn token, nhưng history vẫn còn.

Xóa thư mục lab khi làm xong, và không đẩy chuỗi này lên remote.

## Thực hành

1. Chạy lab trên. Xác nhận `git log -S` vẫn trả về commit dù file đã được "xóa".
2. Viết ra giấy thứ tự ba bước: revoke, cấp secret mới cho app đang chạy, lọc history.
3. Trong lúc học, không revoke token thật nào và không chạy filter-repo trên repo mà người khác đang dùng.

## Kết quả mong đợi

`git log -S` in ra commit `oops`. Bạn không coi commit "xoa token" là đã xử lý xong.

## Checklist

- [ ] Revoke secret ở hệ thống cấp nó trước khi sửa Git.
- [ ] App đang chạy đã nhận secret mới từ secret store.
- [ ] Gọi thử bằng secret cũ và xác nhận bị từ chối.
- [ ] History đã được viết lại và team đã được báo clone lại.
- [ ] Secret scanner hoặc hook chỉ là lưới an toàn phía sau, không thay được việc revoke.

## Lỗi thường gặp

**Force-push một commit xóa file.** Commit cũ vẫn còn trên remote cho tới khi history thật sự được viết lại và server dọn reflog. Theo tài liệu xóa dữ liệu nhạy cảm, GitHub vẫn giữ các bản sao. Attacker nào đã clone thì vẫn có key. Chỉ revoke mới đóng được cửa.

**Rotate password database nhưng quên replica và worker.** Web hết lỗi, nhưng job nền vẫn dùng password cũ, và password cũ vẫn là một cửa mở. Hãy cập nhật mọi nơi đang giữ bản sao, rồi mới revoke bản cũ.

## Nguồn

1. [Removing sensitive data](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository) — history, fork, và vì sao phải coi secret đã lộ.
2. [git-filter-repo](https://github.com/newren/git-filter-repo) — công cụ viết lại history.

## Tiếp theo

[Deploy xanh, user thấy bản cũ](./05-stale.md)
