Dùng AI

Giữ khóa API an toàn: .env, .gitignore, quét bí mật

Khóa API không bao giờ được viết thẳng vào code. Để khóa trong tệp .env, ghi .env vào .gitignore, đọc khóa qua biến môi trường, và quét dự án trước khi commit. GitHub có push protection chặn nhiều loại khóa khi bạn push lên kho công khai. Nếu khóa đã lộ, việc đầu tiên là thu hồi và tạo khóa mới; xóa commit thôi là không đủ.

Ý chính
  • Khóa API giống mật khẩu kèm thẻ thanh toán: ai có khóa là dùng được tài khoản của bạn và bạn trả tiền.
  • Ba lớp phòng: khóa nằm trong tệp .env và đọc qua biến môi trường; .env nằm trong .gitignore; quét dự án trước khi commit, và để push protection của GitHub chặn thêm một lần khi push.
  • Theo GitHub Docs (11/11/2026), push protection cho tài khoản cá nhân bật sẵn và chặn bí mật khi push lên kho công khai; khóa API của Anthropic, OpenAI và token Hugging Face đều nằm trong danh sách mẫu được quét.
  • Lộ khóa thì thu hồi và tạo khóa mới trước, rồi mới tính chuyện dọn lịch sử git.

Bài này dựa trên GitHub Docs về push protection, các mẫu bí mật được quét, gỡ dữ liệu nhạy cảm và tệp bỏ qua, đọc ngày 11/11/2026. Ví dụ dùng khóa API của Claude, nhưng cách làm giống hệt với khóa của các hãng khác.

Vì sao khóa API dễ bị lộ

Khi học AI, bạn sẽ gọi API của mô hình bằng một khóa bí mật. Những đường lộ khóa hay gặp:

  • Viết thẳng khóa vào code "cho nhanh", rồi quên và commit.
  • Commit cả tệp .env vì chạy git add . khi chưa có .gitignore.
  • Notebook in ra biến chứa khóa, và đầu ra của ô đó được lưu cùng tệp .ipynb.
  • Chụp màn hình hay dán log có khóa vào nhóm chat hỏi lỗi.
  • Đưa khóa vào tệp cấu hình của agent AI, và agent đọc hay ghi tệp đó ra chỗ khác.

Kho công khai trên GitHub có thể bị người khác đọc ngay sau khi bạn push. Vì vậy cách an toàn là để khóa không bao giờ đi vào git.

Lớp 1: khóa nằm trong .env, đọc qua biến môi trường

Tạo tệp .env ở thư mục gốc dự án, mỗi dòng một biến:

ANTHROPIC_API_KEY=khóa-của-bạn

Code chỉ đọc tên biến, không bao giờ chứa giá trị. Thư viện anthropic tự đọc biến ANTHROPIC_API_KEY, nên khi biến đã được đặt, bạn chỉ cần tạo client mà không truyền khóa. Đoạn Python dưới đây nạp .env vào biến môi trường mà không cần thư viện ngoài (bạn cũng có thể dùng thư viện python-dotenv), không ghi đè biến đã có, báo lỗi rõ ràng khi thiếu khóa, và chỉ in độ dài khóa chứ không in khóa.

Kèm theo, tạo tệp .env.example chỉ có tên biến, để người khác clone dự án biết cần đặt biến gì:

ANTHROPIC_API_KEY=

Tệp .env.example commit được, tệp .env thì không.

Lớp 2: .gitignore chặn .env

Thêm vào .gitignore ở gốc kho:

.env
.venv/
__pycache__/

Theo GitHub Docs về bỏ qua tệp, .gitignore chỉ có tác dụng với tệp chưa được git theo dõi. Nếu .env đã từng được commit, thêm vào .gitignore không làm nó biến mất khỏi kho. Khi đó phải bỏ theo dõi tệp bằng git rm --cached .env, commit thay đổi, và coi như khóa trong đó đã lộ (xem phần cuối bài).

