امنیت ایجنتهای هوش مصنوعی و جلوگیری از نشت API Key
اگر ایجنت شما به API Key اصلی دسترسی داشته باشد، با یک حمله Prompt Injection ساده لو میرود. به جای کلید اصلی، از توکنهای کوتاهمدت و محدود در محیط ایجنت استفاده کنید.
مفهوم دور نگه داشتن اسرار از ایجنتهای هوش مصنوعی
یک ایجنت هوش مصنوعی در واقع یک پردازش عادی در لینوکس است که دستورات را اجرا میکند. هر متغیر محیطی (environment variable) که در اختیار آن پردازش باشد، توسط کدی که اجرا میکند قابل خواندن است؛ بنابراین یک API key در محیط ایجنت، کلیدی است که ایجنت میتواند آن را به هر میزبانی که به آن دسترسی دارد، ارسال کند. دور نگه داشتن اسرار از ایجنت به این معناست که بهجای کلید اصلی، یک دستگیره (handle) به آن بدهید: یک توکن کوتاهمدت با دسترسی محدود، یا یک جاینگهدار (placeholder) که در مرز شبکه توسط سیستم دیگری با مقدار واقعی جایگزین میشود.
این موضوع درباره تبدیل شدن مدل به یک موجود خصمانه نیست. سازوکار آن بسیار سادهتر است. ایجنت یک صفحه وب، یک فایل README یا یک کامنت در issue را میخواند که حاوی دستورالعملهایی است و از آنها پیروی میکند، زیرا برای یک مدل زبانی، تفاوتی بین متنی که شما نوشتهاید و متنی که خودش دریافت کرده وجود ندارد. این همان تزریق دستور (prompt injection) است. هنگامی که این اتفاق میافتد، میزان خسارت دقیقاً توسط یک چیز محدود میشود: آنچه پردازش میتواند بخواند. اگر هنوز مرزبندی را انجام ندادهاید، اجرای ایمن ایجنت کدنویسی روی سرور مراحل ایزولهسازی را پوشش میدهد که این راهنما بر پایه آنها بنا شده است.
مدل تهدید به زبان ساده
این دستور را با کاربری که agent شما با آن اجرا میشود، اجرا کنید.
tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'هر خطی که این دستور چاپ میکند، تنها به اندازه یک درخواست HTTP با سرور یک غریبه فاصله دارد. اکنون به آنچه در دیسک در نزدیکی agent قرار دارد نگاه کنید.
grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'یک agent که به shell دسترسی دارد، برای خارج کردن آن دادهها نیازی به exploit هوشمندانه ندارد. چهار مسیر معمولی این کار را انجام میدهند و هر چهار مورد در لاگها مانند فعالیتهای عادی به نظر میرسند:
- یک درخواست خروجی
curlیاfetchبه هر میزبان، که مقدار مورد نظر در query string قرار دارد. - یک
git commitوgit pushبه مخزنی که agent اجازه نوشتن در آن را دارد. - یک اسکریپت نصب پکیج، که کد دلخواه را با دسترسی همان کاربرِ agent اجرا میکند.
- یک DNS lookup برای نام میزبان که حاوی آن مقدار است؛ این روش حتی زمانی که خروجی HTTP مسدود شده باشد نیز کار میکند.
شما نمیتوانید با بازبینی دستی از این وضعیت خارج شوید. راه حل این است که اطمینان حاصل کنید هیچ چیز ارزشمندی در دسترس agent قرار ندارد.
وجود یک secret در دایرکتوری کاری، به معنای وجود آن در context window است
یک agent فایلها را میخواند. یک فایل .env در مخزنی که agent در آن کار میکند خوانده خواهد شد و پس از خوانده شدن، در context window قرار میگیرد؛ این یعنی فایل مذکور در transcript، در هر لاگی که نگهداری میکنید و در هر چیزی که agent در ادامه مینویسد، وجود خواهد داشت.
پیش از این، با وجود کلید در دایرکتوری کاری agent:
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billingپس از این، با انتقال فایل به خارج از دسترس:
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/.envکاربرِ agent دیگر نمیتواند فایل را باز کند، زیرا دایرکتوری کاری دیگر حاوی آن نیست. قوانین منع دسترسی (Deny rules) در پیکربندی خودِ agent، لایهٔ دوم محافظت هستند، نه لایهٔ اول. Claude Code قوانین مجوز را از .claude/settings.json در پروژه میخواند:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}این کار از اشتباهات سهوی agent هنگام جستجو در فایلها جلوگیری میکند. اما مانع از اجرای base64 .env توسط دستورات تزریقشده نمیشود، زیرا این یک دستور shell است و نه خواندن فایل. اینکه پیش از اجرای آن دستور از شما پرسیده شود یا خیر، به حالت مجوزهای session بستگی دارد و حالت auto در اوت 2026 به پیشفرض Claude Code تبدیل میشود، بنابراین سروری که آن را مانیتور نمیکنید، دستورات بیشتری را بدون پرسش اجرا خواهد کرد. همین محدودیت برای هر چیزی که به جای تغییر مجوزها، عادتهای agent را شکل میدهد نیز صدق میکند: مهارتی که agent را به اعمال کوچکترین تغییرِ کارآمد محدود میکند، مانع از سرک کشیدن agent به فایلهایی میشود که نباید باز کند، اما این همچنان توصیهای است که میتوان مدل را از آن منصرف کرد. پیکربندی را به عنوان یک حفاظ و مجوزهای سیستمفایل را به عنوان دیوار در نظر بگیرید. همین تفکیک در داخل کانتینرها نیز اعمال میشود: فایلهای env و secretها در Docker Compose نسخهٔ دیگری از این مشکل را در یک لایه پایینتر بررسی میکند.
برای هر agent یک کاربر بدون امتیاز (unprivileged) اختصاص دهید
اگر agent با حساب کاربری شما اجرا شود، به کلیدهای SSH، اعتبارنامههای ابری و تاریخچه shell شما دسترسی پیدا میکند. ایجاد یک کاربر مجزا تنها با یک دستور انجام میشود و تمام این مخاطرات را از بین میبرد.
sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519خط آخر باید با cat: /home/you/.ssh/id_ed25519: Permission denied شکست بخورد. اگر به جای آن یک کلید نمایش داده شد، یعنی دایرکتوری home شما برای گروه یا سایر کاربران قابل خواندن است و chmod 700 ~ این مشکل را برطرف میکند. کاربر agent را به sudo اضافه نکنید و به آن دسترسی NOPASSWD فراتر از دستوری که واقعاً به آن نیاز دارد، ندهید. کاربران با حداقل امتیاز در VPS جزئیات مربوط به گروهها و sudoers را بررسی میکند. این جداسازی را هنگام اجرای بیش از یک session روی سرور در نظر داشته باشید، زیرا یک session از Claude Code میتواند متن را مستقیماً به session دیگری ارسال کند و هر آنچه در session اول وجود دارد، میتواند در یک پیام از آن کانال عبور کند.
یک مرز امنیتی دیگر نیز در VPSهای ابری ارزش اعمال دارد. سرویس metadata نمونه (instance metadata service) به یک آدرس link-local ثابت پاسخ میدهد و اغلب به هر درخواستی، اعتبارنامههای نقش (role credentials) را ارائه میدهد.
sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTاین موضوع را از سمت agent بررسی کنید. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ باید هیچ خروجی نداشته باشد و با کد غیر صفر خارج شود، زیرا بسته (packet) پیش از خروج از سرور رد میشود.
تزریق اعتبارنامه در مرز شبکه
الگویی که این مشکل را بهطور واقعی حل میکند، تزریق اعتبارنامه (credential injection) است. در این حالت، agent هرگز کلید واقعی را در اختیار ندارد. درخواست خود را از طریق یک gateway محلی ارسال میکند و gateway در مسیر خروجی، یک جایگذار (placeholder) را با secret واقعی جایگزین میکند. secret در فضای ذخیرهسازی gateway، در یک پردازش متفاوت و تحت مالکیت کاربری دیگر نگهداری میشود.
ابزار OneCLI یک پیادهسازی متنباز از این الگو با مجوز Apache-2.0 است که به عنوان یک container در کنار agent اجرا میشود. تا ژوئیه 2026، مستندات این پروژه این پیکربندی را به شرح زیر توصیف کردهاند:
git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --waitداشبورد روی پورت 10254 و gateway روی پورت 10255 گوش میدهند. شما اعتبارنامه واقعی را یکبار ذخیره میکنید، سپس به هر agent یک مقدار جایگذار به جای کلید، به همراه یک access token با دسترسی محدود میدهید که آن را در هدر Proxy-Authorization ارسال میکند. gateway درخواست خروجی را بر اساس host و path مطابقت میدهد، اعتبارنامه مربوطه را رمزگشایی کرده و جایگزین میکند. محیط اجرای agent حاوی هیچ داده ارزشمندی برای سرقت نیست.
ارزش این روش در رمزنگاری نیست. ارزش اصلی این است که پاسخ به پرسش «این agent از چه چیزی و چه زمانی استفاده کرده است» به یک پرسوجوی لاگ تبدیل میشود. شما به جای حدس زدن اینکه کدامیک از 6 محیط، نسخهای از کلید را در اختیار داشته، یک مسیر حسابرسی (audit trail) واحد را میخوانید.
ارسال رمز به پردازش، نه به محیط اجرا
اگر agent را تحت systemd اجرا میکنید، اصلاً نیازی به متغیرهای محیطی (environment variables) ندارید. LoadCredential= رمز را در یک دایرکتوری خصوصی قرار میدهد که فقط همان سرویس قادر به خواندن آن است؛ این دایرکتوری در فایل unit به صورت %d و داخل پردازش به صورت $CREDENTIALS_DIRECTORY در دسترس قرار میگیرد. این مقدار هرگز در /proc/<pid>/environ ظاهر نمیشود، بنابراین ps eww نمیتواند آن را نمایش دهد و با متوقف شدن سرویس، دایرکتوری نیز حذف میشود.
ابتدا اعتبارنامه را برای ماشین رمزنگاری کنید. این دستورات از مستندات systemd گرفته شدهاند و روی systemd 250 یا جدیدتر کار میکنند که شامل Ubuntu 24.04 و 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_keyدستور آخر sk-example-value را چاپ میکند. این نشان میدهد که فایل رمزنگاریشده روی این میزبان قابل رمزگشایی است. سپس در فایل 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-workerکد agent شما هنگام نیاز به مقدار، فایل را در $AGENT_KEY_FILE باز میکند. خواندن فایل تنها یک لحظه طول میکشد. اما یک متغیر محیطی در تمام طول عمر پردازش و در هر فرزند (child) که ایجاد میکند، باقی میماند.
استفاده از توکنهای کوتاهمدت بهجای کلیدهای بلندمدت
کلیدی که هرگز منقضی نمیشود، ماهها بعد نیز اگر در یک لاگ یا رونوشت ظاهر شود، همچنان معتبر است. هر جا که سرویس، توکن نشست (session token) ارائه میدهد، از آن استفاده کنید و کوتاهترین طول عمری که برای انجام کار مجاز است را تنظیم نمایید.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 90015 دقیقه حداقل زمانی است که AWS STS (سرویس توکن امنیتی) میپذیرد و معمولاً برای یک وظیفهٔ agent کافی است. برای GitHub، به کاربر agent یک ورود gh اختصاصی بدهید و از یک توکن با دسترسی محدود (fine-grained) استفاده کنید که فقط به مخزنی که روی آن کار میکند محدود شده باشد؛ بهطوری که gh auth token در آن نشست، نتواند به هیچ منبع دیگری دسترسی پیدا کند. ابتدا محدودسازی را بر اساس منبع (resource) و سپس بر اساس زمان اعمال کنید.
تأیید کنید، سپس به تأیید ادامه دهید
پس از هر تغییر در تنظیمات یک agent، اجرای سه بررسی توصیه میشود. این دستورات را با کاربریِ همان agent اجرا کنید، نه با کاربر خودتان.
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/userخروجی دستور اول باید کاملاً خالی باشد. دستور دوم باید ls: cannot open directory '/home/you/': Permission denied را چاپ کند. دستور سوم به شما میگوید که مسیر شبکهٔ agent چه هویتی را ارائه میدهد؛ این همان پرسشی است که الگوی gateway برای پاسخ به آن ایجاد شده است: مقدار 401 به این معناست که agent هیچ اعتبارنامهٔ GitHub اختصاصی ندارد و مقدار 200 نشان میدهد که یک اعتبارنامه به همراه دارد، بنابراین باید بدانید که کدام توکن در حال استفاده است. اگر agentها را بهصورت بدون نظارت (unattended) اجرا میکنید، کنترل هزینههای AI agent روی VPS محدودیتهای بودجهای را که با این محدودیتهای دسترسی همراه هستند، پوشش میدهد.
FAQ
آیا میتوانم به مدل اعتماد کنم که کلیدهای مرا لو نمیدهد؟
خیر، زیرا در این مدل تهدید، مدل نقش مهاجم را ندارد. عامل (agent) متن را از صفحات وب، مخازن و سیستمهای ردیابی خطا میخواند و آن متن میتواند حاوی دستورالعمل باشد. مدل هیچ راه مطمئنی برای تشخیص دستورالعملهای شما از متنی که دریافت کرده است ندارد. هر کنترلی که به انتخاب صحیح مدل وابسته باشد، در اولین باری که یک دستورالعمل تزریقشده متقاعدکننده باشد، شکست میخورد؛ بنابراین کنترل باید در سطح سیستمعامل یا شبکه اعمال شود.
آیا متغیرهای محیطی واقعاً برای اسرار عامل بد هستند؟
آنها از یک جهت خاص بد هستند: به ارث میرسند. هر پردازش فرزندی که عامل ایجاد میکند، یک کپی از آنها را دریافت میکند، از جمله اسکریپتهای ساخت (build script)، اجراکنندههای تست و هر hook نصب بستهای. این متغیرها همچنین از طریق /proc/<pid>/environ توسط همان کاربر قابل خواندن هستند، بنابراین هر چیزی که عامل اجرا میکند میتواند بدون اینکه عامل آنها را منتقل کند، آنها را بخواند. خواندن فایل در لحظه استفاده، با استفاده از LoadCredential= یا یک gateway، میزان افشا را به همان لحظه محدود میکند.
آیا قرار دادن اسرار در یک vault به تنهایی این مشکل را حل میکند؟
فقط تا حدی. vault مشکل ذخیرهسازی را حل میکند. اگر آن vault را خودتان میزبانی میکنید، نیاز به یک مرحله امنسازی جداگانه دارد، زیرا یک سرور Vaultwarden معمولاً از طریق توکن مدیریتی یا فایل پشتیبان خود هک میشود، نه از طریق آیتمهای رمزنگاریشدهای که در خود نگه میدارد. این کار مرحله آخر را حل نمیکند؛ جایی که چیزی راز را از vault بیرون میکشد و به عنوان یک متغیر محیطی به عامل میدهد، که شما را به نقطه اول بازمیگرداند. آنچه اهمیت دارد این است که چه کسی جایگزینی (substitution) را انجام میدهد. اگر عامل راز را دریافت کند، عامل راز را در اختیار دارد. اگر یک gateway یا سیستم init جایگزینی را خارج از پردازش عامل انجام دهد، عامل هرگز آن را در اختیار نخواهد داشت.
چگونه بفهمم که آیا یک عامل قبلاً چیزی را لو داده است؟
معمولاً پس از وقوع حادثه نمیتوانید متوجه شوید، و همین دلیل اصلی برای استفاده از gateway است. بدون آن، شواهد شما در تاریخچه shell، رونوشتهای عامل و لاگهای اتصالات خروجی که احتمالاً نگهداری نمیکنید، پراکنده است. با یک gateway اعتبارنامه، هر بار استفاده از یک اعتبارنامه به صورت یک خط شامل هویت عامل و یک برچسب زمانی ثبت میشود. اگر به نشت اطلاعات مشکوک هستید، ابتدا کلید را تغییر دهید (rotate) و سپس تحقیق کنید. تغییر کلید کمهزینه است، اما رسیدن به قطعیت اینطور نیست.
حداقل کاری که امروز باید انجام دهم چیست؟
تمام فایلهای .env را از دایرکتوریهایی که عاملهای شما در آنها کار میکنند خارج کنید و برای هر عامل یک کاربر بدون امتیاز (unprivileged) ایجاد کنید. این دو تغییر حدود ده دقیقه زمان میبرند و رایجترین مسیر نفوذ، یعنی خواندن فایل اعتبارنامهای که دلیلی برای قرار گرفتن در کنار کد نداشته است، را مسدود میکنند. gateway و توکنهای با عمر کوتاه گامهای بعدی هستند، نه گام اول. همین نقطه شروع برای هر runtime عامل، از جمله اجرای ایمن یک عامل خودمختار روی یک VPS، صدق میکند.