Nút “Don’t Ask Again” (Không hỏi lại) của Claude Code lưu chính xác lệnh bạn đã phê duyệt thành quy tắc allow trong tệp .claude/settings.local.json. Nếu lệnh đó chứa mật khẩu hoặc khóa API, bí mật giờ đây nằm dưới dạng văn bản thuần trong thư mục dự án của bạn, và nếu tệp bị commit, nó sẽ thành công khai. Công ty bảo mật Apiiro đã tìm thấy 1.122 tệp như vậy trên GitHub công khai chứa 1.379 bí mật thực sự. Dưới đây là cách kiểm tra các repo của bạn bằng một lệnh duy nhất, dọn dẹp và ngăn chặn việc này tái diễn.
Chuyện gì đã xảy ra: những con số trong nghiên cứu của Apiiro
Apiiro đã quét GitHub công khai để tìm các bản sao của .claude/settings.local.json — tệp theo từng dự án nơi Claude Code lưu các phê duyệt quyền cá nhân của bạn. Họ tìm thấy 104.185 bản sao trên 103.617 kho lưu trữ. Trong đó, 1.122 tệp có ít nhất một bí mật thực sự, tổng cộng 1.379 bí mật khác nhau, theo bài viết của iTnews về nghiên cứu này.
| Loại bí mật | Số lượng |
|---|---|
| Mật khẩu cơ sở dữ liệu | 379 |
| Khóa service-role của Supabase | 122 |
| Đăng nhập SSH cho máy chủ công khai | 43 |
| Token GitHub, GitLab và npm | 42 |
| Khóa AWS | 23 |
Khi Apiiro mở rộng tìm kiếm sang các phiên bản cũ của tệp trong lịch sử git, tệp dùng chung .claude/settings.json và các bản ghi hội thoại của agent đã lưu, con số tăng lên 2.539 bí mật trên 1.994 kho lưu trữ. Apiiro không kiểm tra xem các thông tin xác thực này còn hoạt động hay không, nhưng một tổ chức được họ thông báo đã xác nhận một khóa vẫn còn hiệu lực.
Con số từ máy quét là phần đáng lo ngại nhất. Trong số 1.122 tệp có bí mật thực sự, TruffleHog phát hiện 643 và gitleaks phát hiện 564. Có 215 tệp lọt qua cả hai công cụ. Vì vậy, “chúng tôi có chạy máy quét bí mật” không phải là bằng chứng cho thấy bạn sạch sẽ.
Câu chuyện này đã leo lên top đầu của Hacker News với 582 điểm. Boris Cherny, người phụ trách Claude Code tại Anthropic, đã phản hồi trong thread rằng Claude Code từ trước đến nay chưa từng làm sạch bí mật và đội ngũ đang nghiên cứu vấn đề này. Đọc nghiên cứu gốc của Apiiro.
“Don’t Ask Again” đưa bí mật vào tệp như thế nào
Khi Claude Code muốn chạy một lệnh Bash, nó sẽ hỏi trước. Nếu chọn “Yes, and don’t ask again”, Claude Code sẽ ghi một phê duyệt cố định vào .claude/settings.local.json dưới dạng quy tắc allow, như tài liệu cài đặt của Anthropic mô tả. Quy tắc này ghi lại toàn bộ nội dung lệnh. Nếu lệnh là thế này:
psql "postgresql://admin:Sup3rS3cret@db.example.com:5432/prod" -c "select count(*) from users"
thì tệp cài đặt của bạn giờ sẽ chứa đại khái như thế này:
{
"permissions": {
"allow": [
"Bash(psql \"postgresql://admin:Sup3rS3cret@db.example.com:5432/prod\" -c \"select count(*) from users\")"
]
}
}
Mật khẩu nằm trong quy tắc vì nó có trong lệnh. Claude Code lưu lại đúng những gì bạn phê duyệt, từng chữ một.

