Cách Đưa Một Bí Mật Cho AI Coding Agent Mà Không Bỏ Nó Vào Prompt

Một agent cần khóa Stripe hay mật khẩu cơ sở dữ liệu để chạy lệnh của nó. Bạn dán giá trị đó vào khung chat và nó rơi vào tệp transcript trên đĩa, sang phía nhà cung cấp mô hình, đôi khi vào cả một commit. Quy tắc chúng tôi rút ra sau ba lần tự mình làm hỏng : một bí mật đến được agent qua biến môi trường, và không qua đường nào khác.

Tình huống lúc nào cũng y như nhau. Một agent đang làm dở nửa chừng một tác vụ, nó cần gọi Stripe test API của bạn, chạy một migration lên cơ sở dữ liệu staging, hoặc push lên một registry riêng tư. Nó hỏi xin khóa. Bạn có khóa sẵn trong trình quản lý mật khẩu, agent thì đang chờ, và thứ nhanh nhất trên đời là dán nó vào khung chat.

Cú dán đó chính là khoảnh khắc khóa rời khỏi tầm kiểm soát của bạn. Không phải vì mô hình có ác ý, mà vì một prompt là một đối tượng được lưu lại và được chuyển đi : nó đi vào một tệp transcript trên đĩa của bạn, nó đi sang nhà cung cấp mô hình, và nó bị sao chép lại bởi mỗi lần chạy lại cùng mỗi subagent thừa hưởng cuộc hội thoại.

Chúng tôi làm ra AgentsRoom, một ứng dụng desktop chạy các agent lập trình, nên chúng tôi đã phải tự giải bài này cho chính mình. Chúng tôi làm sai ba lần trong tháng 9 năm 2026. Đây là quy tắc cuối cùng chúng tôi rút ra, ba sai lầm đã đẻ ra nó, và cách áp dụng dù bạn có dùng ứng dụng của chúng tôi hay không.

Câu trả lời ngắn

  • Một bí mật đến được agent qua một biến môi trường, và không qua bất cứ đường nào khác. Không phải một tin nhắn, không phải một ticket, không phải một skill, không phải một tùy chọn dòng lệnh.
  • Agent dùng $NAME, không bao giờ dùng giá trị. Shell sẽ khai triển nó lúc lệnh chạy.
  • Lưu giá trị một lần, bên ngoài repo, được mã hóa bằng keychain của hệ điều hành, rồi tham chiếu nó theo tên ở mọi nơi khác.
  • Một bí mật đã đi qua một cuộc hội thoại là một bí mật cháy. Thay nó đi, rồi mới dọn dẹp.
  • Việc che giá trị là lưới an toàn, không phải kế hoạch. Nó bắt những gì trông giống một khóa, chứ không bắt một mật khẩu cơ sở dữ liệu đọc lên như một từ bình thường.

Vì sao cú dán đó tệ hơn vẻ ngoài của nó

Ba chuyện xảy ra cùng lúc, và chỉ chuyện đầu tiên là dễ thấy.

Nó đi sang nhà cung cấp mô hình. Đó là phần ai cũng nhìn ra. Khóa của bạn giờ nằm trong thân một request trên hạ tầng của người khác, bên trong một cuộc hội thoại mà bạn không sửa được.

Nó nằm trên đĩa của bạn, ở dạng chữ thường. Claude Code, Codex, Antigravity CLI và những cái còn lại đều giữ một transcript của cuộc hội thoại để bạn tiếp tục được. Tệp đó nằm trong thư mục home của bạn, không mã hóa, lâu chừng nào CLI còn giữ lịch sử. Hãy thử tìm chuỗi sk- trong các transcript của bạn một lần và xem kết quả trả về.

Nó lan ngang. Cuộc hội thoại chính là bộ nhớ của agent, nên bất cứ thứ gì sao chép cuộc hội thoại đều sao chép luôn khóa : một phiên được mở lại, một agent được fork, một subagent được tạo với ngữ cảnh của agent cha, một bản tóm tắt được ghi vào một tệp của dự án. Chúng tôi đã thấy một khóa đi từ một tin nhắn chat sang một mô tả ticket rồi sang tin nhắn đầu tiên của một agent thứ hai, mà không ai làm gì sai cả.