Giữ khóa API an toàn Khóa không bao giờ nằm trong code 1. .envKhóa ở đâyđọc qua os.environ 2. .gitignoreChặn .envcó .env.example 3. QuétTrước commitpush protection 4. Nếu lộThu hồi khóarồi dọn lịch sử Xóa commit không đủ: khóa đã lộ phải đổi
Giữ khóa API an toàn 1. .envKhóa ở đâyđọc qua os.environ 2. .gitignoreChặn .envcó .env.example 3. QuétTrước commitpush protection 4. Nếu lộThu hồi khóarồi dọn lịch sử Xóa commit không đủ: khóa đã lộ phải đổi
Hình 1. Bốn bước giữ khóa API: khóa nằm trong tệp .env và code đọc qua os.environ; .gitignore chặn .env, dự án có .env.example; quét trước khi commit và để push protection của GitHub chặn thêm khi push; nếu khóa đã lộ thì thu hồi khóa trước rồi mới dọn lịch sử. Xóa commit không đủ, khóa đã lộ phải đổi.

Lớp 3: quét trước khi commit

Đoạn Python thứ hai quét một thư mục dự án, tìm chuỗi trông giống khóa của Anthropic (bắt đầu bằng sk-ant-), OpenAI, Hugging Face (hf_) và token GitHub, và nhắc khi có tệp .env mà .gitignore chưa chặn. Kết quả luôn che bớt khóa, chỉ hiện vài ký tự đầu và độ dài. Nó bỏ qua thư mục .git, môi trường ảo và tệp nhị phân. Chạy với đường dẫn dự án thì trả mã thoát 1 khi thấy điều đáng ngờ, nên có thể gắn vào hook trước commit.

"""Quét thư mục dự án tìm chuỗi trông giống khóa API trước khi commit, và nhắc nếu .env chưa nằm trong .gitignore.

Chạy: python quet_khoa.py duong/dan/du_an     (chỉ đọc, không sửa gì, không gửi đi đâu)
Chạy không tham số: tạo một dự án mẫu trong thư mục tạm rồi quét.
Kết quả luôn che bớt khóa, chỉ hiện vài ký tự đầu. Đây là lưới lọc thô; GitHub còn có push protection quét khi push.
"""
import os
import re
import sys
import tempfile

MAU_KHOA = [
    ("Anthropic", re.compile(r"sk-ant-[A-Za-z0-9_-]{20,}")),
    ("OpenAI", re.compile(r"sk-(?:proj-)?[A-Za-z0-9_-]{20,}")),
    ("Hugging Face", re.compile(r"hf_[A-Za-z0-9]{30,}")),
    ("GitHub", re.compile(r"(?:ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{50,})")),
]
BO_QUA_THU_MUC = {".git", ".venv", "venv", "__pycache__", "node_modules"}


def che(khoa):
    return khoa[:7] + "…(" + str(len(khoa)) + " ký tự)"


def quet_van_ban(van_ban):
    thay = []
    for so_dong, dong in enumerate(van_ban.splitlines(), 1):
        da_thay = set()
        for hang, mau in MAU_KHOA:
            for m in mau.finditer(dong):
                if any(m.start() >= a and m.end() <= b for a, b in da_thay):
                    continue  # khóa Anthropic cũng khớp mẫu sk- của OpenAI: chỉ báo một lần
                da_thay.add((m.start(), m.end()))
                thay.append((so_dong, hang, che(m.group())))
    return thay


def env_bi_bo_qua(goc):
    duong = os.path.join(goc, ".gitignore")
    if not os.path.exists(duong):
        return False
    with open(duong, encoding="utf-8") as f:
        dong = [d.strip() for d in f]
    return any(d in (".env", ".env*", "*.env", "/.env") for d in dong)


def quet_thu_muc(goc):
    ket_qua = []
    for thu_muc, con, tep in os.walk(goc):
        con[:] = sorted(c for c in con if c not in BO_QUA_THU_MUC)
        for ten in sorted(tep):
            duong = os.path.join(thu_muc, ten)
            try:
                with open(duong, encoding="utf-8") as f:
                    van_ban = f.read()
            except (UnicodeDecodeError, OSError):
                continue  # bỏ qua tệp nhị phân
            for so_dong, hang, khoa in quet_van_ban(van_ban):
                ket_qua.append(f"{os.path.relpath(duong, goc)}:{so_dong}  {hang:<12} {khoa}")
    if os.path.exists(os.path.join(goc, ".env")) and not env_bi_bo_qua(goc):
        ket_qua.append(".env có trong thư mục nhưng CHƯA nằm trong .gitignore")
    return ket_qua