Vì sao nó lọt lên GitHub
Claude Code cố gắng giữ tệp này ngoài git. Lần đầu tiên nó ghi .claude/settings.local.json vào một kho lưu trữ chưa bỏ qua tệp này, nó sẽ thêm **/.claude/settings.local.json vào tệp git excludes toàn cục của bạn. Nhưng cơ chế bảo vệ này có nhiều lỗ hổng:
- Bạn tự tạo tệp bằng tay. Tài liệu nói rõ: nếu bạn tự tạo tệp, bạn phải tự thêm nó vào
.gitignore. Theo cách đọc của Cherny trên HN, các bản sao bị rò rỉ được tạo thủ công rồi mới được commit. - Quy tắc ignore chỉ nằm trên một máy. Nó nằm trong excludes toàn cục của bạn, không phải trong
.gitignorecủa repo. Đồng nghiệp, một job CI hoặc chiếc laptop mới sao chép thư mục sẽ không được thừa kế quy tắc này. - Tệp bị thêm cưỡng bức hoặc đã được theo dõi trước khi quy tắc tồn tại.
git add -fghi đè lên các quy tắc ignore, và một khi tệp đã được theo dõi, các quy tắc ignore không còn áp dụng cho nó nữa. - Tệp dùng chung. Trong extension VS Code, thẻ phê duyệt cho phép bạn chọn tệp đích, bao gồm cả
.claude/settings.jsonđược commit. Quy tắc lưu ở đó sẽ đến với tất cả mọi người.
Kiểm tra các repo của bạn bằng một lệnh
Bắt đầu với repo bạn đang ở. Các lệnh sau cho biết tệp có đang được theo dõi hay không và liệu nó đã từng được commit chưa:
git ls-files | grep -E '(^|/)\.claude/settings(\.local)?\.json$'
git log --all --name-only --format= | grep -E '\.claude/settings(\.local)?\.json$' | sort -u
Nếu dòng đầu tiên in ra kết quả nào đó, tệp đang nằm trong cây thư mục hiện tại của bạn. Nếu dòng thứ hai in ra kết quả nào đó, tệp nằm trong lịch sử của bạn, kể cả khi bạn đã xóa nó sau đó.
Để quét tất cả các repo trong một thư mục cùng lúc:
find ~/code -path '*/.claude/settings.local.json' -exec sh -c \
'cd "$(dirname "$1")/.." && git ls-files --error-unmatch .claude/settings.local.json >/dev/null 2>&1 && echo "TRACKED: $PWD"' _ {} \;
Với một tổ chức GitHub, GitHub CLI có thể tìm kiếm mã theo tên tệp:
gh search code permissions --owner your-org --filename settings.local.json
Sau đó xem nội dung bên trong bất kỳ tệp nào bạn tìm thấy. Vì các máy quét đã bỏ sót 19% tệp bị rò rỉ, hãy tự đọc các quy tắc thay vì tin vào kết quả quét màu xanh:
grep -nE 'Bash\(' .claude/settings.local.json | grep -iE 'pass|token|key|secret|://[^/]*:[^@]*@'
Một chuỗi kết nối có dạng user:password@, một header Authorization: Bearer, -p theo sau là mật khẩu dạng văn bản, hoặc bất kỳ thứ gì bắt đầu bằng sk-, ghp_, AKIA hay eyJ đều là thông tin xác thực. Chạy gitleaks trên repo như một bước kiểm tra bổ sung, chứ không phải bước duy nhất.
Cách khắc phục tệp cài đặt bị rò rỉ
Thứ tự thực hiện rất quan trọng. Việc xoay vòng (rotate) khóa phải đến trước, vì xóa tệp không có tác dụng gì với bản sao đã nằm trong forks, clones và bộ nhớ đệm.
- Xoay vòng mọi thông tin xác thực mà tệp từng chứa. Kiểm tra toàn bộ lịch sử của tệp bằng
git log -p --all -- .claude/settings.local.json, liệt kê mọi bí mật trong đó, và thu hồi từng cái tại nhà cung cấp. Hướng dẫn của Apiiro cũng đặt bước này lên đầu. - Ngừng theo dõi tệp và bỏ qua nó ngay trong repo:
git rm --cached .claude/settings.local.json
echo '.claude/settings.local.json' >> .gitignore
git add .gitignore && git commit -m "Stop tracking Claude Code local settings"
git check-ignore -v .claude/settings.local.json
Lệnh cuối cùng nên in ra dòng tương ứng trong .gitignore. Quy tắc nằm trong repo sẽ bảo vệ mọi bản clone.
- Dọn dẹp lịch sử nếu repo là công khai hoặc được chia sẻ rộng rãi. git-filter-repo xóa tệp khỏi mọi commit:
git filter-repo --invert-paths --path .claude/settings.local.json
git push --force --all
Việc này viết lại lịch sử cho tất cả mọi người, nên hãy cảnh báo đội ngũ của bạn trước. Nó cũng không chạm đến được các fork, vì vậy đây chỉ là dọn dẹp, không thay thế được bước 1.
- Dọn dẹp cả tệp cục bộ. Mở
.claude/settings.local.json, xóa mọi quy tắc chứa bí mật dạng văn bản, và thay thế bằng một mẫu (pattern) (xem phần tiếp theo). - Bật push protection. GitHub secret scanning push protection chặn các lượt push chứa định dạng bí mật đã được nhận diện. Nó không bắt được tất cả, như 215 tệp bị bỏ sót cho thấy, nhưng nó chặn được các định dạng token phổ biến.
Ngăn chặn tái diễn
Cách khắc phục rất đơn giản để nói: không bao giờ đặt bí mật dạng văn bản vào lệnh mà agent của bạn chạy, và phê duyệt các mẫu thay vì lệnh chính xác.