Và tấm lưới an toàn quen thuộc thì không đỡ nổi. Việc che giá trị trong đầu ra hoạt động theo mẫu : sk-…, glpat-…, dáng của một JWT. Nó không thể biết thursday-harbour-71 là mật khẩu cơ sở dữ liệu staging của bạn. Trong chính ứng dụng của mình, chúng tôi che các giá trị khớp mẫu trong đầu ra của terminal, mà vẫn coi đó là tấm lưới dưới cái đu bay, chứ không phải chính cái đu bay.

Quy tắc một cửa

Quy tắc đây, gói trong một dòng : một thông tin đăng nhập đến được agent qua một biến môi trường của tiến trình nơi agent chạy, và không qua bất kỳ kênh nào khác.

Tất cả phần còn lại là văn bản thuần túy. Một tin nhắn bạn gõ, một mô tả ticket, một tệp skill, một system prompt, một tùy chọn dòng lệnh : tất cả đều do một con người hoặc một mô hình đọc, nên một bí mật viết ở đó là một bí mật đã được công bố.

Dòng lệnh xứng đáng có một câu riêng, vì nó tạo cảm giác an toàn hơn một tin nhắn chat trong khi thực tế thì không. Một giá trị nằm trong --api-key=… được mọi tiến trình trên máy nhìn thấy qua ps, nó rơi vào lịch sử shell của bạn, nó bị ghi log bởi bất cứ thứ gì lưu lại lệnh đã khởi chạy, và nó hiện lên lại trong các công cụ chẩn đoán in dòng tiến trình ra cho bạn xem. Còn biến môi trường của một tiến trình thì chỉ tiến trình đó và root đọc được. Bề mặt nhỏ hơn, tiện lợi y như nhau.

Trong khi đó agent không bao giờ cần giá trị. Nó cần viết ra cái này :

curl -H "Authorization: Bearer $STRIPE_SECRET_KEY" https://api.stripe.com/v1/charges

Shell khai triển $STRIPE_SECRET_KEY lúc thực thi. Agent đã viết mười bảy ký tự và một dấu đô la. Transcript của nó chứa mười bảy ký tự và một dấu đô la.

Cách nó hoạt động trong ứng dụng của chúng tôi

Cơ chế không quan trọng bằng hình dạng, nhưng hình dạng thì dễ bắt chước hơn khi bạn được nhìn thấy một bản hiện thực cụ thể.

Một kho, một tệp. AgentsRoom giữ các bí mật trong một tệp duy nhất trong thư mục home của bạn, quyền 0600, được mã hóa qua keychain của hệ điều hành, và xác nhận lại ở mỗi lần ghi. Không có gì được đồng bộ lên máy chủ, và không có gì đi sang một máy tính khác : một bí mật thuộc về chiếc máy đang giữ nó. Nếu keychain không khả dụng, việc lưu sẽ thất bại chứ không lùi về ghi dạng chữ thường.

Bạn lưu một giá trị đúng một lần. Lệnh & kết nối > Bí mật, trong bất kỳ dự án nào. Một cái tên (chữ cái, chữ số, dấu chấm, gạch ngang, gạch dưới, 64 ký tự, để dùng được luôn làm tên biến môi trường) và một giá trị, gõ vào một ô dạng mật khẩu. Dòng hiện ra cho thấy cái tên cùng tham chiếu của nó, {{secret:STRIPE_SECRET_KEY}}, và một nút sao chép tham chiếu đó. Không có nút hiện giá trị và không có nút sao chép giá trị, vì không tồn tại đường đi nào trong mã nguồn trả một giá trị về cho giao diện.

Bạn tham chiếu nó ở nơi môi trường được dựng lên. Chỉnh sửa agent > Nâng cao > Biến môi trường, hoặc cài đặt của dự án, hoặc cài đặt của provider, hoặc một dòng lệnh dev :

STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}}

Lúc khởi chạy, tiến trình chính thay giá trị vào môi trường của terminal mà nó sắp tạo. Giao diện không bao giờ thấy nó, agent không bao giờ thấy nó, điện thoại cũng không bao giờ thấy nó. Trang Trình quản lý bí mật dẫn bạn đi qua từng màn hình.

Agent khám phá ra các cái tên, không phải các giá trị. Một agent được nối với các công cụ MCP của chúng tôi có thể gọi secrets_list và nhận về danh sách tên có trên máy đó. Chừng đó là đủ để nó có ích : nó có thể nói "bạn đã lưu một STRIPE_SECRET_KEY, hãy thêm STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}} vào môi trường của agent này rồi tôi sẽ dùng $STRIPE_SECRET_KEY". Nó không đọc được giá trị, kể cả khi đang chạy một lệnh có dùng giá trị đó.