def tao_du_an_mau(goc):
    tep = {
        "app.py": 'import os\nkhoa = os.environ["ANTHROPIC_API_KEY"]  # đúng: đọc từ biến môi trường\n',
        "thu_nghiem.py": 'client = Client(api_key="sk-ant-api03-' + "x" * 40 + '")  # sai: khóa viết thẳng\n',
        "notebook_cu.py": 'HF_TOKEN = "hf_' + "A" * 34 + '"\n',
        ".env": "ANTHROPIC_API_KEY=sk-ant-api03-" + "y" * 40 + "\n",
        ".gitignore": "__pycache__/\n.venv/\n",
    }
    for ten, noi_dung in tep.items():
        with open(os.path.join(goc, ten), "w", encoding="utf-8") as f:
            f.write(noi_dung)


def chay_thu():
    with tempfile.TemporaryDirectory() as goc:
        tao_du_an_mau(goc)
        kq = quet_thu_muc(goc)
    print("\n".join(kq) if kq else "không thấy gì đáng ngờ")
    return kq


if __name__ == "__main__":
    if len(sys.argv) > 1:
        kq = quet_thu_muc(sys.argv[1])
        print("\n".join(kq) if kq else "không thấy gì đáng ngờ")
        sys.exit(1 if kq else 0)
    chay_thu()

Với dự án mẫu trong code, kết quả bắt được ba chỗ: khóa trong .env (đúng chỗ, nhưng .gitignore chưa chặn), khóa viết thẳng trong thu_nghiem.py, token Hugging Face còn sót trong notebook_cu.py. Tệp app.py đọc khóa qua os.environ thì không bị báo. Đây là lưới lọc thô bằng biểu thức chính quy; các công cụ chuyên dụng quét được nhiều loại bí mật hơn và quét cả lịch sử commit.

Notebook: chỗ khóa hay lọt nhất

Notebook Jupyter lưu cả đầu ra của từng ô vào tệp .ipynb. Nếu có lúc bạn chạy print(khoa) hay hiển thị biến chứa khóa để kiểm tra, khóa nằm luôn trong tệp dù đã xóa dòng code đó. Vài thói quen cho notebook:

  • Đọc khóa bằng os.environ ở ô đầu, không gán chuỗi khóa vào biến trong ô.
  • Trước khi commit, xóa toàn bộ đầu ra: trong Jupyter chọn xóa đầu ra mọi ô, hoặc chạy jupyter nbconvert --clear-output --inplace ten_notebook.ipynb.
  • Chạy công cụ quét ở trên cho cả thư mục, vì nó đọc tệp .ipynb như văn bản và bắt được khóa nằm trong đầu ra.
  • Với Google Colab, dùng mục bí mật (Secrets) có sẵn của Colab thay vì dán khóa vào ô code.

Push protection của GitHub

GitHub có thêm một lớp ở phía máy chủ. Theo trang About push protection (11/11/2026):

  • Push protection cho người dùng bật sẵn với tài khoản cá nhân trên GitHub.com, chặn bạn push bí mật lên kho công khai. Khi bị chặn, GitHub từ chối lần push và giải thích lý do; bạn gỡ bí mật khỏi commit rồi push lại.
  • Push protection cho kho thì mặc định tắt và cần quản trị viên bật (yêu cầu GitHub Secret Protection).
  • Có thể vượt qua chặn bằng cách nêu lý do, nhưng đừng làm vậy với khóa thật.

Trang Supported secret scanning patterns liệt kê các loại bí mật GitHub nhận ra. Ngày 11/11/2026, danh sách có anthropic_api_key, openai_api_key và hf_user_access_token, cả ba đều được đánh dấu là mẫu của đối tác và có push protection. Mẫu đối tác nghĩa là khi khóa xuất hiện trong kho công khai, GitHub báo cho chính hãng phát hành khóa.

Push protection là lớp cuối, không thay cho hai lớp đầu: nó chỉ nhận ra những dạng bí mật nó biết.

Khóa đã lộ thì làm gì