Tham chiếu đến bí mật, đừng gõ chúng ra. Shell sẽ mở rộng biến tại thời điểm chạy, nhưng quy tắc allow lưu lại văn bản như được viết. Vì vậy psql "$DATABASE_URL" sẽ lưu $DATABASE_URL trong quy tắc, chứ không phải mật khẩu:
{
"permissions": {
"allow": ["Bash(psql \"$DATABASE_URL\" *)", "Bash(npm run test *)"],
"deny": ["Read(./.env)", "Read(./.env.*)"]
}
}
Các quy tắc deny được lấy thẳng từ tài liệu cài đặt của Anthropic; chúng ngăn Claude Code đọc các tệp .env của bạn, nên khóa cũng không thể được dán từ đó vào một lệnh nào khác.
Giữ các quy tắc hẹp. Những người bình luận trên HN trích dẫn nghiên cứu cho hay 5% các tệp cho phép mọi lệnh shell và 43% có các quy tắc interpreter như Bash(python:*). Một quy tắc như vậy cho phép agent chạy bất kỳ gì thông qua Python, nên nó thực chất là quy tắc cho phép mọi thứ nhưng phức tạp hơn.
Cân nhắc dùng chế độ auto và sandbox. Cherny khuyên dùng chế độ auto thay vì viết quy tắc quyền bằng tay và cho biết Anthropic đã xây dựng một sandbox mã nguồn mở. Một số người bình luận chỉ ra rằng chế độ auto là một bộ phân loại, không phải ranh giới bảo mật, nên hãy kết hợp nó với các thói quen nêu trên.
Giữ agent tránh xa các bí mật production. Khóa an toàn nhất là khóa mà agent không bao giờ nhìn thấy. Chúng tôi đã đề cập các mô hình cho việc này trong bài cách để AI agent dùng khóa API mà không nhìn thấy chúng, và những gì kẻ tấn công làm với khóa AI bị rò rỉ trong bài khóa API AI bị đánh cắp và LLMjacking.
Để agent lập trình của bạn gọi API mà không cần gõ khóa, với Apidog CLI
Một cách phổ biến khiến khóa rơi vào một lệnh được phê duyệt là một lệnh gọi API nhanh: curl -H "Authorization: Bearer sk-..." để kiểm tra một endpoint. Bạn có thể giao việc đó cho Apidog CLI thay thế. Khóa nằm trong một biến môi trường của Apidog, kịch bản kiểm thử của bạn tham chiếu đến nó qua {{API_KEY}}, và lệnh mà agent của bạn chạy chỉ chứa các ID dự án, kịch bản và môi trường. Không có gì bí mật trong đó để bị lưu lại.
Thiết lập một lần, ngay trên terminal của riêng bạn thay vì thông qua agent, để token truy cập của bạn không bao giờ đi qua một lượt phê duyệt:
npm install -g apidog-cli@latest
apidog auth login
npx -y skills add https://apidog.com # pick Claude Code when prompted
Sau đó đưa cho Claude Code một prompt:
List the environments in Apidog project 123456, then run test scenario 7890
against the staging environment and summarise any failed assertions.
The API key is stored in that environment as {{API_KEY}}. Never pass a key,
token or password on the command line, and never use --env-var for secrets.
Agent sẽ chạy các lệnh như thế này, vốn an toàn để phê duyệt bằng “Don’t Ask Again”:
apidog environment list --project 123456
apidog run -t 7890 --project 123456 -e <envId> -r cli,json
Hai lưu ý thẳng thắn. Thứ nhất, apidog run dùng giá trị dùng chung của biến, được đồng bộ lên Apidog và hiển thị với các thành viên của dự án, vì vậy hãy lưu một khóa kiểm thử có phạm vi hẹp ở đó, chứ không phải khóa root production. Nếu bạn muốn giữ các giá trị trên đĩa, apidog run cũng nhận --variables <path>; hãy trỏ đến một tệp bên ngoài repo. Thứ hai, cờ --env-var "key=value" tồn tại để truyền biến qua dòng lệnh, và đó chính là mô hình gây rò rỉ, nên hãy bảo agent của bạn không dùng nó cho bí mật.
Các nhóm dùng gói Enterprise còn có thể kéo bí mật từ HashiCorp Vault, Azure Key Vault hoặc AWS Secrets Manager với Apidog Vault Secrets, và Enterprise SaaS Secret Scanner đánh dấu các khóa được lưu trong request, script và biến của Apidog. Nó không quét các repo GitHub, nên vẫn hãy duy trì các bước kiểm tra nêu trên. Để xem hướng dẫn thiết lập đầy đủ, tham khảo bài cách dùng Apidog CLI trong Claude Code và quản lý môi trường và biến bí mật trong Apidog. Gói Free của Apidog hỗ trợ tối đa 4 người dùng.
FAQ
Tệp settings.local.json của Claude Code có được gitignore mặc định không?
Một phần. Khi Claude Code tạo tệp này trong một kho git, nó thêm **/.claude/settings.local.json vào git excludes toàn cục của bạn. Điều đó chỉ bảo vệ máy của bạn, và không áp dụng nếu bạn tự tạo tệp bằng tay hoặc thêm nó cưỡng bức. Hãy thêm dòng này vào .gitignore của chính repo nữa.
Việc xóa tệp có loại bỏ khóa bị rò rỉ không?
Không. Khóa vẫn nằm trong lịch sử git, trong các fork và trong mọi bản clone được tạo khi nó còn công khai. Hãy xoay vòng khóa trước, sau đó ngừng theo dõi tệp và dọn dẹp lịch sử bằng git-filter-repo.
gitleaks hoặc TruffleHog có bắt được các rò rỉ này không?
Không đáng tin cậy. Trong mẫu của Apiiro, 215 trên 1.122 tệp có bí mật thực sự bị cả hai công cụ bỏ sót. Hãy chạy máy quét, sau đó tự đọc các quy tắc Bash(...) trong tệp.
Đây là vấn đề của mô hình hay của Claude Code?
Apiiro đã tái hiện hiện tượng này với Opus 5.5 và Fable 5.1 và kết luận đây là đặc tính của Claude Code, không phải của mô hình. Lượt phê duyệt lưu nội dung lệnh, bất kể mô hình nào viết ra lệnh đó.
Làm thế nào để phê duyệt lệnh một cách an toàn trong Claude Code?
Phê duyệt các mẫu như Bash(npm run test *) và tham chiếu bí mật qua biến môi trường như $DATABASE_URL. Thêm các quy tắc deny cho Read(./.env) và Read(./.env.*).