Ba sai lầm chúng tôi đã phát hành

Quy tắc bên trên nghe hiển nhiên khi đã viết ra giấy. Nó là mô sẹo của ba con bug, tất cả đều trong tháng 9 năm 2026, tất cả đều do người dùng báo.

Một tham chiếu viết trong ticket lại bị phân giải ra giá trị

Ngày 19 tháng 9. Robert ghi lại một ánh xạ trong mô tả ticket, cái STRIPE_SECRET_KEY={{secret:STRIPE_SECRET_KEY}} hoàn toàn hợp lý, để ai nhận ticket cũng biết phải thiết lập gì. Đọc ticket qua API thì tham chiếu trả về vẫn nguyên vẹn. Nhưng agent khởi chạy từ ticket đó lại nhận được giá trị thật trong tin nhắn đầu tiên của nó.

Nguyên nhân là một chi tiết đường ống với hậu quả rất thật. Phần brief của ticket trở thành prompt đầu tiên của agent, và nó di chuyển bên trong một biến môi trường của lệnh khởi chạy. Bộ phân giải của chúng tôi thay thế mọi giá trị môi trường, nên nó thay thế luôn cả prompt. Khóa đã lên đĩa và đã sang nhà cung cấp mô hình. Phải thay khóa, không có cách nào né.

Bản sửa là làm cho lệnh khởi chạy của agent trở thành văn bản thuần túy : khi môi trường có mang một id agent, biến chứa prompt đầu tiên được để nguyên không đụng tới. Một lệnh dev dùng cùng biến đó thì không có id agent nên vẫn được phân giải như trước. Chúng tôi chấp nhận một tác dụng phụ một cách có chủ ý : một tham chiếu gõ vào phần tùy chọn CLI của agent cũng không còn được phân giải nữa, và như vậy là đúng, vì dòng lệnh dù sao cũng kết thúc trong ps và trong các log.

Bài học : cánh cửa phải hẹp một cách có chủ ý. "Thay thế ở mọi chỗ mà nó có thể có ích" chính là cách một bí mật kết thúc ở nơi lẽ ra nó không bao giờ được đến.

Một tham chiếu dán vào tin nhắn, và không bao giờ được phân giải

Ngày 22 tháng 9. Cs040618 báo đúng vấn đề ngược lại : "tôi dán tham chiếu vào cuộc hội thoại mà agent không hiểu nổi cái khóa này là gì".

Không phải bug. Một tin nhắn là văn bản thuần túy theo thiết kế, đó chính xác là bản sửa của ba ngày trước đó. Nhưng không có gì trong ứng dụng nói điều đó ra vào đúng lúc người dùng thao tác, còn agent, khi nhận {{secret:…}} trong một tin nhắn, thì chẳng biết trả lời thế nào.

Ba bản sửa, tất cả đều nhằm chỉ ra cánh cửa nằm ở đâu : một gợi ý bật lên khi bạn dán một tham chiếu vào ô soạn tin và trỏ sang phần biến môi trường, một đoạn trợ giúp phía trên danh sách bí mật nêu tên ba ô nơi tham chiếu được thay thế và nói rõ "không bao giờ trong một tin nhắn", và một dòng trong chính phần hướng dẫn của agent bảo nó phải trả lời gì khi một tham chiếu đến qua tin nhắn.

"Cài đặt > Bí mật" không hề tồn tại

Ngày 28 tháng 9. Agent của Mohammad gửi một báo cáo : người dùng nói đã thêm OTPIQ_API_KEY trong Cài đặt > Bí mật, đã lưu vài lần trong suốt ba mươi phút, mà secrets_list vẫn trả về đúng ba bí mật cũ.

Đường lưu không có vấn đề gì. Bí mật chưa bao giờ đến được kho, vì không hề có trang Bí mật trong phần cài đặt. Sáu chuỗi trợ giúp trong ứng dụng desktop, một trang trên chính website này và phần hướng dẫn của agent đều chỉ người dùng tới một màn hình không tồn tại. Thứ thật sự tồn tại trong cài đặt là khối biến môi trường, thế là anh ấy gõ token vào đó, trong cặp ngoặc nhọn, mà cái đó không phải một tham chiếu : không bao giờ được thay thế, gửi thẳng nguyên dạng cho agent, và được đồng bộ theo dự án, nên token đã rời khỏi máy.

