Huwag Ilagay ang API Keys sa AI Agents
Maaaring ma-leak ang API key sa isang tool call. Gumamit ng scoped, short-lived token sa credential gateway, hindi ng tunay na key sa agent.
Ano ang ibig sabihin ng pag-iwas na ilagay ang secrets sa AI agents
Ang AI agent ay isang normal na Linux process na nagpapatakbo ng mga command. Nababasa ng code na pinapatakbo nito ang bawat environment variable na hawak ng process. Kaya ang API key sa environment ng agent ay isang key na maaaring ipadala ng agent sa anumang host na maaabot nito. Ang pag-iwas na ilagay ang secrets sa agent ay nangangahulugang pagbibigay rito ng handle sa halip na key: isang short-lived at scoped token, o isang placeholder na pinapalitan ng ibang component ng aktuwal na value sa network boundary.
Hindi ito tungkol sa model na naging hostile. Mas simple ang mekanismo. Nagbabasa ang agent ng web page, README, o issue comment na may instructions, at sinusunod nito ang mga iyon. Para sa language model, walang pagkakaiba ang text na isinulat mo at text na kinuha nito. Ito ang prompt injection. Kapag nangyari ito, nakadepende ang lawak ng damage sa iisang bagay: kung ano ang nababasa ng process. Kung wala ka pang boundary, tinatalakay sa ligtas na pagpapatakbo ng coding agent sa server ang mga isolation rung na pinagsasaligan ng guide na ito.
Ang threat model sa payak na paliwanag
Patakbuhin ito bilang user na ginagamit ng iyong agent.
tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'Ang bawat linyang inilalabas nito ay isang HTTP request lang ang layo mula sa server ng isang hindi kilalang tao. Tingnan ngayon kung ano ang nasa disk na malapit sa agent.
grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'Hindi kailangan ng agent na may shell ng sopistikadong exploit para mailabas ang data na iyon. Apat na karaniwang paraan ang sapat, at lahat ng ito ay mukhang normal na gawain sa log:
- Isang outbound na
curlofetchpapunta sa kahit anong host, kung saan nasa query string ang value. - Isang
git commitatgit pushpapunta sa repository na maaaring sulatan ng agent. - Isang package install script na nagpapatakbo ng arbitrary code bilang user ng agent.
- Isang DNS lookup ng hostname na naglalaman ng value. Nakakalabas pa rin ito kahit naka-block ang HTTP egress.
Hindi ito malulutas sa pamamagitan lamang ng pagsusuri. Ang solusyon ay tiyaking walang mahalagang data na maaabot nito.
Ang secret sa working tree ay secret sa context window
Nagbabasa ng files ang agent. Mababasa ang isang .env file sa repository na pinagtatrabahuhan nito. Kapag nabasa na ito, nasa context window na ito. Ibig sabihin, kasama na ito sa transcript, sa anumang log na itinatago mo, at sa anumang susunod na isusulat ng agent.
Dati, kapag nasa tree na pinagtatrabahuhan ng agent ang key:
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billingPagkatapos, kapag inilipat ang file sa lugar na hindi na maaabot:
sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.envHindi na mabubuksan ng user ng agent ang file dahil wala na ito sa working tree. Ikalawang layer lamang ang deny rules sa sariling config ng agent, hindi ang unang layer. Nagbabasa ang Claude Code ng permission rules mula sa .claude/settings.json sa project:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}Pinipigilan nito ang hindi sinasadyang pagbukas ng agent ng file habang nag-e-explore. Saklaw din nito ang mga file tool: sinusuri rin ng Claude Code ang mga rule na ito laban sa mga file command na nakikilala nito sa Bash, gaya ng cat at head, at laban sa mga target ng redirection gaya ng < .env. Hindi nito pinipigilan ang injected instruction na patakbuhin ang grep -r KEY . sa loob ng project directory, o ang maikling Python script na mismong nagbubukas ng .env. Ayon sa permission documentation, hindi nalalapat ang deny rules sa mga command na nagbabasa ng files nang hindi pinapangalanan ang mga ito, o sa mga subprocess na mismong nagbubukas ng files. Para sa mga ito, itinuturo ng documentation ang sandbox ng Claude Code, na nagpapatupad ng block sa antas ng operating system. Ang paghingi ng confirmation bago tumakbo ang ganitong command ay nakadepende sa permission mode ng session. Naka-built-in na starting mode na ngayon ang auto mode para sa interactive Claude Code sessions, kaya ang server na hindi mo mino-monitor ay makakapagpatakbo ng mas marami sa mga command na ito nang walang prompt. Nalalapat din ang parehong limitasyon sa anumang humuhubog sa gawi ng agent sa halip na sa permissions nito: ang skill na naglilimita sa agent sa pinakamaliit na pagbabagong gumagana ay nakatutulong upang hindi mapunta ang isang run sa mga file na wala itong dahilan upang buksan, pero payo pa rin ito na maaaring hindi sundin ng model. Ituring ang config bilang guardrail at ang filesystem permission bilang pader. Nalalapat din ang parehong paghahati sa loob ng containers: tinatalakay ng env files at secrets sa Docker Compose ang bersyon ng problemang ito na nasa isang layer sa ibaba.
Bigyan ang bawat agent ng sarili nitong unprivileged user
Kung tumatakbo ang agent bilang ikaw, namamana nito ang iyong SSH keys, cloud credentials, at shell history. Isang command lang ang kailangan para gumawa ng hiwalay na user, at maaalis nito ang lahat ng iyon.
sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519Dapat mag-fail ang huling linya gamit ang cat: /home/you/.ssh/id_ed25519: Permission denied. Kung nag-print ito ng key, maaabot ng user ng agent ang file. Ang pagbukas ng file gamit ang pangalan nito ay nangangailangan ng search (x) permission sa bawat directory na nasa itaas nito, hindi read permission. Kaya kahit ang home directory na may mode na 711 ay hindi maaaring i-list ng iba, maaabot pa rin ang key kung readable mismo ang key file. Isinasara ng chmod 700 ~ ang path, at inaayos ng chmod 600 ~/.ssh/id_ed25519 ang key. Huwag idagdag ang agent user sa sudo, at huwag itong bigyan ng NOPASSWD rule na mas malawak kaysa sa iisang command na talagang kailangan nito. Ipinaliliwanag ng Mga user na may least privilege sa isang VPS ang detalye ng group at sudoers. Panatilihin ang separation na ito kapag higit sa isang session na ang pinapatakbo mo sa box, dahil maaaring direktang magpadala ng text ang isang Claude Code session sa isa pa, at anumang hawak ng unang session ay maaaring tumawid sa channel na iyon sa iisang message.
May isa pang boundary na dapat idagdag sa isang cloud VPS. Sumasagot ang instance metadata service sa isang fixed link-local address, at madalas itong nagbibigay ng role credentials sa anumang humihingi. Walang check sa endpoint kung aling local user ang tumatawag. Sa EC2, nakadokumento ng AWS na hindi nangangailangan ng token ang IMDSv1 calls, at ang sarili nitong payo para ilayo ang isang user sa service ay isang local firewall rule na nakabatay sa user ID nito.
sudo iptables -I OUTPUT 1 -p tcp -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTIpasok ang rule sa itaas gamit ang -I sa halip na idagdag ito sa dulo gamit ang -A. Sa isang host na nagpapatakbo ng ufw na may default allow outgoing policy, tinatanggap ng huling OUTPUT chain rule ng ufw ang bawat bagong TCP connection. Dahil dito, hindi kailanman naaabot ang idinagdag sa dulo na rule. Nawawala rin ang iptables rules pagkatapos ng reboot maliban kung ise-save mo ang mga ito, at kung na-enable mo ang IPv6 endpoint ng metadata service, [fd00:ec2::254], idagdag ang parehong rule gamit ang ip6tables.
Suriin ito mula sa panig ng agent. Dapat walang i-print ang sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ at dapat itong mag-exit nang non zero, dahil nire-reject ang packet bago ito lumabas sa box. Kung nag-print ito ng listing, hindi naaabot ang rule.
I-inject ang credential sa boundary
Ang pattern na talagang lumulutas dito ay credential injection. Walang totoong key na hinahawakan ang agent. Ipinapadala nito ang request sa pamamagitan ng local gateway, at pinapalitan ng gateway ang placeholder ng totoong secret habang palabas ang request. Nasa storage ng gateway ang secret, nasa ibang process, at pagmamay-ari ng ibang user.
Ang OneCLI ay isang open source implementation nito. Apache-2.0 licensed ito maliban sa mga ee/ directory nito, na may hiwalay na enterprise license at nangangailangan ng subscription para sa production use. Tumatakbo ito sa mga container katabi ng agent. Noong October 2026, idinodokumento ng project ang Docker Compose setup na ito, na kumukuha ng tatlong generated secret mula sa .env file na nasa tabi ng compose file:
git clone https://github.com/onecli/onecli.git && cd onecli/docker
cat > .env <<EOF
SECRET_ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
GATEWAY_INTERNAL_SECRET=$(head -c 32 /dev/urandom | base64)
BETTER_AUTH_SECRET=$(head -c 32 /dev/urandom | base64)
COMPOSE_PROFILES=runner
EOF
chmod 600 .env
docker compose up -d --waitMag-back up ng kopya ng .env file na iyon, dahil kapag nawala ang SECRET_ENCRYPTION_KEY, mawawala ang reference sa bawat secret na naka-store kasama nito. Nakikinig ang dashboard sa port 10254 at ang gateway sa port 10255. Pareho silang naka-bind sa 127.0.0.1 bilang default. Isang beses mo lang ise-store ang totoong credential. Pagkatapos, bigyan ang bawat agent ng placeholder value kapalit ng key, kasama ang sarili nitong scoped access token na ipinapadala nito sa isang Proxy-Authorization header. Tinutukoy ng gateway ang outbound request batay sa host at path, dini-decrypt ang katugmang credential, at ipinapalit ito sa placeholder. Walang kapaki-pakinabang na secret na nasa environment ng agent.
Hindi encryption ang pangunahing halaga nito. Ang mahalaga ay nagiging log query ang tanong na “ano ang ginamit ng agent na ito, at kailan?” Isang audit trail lang ang babasahin mo sa halip na hulaan kung alin sa anim na environment ang may kopya ng key.
Ibigay ang secret sa process, hindi sa environment
Kung pinapatakbo mo ang agent sa ilalim ng systemd, hindi mo na kailangan ng environment variables. Inilalagay ng LoadCredential= ang secret sa isang private directory na mababasa lamang ng user ng service, inilalantad bilang %d sa unit file at bilang $CREDENTIALS_DIRECTORY sa loob ng process. Ang restriction na ito ay batay sa user, hindi sa service. Kaya bigyan ang bawat agent service ng sarili nitong user. Maaaring buksan ng ibang process na tumatakbo sa parehong user ang directory maliban kung gumagamit din ang unit ng filesystem namespacing, gaya ng PrivateMounts=yes, na nagtatago rito mula sa ibang service. Hindi kailanman lumilitaw ang value sa /proc/<pid>/environ, kaya hindi ito maipapakita ng ps eww. Nawawala rin ang directory kapag humihinto ang service.
I-encrypt muna ang credential para sa machine. Galing ang mga command na ito sa systemd documentation at gumagana sa systemd 250 o mas bago. Saklaw nito ang Ubuntu 24.04 at Debian 13:
echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
systemd-creds cat api_keyIpinapakita ng huling command ang sk-example-value. Pinatutunayan nito na nade-decrypt ang encrypted file sa host na ito. Pagkatapos, i-reference ito mula sa unit:
[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-workerBinubuksan ng agent code ang file sa $AGENT_KEY_FILE kapag kailangan nito ang value. Sandali lamang ang pagbasa ng file. Nanatili ang environment variable habang tumatakbo ang process, pati sa bawat child process na nililikha nito.
Piliin ang mga token na maikli ang lifetime kaysa sa mga key na matagal bago mag-expire
Valid pa rin ang key na hindi kailanman nag-e-expire kapag lumitaw ito makalipas ang ilang buwan sa isang log o transcript. Kung nag-aalok ang serbisyo ng session token, iyon ang gamitin at itakda ang pinakamaikling lifetime na pinapahintulutan ng gawain.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900Labinlimang minuto ang minimum na tinatanggap ng AWS STS (security token service), at karaniwan itong sapat para sa isang agent task. Para sa GitHub, bigyan ang agent user ng sarili nitong gh login na may fine-grained token na limitado sa iisang repository na pinagtatrabahuhan nito, upang ang gh auth token sa loob ng session na iyon ay magbalik ng credential na walang access sa iba pang resource. Unahin ang paglimita ayon sa resource, saka ayon sa oras.
I-verify, pagkatapos ay patuloy na mag-verify
Sulit patakbuhin ang 3 check pagkatapos ng anumang pagbabago sa setup ng isang agent. Patakbuhin ang mga ito bilang user ng agent, hindi bilang ikaw.
sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/userWalang dapat i-print ang una. Dapat i-print ng ikalawa ang ls: cannot open directory '/home/you/': Permission denied, ngunit ipinapakita lamang nito na hindi maililista ang directory; ang cat test kanina ang nagpapatunay na hindi mababasa ang file sa loob nito. Ipinapakita ng ikatlo kung anong identity ang ipinapakita ng network path ng agent. Ito ang tanong na sinasagot ng gateway pattern: nangangahulugang walang credential na tinatanggap ng GitHub ang 401, maaaring wala talagang credential o invalid o expired ang gamit, at nangangahulugang may valid na credential ang 200. Kaya dapat alam mo kung aling token ang ginagamit nito. Kung nagpapatakbo ka ng mga agent nang unattended, tinatalakay sa pagkontrol sa gastos ng AI agent sa isang VPS ang mga limitasyon sa budget na kaakibat ng mga limitasyong ito sa access.
FAQ
Maaari ko bang basta pagkatiwalaan ang model na hindi i-leak ang mga key ko?
Hindi, dahil hindi ang model ang attacker sa threat model na ito. Nagbabasa ang agent ng text mula sa mga web page, repository, at issue tracker, at maaaring may mga instruction ang text na iyon. Walang maaasahang paraan ang model para matukoy kung alin ang mga instruction mo at alin ang text na na-fetch nito. Nabibigo ang anumang control na nakadepende sa tamang pagpili ng model sa unang pagkakataong nakakumbinsi ang injected instruction, kaya dapat nasa operating system o network ang control.
Talaga bang masama ang environment variable para sa mga secret ng agent?
Masama ang mga ito sa isang partikular na paraan: namamana ang mga ito. Nakakakuha ng kopya ang bawat child process na ini-spawn ng agent, kabilang ang build script, test runner, at anumang package install hook. Mababasa rin ang mga variable sa pamamagitan ng /proc/<pid>/environ ng parehong user, kaya mababasa ang mga ito ng anumang pinapatakbo ng agent nang hindi na kailangang ipasa ng agent. Nililimitahan ng file na binabasa sa mismong oras ng paggamit, gamit ang LoadCredential= o gateway, ang exposure sa sandaling iyon lamang.
Nalulutas ba nito nang mag-isa ang paglalagay ng mga secret sa vault?
Bahagya lamang. Inaayos ng vault ang storage. Kung ikaw ang nagho-host ng vault, kailangan itong dumaan sa sarili nitong hardening pass, dahil karaniwang nabibreach ang isang Vaultwarden server sa admin token o backup file nito, hindi sa mga naka-encrypt na item na hawak nito. Hindi nito inaayos ang huling hakbang, kung saan may kumukuha ng secret mula sa vault at ipinapasa ito sa agent bilang environment variable. Ibinabalik ka nito sa dating sitwasyon. Ang mahalaga ay kung sino ang nagsasagawa ng substitution. Kung agent ang nag-fetch ng secret, nasa agent ang secret. Kung gateway o init system ang nagsasagawa ng substitution sa labas ng process ng agent, hindi kailanman hawak ng agent ang secret.
Paano ko malalaman kung may na-leak na ang agent?
Karaniwan, hindi mo na malalaman pagkatapos mangyari, at ito ang dahilan kung bakit kailangan ang gateway. Kung wala nito, kalat ang ebidensiya sa shell history, transcript ng agent, at outbound connection logs na malamang ay hindi mo iniingatan. Kapag may credential gateway, bawat paggamit ng credential ay isang linya na may agent identity at timestamp. Kung pinaghihinalaan mong may leak, i-rotate muna ang key at saka mag-imbestiga. Mura ang rotation, pero hindi ang katiyakan.
Ano ang minimum na dapat kong gawin ngayon?
Ilipat ang bawat .env file palabas ng mga directory na ginagamit ng iyong mga agent, at gumawa ng isang unprivileged user para sa bawat agent. Tumatagal nang humigit-kumulang sampung minuto ang dalawang pagbabagong ito at sinasara ang pinakakaraniwang path: binabasa ng agent ang credential file na walang dahilan para nasa tabi ng code. Ang gateway at short-lived token ang susunod na hakbang, hindi ang unang hakbang. Pareho ang panimulang hakbang para sa anumang agent runtime, kabilang ang ligtas na pagpapatakbo ng autonomous agent sa isang VPS.