Theo trang Removing sensitive data from a repository, bước đầu tiên là thu hồi hoặc đổi khóa. Khóa đã đổi thì không dùng được nữa, và nhiều khi việc này đã giải quyết xong vấn đề mà không cần viết lại lịch sử.

Trình tự nên làm:

  1. Vào trang quản lý khóa của hãng (với Claude là phần API keys trong Claude Console), thu hồi khóa bị lộ, tạo khóa mới.
  2. Cập nhật khóa mới vào .env trên máy và vào Secrets của GitHub Actions nếu có dùng.
  3. Xem lịch sử sử dụng và hóa đơn trên trang của hãng để phát hiện lượt gọi lạ.
  4. Nếu vẫn muốn gỡ khóa khỏi lịch sử git, GitHub Docs khuyên dùng git-filter-repo. Docs cũng cảnh báo nhiều tác dụng phụ: mã băm commit thay đổi, chữ ký commit bị mất, người khác còn bản clone cũ có thể push dữ liệu trở lại, bản fork và bộ nhớ đệm vẫn còn giữ dữ liệu.

Vì bước 4 phức tạp và không chắc chắn, hãy luôn coi khóa từng nằm trong một commit đã push là khóa đã lộ.

Khóa trong GitHub Actions và agent AI

Workflow cần gọi API thì đặt khóa trong Settings, Secrets and variables, Actions của kho, rồi tham chiếu bằng secrets.TEN_KHOA trong tệp YAML; xem bài GitHub Actions. Với agent AI chạy trên máy (như Claude Code), không để agent đọc tệp .env khi không cần, và cấu hình quyền để chặn việc đó; bài giới hạn quyền của agent có ví dụ luật chặn tệp bí mật.

Một vài thói quen nữa:

  • Mỗi dự án một khóa riêng, đặt hạn mức chi tiêu nếu trang của hãng cho phép, để khi lộ thì thiệt hại có giới hạn.
  • Không dán khóa vào notebook, không in khóa ra log, không gửi khóa qua tin nhắn.
  • Khi nhờ người khác xem lỗi, xóa khóa khỏi đoạn log trước khi gửi.
Cẩn thận
  • Viết khóa thẳng vào code "tạm thời" rồi quên.
  • Thêm .env vào .gitignore sau khi đã commit nó, và tưởng thế là xong.
  • Chỉ xóa commit chứa khóa mà không thu hồi khóa.
  • Vượt qua push protection bằng lý do "sẽ sửa sau" với một khóa thật.
  • Để khóa trong ô đầu ra của notebook rồi đẩy notebook lên GitHub.

Câu hỏi thường gặp

Kho riêng tư thì có cần cẩn thận không?

Có. Kho riêng tư vẫn có thể bị chuyển thành công khai, được chia sẻ cho người khác, hay bị lộ qua bản clone trên máy. Khóa vẫn chỉ nên nằm trong .env.

Tệp .env có cần commit cho người cùng nhóm không?

Không. Commit .env.example chỉ có tên biến; mỗi người tự tạo .env với khóa của mình, hoặc nhóm chia sẻ khóa qua kênh riêng có quản lý.

Đã đổi khóa rồi có cần xóa khỏi lịch sử git không?

Khóa cũ đã thu hồi thì không còn dùng được. GitHub Docs nói việc đổi khóa nhiều khi đã đủ; viết lại lịch sử chỉ nên làm khi thật cần và hiểu các tác dụng phụ.

Push protection có bắt được mọi loại khóa không?

Không. Nó chỉ nhận ra các mẫu trong danh sách của GitHub. Khóa của dịch vụ nhỏ hay mật khẩu tự đặt có thể lọt qua, nên hai lớp đầu vẫn quan trọng nhất.

Học tiếp

Nếu bạn muốn học Python và AI bài bản cùng giảng viên, tham khảo khóa học AI của Big-O Coding.

Muốn học bài bản cùng giảng viên?

Khóa AI – Introduction to Machine Learning: nền tảng Máy học từ thuật toán cơ bản đến Neural Networks và LLM, học bằng Python. Học thử buổi 1 miễn phí.

Xem Khóa AI

Nguồn tham khảo

Chia sẻ: Facebook LinkedIn
Nhắn Zalo tư vấnZalo Gọi 0937 401 483