Chúng tôi sửa các chuỗi để chúng nêu đúng đường đi, cho các công cụ hướng tới agent nói rõ bí mật được thêm ở đâu, và thêm một kiểm tra trên khối môi trường để cảnh báo khi thấy một thông tin đăng nhập trông như giá trị thô, một cái tên trong ngoặc nhọn mà không phải tham chiếu, và một tham chiếu tới một bí mật không tồn tại trên máy này.

Bài học ở đây chẳng liên quan gì tới bí mật : một đường dẫn sai trong một chuỗi trợ giúp là một lỗi bảo mật. Nó đẩy người dùng sang ô gần nhất trông có vẻ đúng.

Nếu bạn không dùng AgentsRoom

Quy tắc này đi đâu cũng dùng được. Hãy chọn cách nào hợp với thiết lập của bạn ; tất cả đều kết thúc ở cùng một chỗ, một biến môi trường trong tiến trình nơi agent chạy.

  • CLI của một trình quản lý mật khẩu. op run -- claude với 1Password, hoặc thứ tương đương bên Bitwarden và Doppler : công cụ đọc các tham chiếu từ một mẫu, tiêm giá trị thật vào môi trường của tiến trình con, và không có gì được ghi xuống đĩa.
  • direnv với một .envrc đã gitignore. Đơn giản, cục bộ, theo từng thư mục. Thêm .envrc và .env* vào .gitignore trước khi giá trị đầu tiên đi vào, chứ không phải sau.
  • Một tệp .env mà agent không bao giờ đọc. Giữ nó bên ngoài repo và nạp nó trong shell khởi chạy agent. Nếu tệp nằm trong repo, sớm muộn agent cũng sẽ mở nó, trích nó trong một tin nhắn, và đẩy bạn về lại vạch xuất phát.
  • Thông tin đăng nhập cloud : hãy dùng loại sống ngắn. GitHub OIDC trong CI thay cho khóa AWS dài hạn, và một workload identity thay cho một tệp khóa service account.

Dù bạn chọn gì, hãy đưa quy tắc đó vào phần hướng dẫn của agent, để nó thôi hỏi. Một đoạn trong CLAUDE.md hoặc AGENTS.md là đủ : không bao giờ hỏi xin giá trị của một thông tin đăng nhập, không bao giờ ghi nó vào một tệp, dùng $NAME và nói cho người dùng biết phải đặt biến nào. Chúng tôi có xuất bản một skill nhỏ nói đúng chừng đó, và nó dài năm dòng.

Nếu một khóa đã đi qua một cuộc hội thoại

Theo thứ tự này, và bước đầu tiên thì không được bỏ.

  1. Thay khóa mới. Bạn không thu hồi được một prompt đã gửi. Hãy coi giá trị đó là đã lộ và cấp một giá trị mới.
  2. Tìm các transcript. Các CLI giữ tệp hội thoại trong thư mục home của bạn. Tìm giá trị cũ trong đó rồi xóa hoặc cắt bớt những gì khớp.
  3. Kiểm tra những nơi sao chép văn bản. Lịch sử của trình quản lý clipboard, ticket hay issue nơi agent được brief, mọi bản tóm tắt mà agent đã ghi vào repo, và git log -p nếu agent có commit trong lúc đang cầm khóa.
  4. Rồi mới dựng cánh cửa. Giá trị mới nằm sau một biến môi trường, tham chiếu theo tên, và cập nhật phần hướng dẫn của agent để lần sau nó không hỏi nữa.

Bước 4 để cuối cùng, vì một cơ chế sạch sẽ dựng quanh một khóa đã rò rỉ chỉ là một vụ rò rỉ được xếp gọn gàng hơn.

Những câu hỏi người ta hỏi thật

Vì sao không bao giờ được đưa khóa API vào prompt gửi cho agent AI ?

Vì một prompt là thứ được lưu lại và được chuyển đi. Claude Code, Codex và các CLI khác ghi cuộc hội thoại vào một tệp transcript trên đĩa của bạn, còn bản thân tin nhắn thì được gửi sang nhà cung cấp mô hình. Từ đó khóa còn nằm trong lịch sử clipboard của bạn, trong mọi log có in lại tin nhắn, và trong các lần chạy lại cùng các subagent sao chép lại cuộc hội thoại. Việc che giá trị theo mẫu bắt được những chuỗi có dạng sk- hay glpat-, nhưng không bắt được một mật khẩu cơ sở dữ liệu trông như một từ bình thường.

Một agent lập trình dùng bí mật mà nó không đọc được bằng cách nào ?

Qua một biến môi trường. Bạn đặt giá trị vào một biến môi trường của tiến trình nơi agent chạy, còn agent thì viết $STRIPE_SECRET_KEY trong lệnh thay cho giá trị. Shell sẽ khai triển nó lúc lệnh chạy. Agent không bao giờ nhìn thấy các ký tự đó, nên chúng không bao giờ lọt vào cuộc hội thoại hay vào transcript của nó.

Tôi truyền bí mật làm tham số dòng lệnh thay thế được không ?

Được, nhưng bạn không nên. Một dòng lệnh thì mọi tiến trình trên máy đều nhìn thấy qua ps, nó đi vào lịch sử shell, vào log của tiến trình, và vào bất cứ công cụ chẩn đoán nào đọc lại lệnh đã được khởi chạy. Còn biến môi trường của một tiến trình duy nhất thì chỉ tiến trình đó và root đọc được, một bề mặt nhỏ hơn rất nhiều.

AgentsRoom lưu các bí mật mà agent dùng ở đâu ?

Trong một tệp duy nhất trong thư mục home của bạn, quyền 0600, được mã hóa qua keychain của hệ điều hành, và chỉ trên chính chiếc máy đó. Không có gì được đồng bộ lên máy chủ, và không có nút nào hiển thị lại một giá trị. Ứng dụng hiện tên, agent thấy tên, còn giá trị chỉ được giải mã trong tiến trình chính, đúng vào lúc một terminal hay một lệnh khởi chạy.

Điều gì xảy ra nếu tôi dán một tham chiếu bí mật vào tin nhắn gửi cho agent ?

Không có gì được thay thế, và đó là chủ ý. Một tin nhắn, một mô tả ticket, một skill và một system prompt đều là văn bản thuần túy, nên tham chiếu viết ở đó sẽ đến tay agent nguyên dạng chữ. Chỉ giá trị của một biến môi trường mới được thay thế. Chúng tôi học được điều này theo cách đau đớn : một tham chiếu viết trong mô tả ticket từng bị phân giải ra giá trị thật trong tin nhắn đầu tiên của agent khởi chạy từ ticket đó.

Một khóa đã đi qua cuộc hội thoại rồi. Tôi phải làm gì ?

Thay khóa mới trước tiên, trước mọi thứ khác, vì bạn không thu hồi được một prompt đã gửi : nó đã nằm ở phía nhà cung cấp và trong tệp transcript trên đĩa của bạn. Sau đó xóa hoặc cắt bớt những transcript có chứa nó, kiểm tra trình quản lý clipboard, và grep cả repo phòng khi agent đã ghi nó vào một tệp. Chỉ khi đó mới đặt giá trị mới ra sau một biến môi trường.

Tóm lại

Một agent không cần đọc khóa của bạn. Nó cần một tiến trình mà môi trường đã sẵn mang khóa đó.

Lưu giá trị một lần, bên ngoài repo, được hệ điều hành mã hóa. Tham chiếu nó theo tên ở nơi môi trường được dựng lên. Để agent viết $NAME. Giữ mọi kênh còn lại ở dạng văn bản thuần túy, kể cả những kênh mà việc thay thế hẳn sẽ tiện lợi, vì chính những kênh tiện lợi đó mới là những kênh được lưu lại và được chuyển đi.

Và khi bạn làm sai, mà bạn sẽ làm sai, hãy thay khóa trước và dọn dẹp sau.

Tải AgentsRoom

Chạy tất cả agent AI của bạn, trên mọi dự án, trong một cửa sổ duy nhất.

Miễn phíTải AgentsRoom

Ứng dụng đồng hành: theo dõi agent khi đi đường

Sử dụng Claude, Codex, Antigravity CLI hoặc nhà cung cấp AI khác.

Tải tiện ích mở rộng
Chrome Web Store

Gửi lỗi và yêu cầu thẳng vào backlog công khai của bạn.

Nhiều dự án
Đa nhà cung cấp
Nhiều agent
Trạng thái trực tiếp
File diff & commit
Ứng dụng đồng hành mobile
Xem trước trực tiếp
Đội agent
Tự động hóa trình duyệt
Dev theo backlog
Thư viện prompt
Thư viện skill
Xem tất cả tính năng

Đọc thêm