SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor

كيف تمنع وكلاء الذكاء الاصطناعي من تسريب الأسرار

قد يسرّب الوكيل مفاتيح API في استدعاء أداة واحد. تعرّف إلى الرموز قصيرة العمر ومحدودة النطاق، وضعها خلف بوابة بيانات اعتماد بدلاً من المفاتيح الحقيقية.

ما يعنيه إبقاء الأسرار خارج وكلاء الذكاء الاصطناعي

وكيل الذكاء الاصطناعي هو عملية Linux عادية تنفّذ الأوامر. ويمكن للكود الذي تنفّذه العملية قراءة كل متغير بيئة تحتفظ به. لذلك فإن وجود API key في بيئة الوكيل يعني أن الوكيل يستطيع إرساله إلى أي مضيف يمكنه الوصول إليه. يعني إبقاء الأسرار خارج الوكيل منحه وسيلة وصول بدلاً من المفتاح نفسه: رمزاً مقيّد النطاق وقصير العمر، أو قيمة نائبة يستبدل بها مكوّن آخر القيمة الحقيقية عند حدود الشبكة.

لا يتعلق الأمر هنا بتحول نموذج إلى جهة معادية. الآلية أبسط من ذلك. يقرأ الوكيل صفحة ويب أو ملف README أو تعليقاً في issue يتضمن تعليمات، ثم يتبعها، لأن النموذج اللغوي لا يميّز بين النص الذي كتبته والنص الذي جلبه. هذا هو prompt injection. وعند حدوثه، يتحدد الضرر بالكامل بعامل واحد: ما تستطيع العملية قراءته. إذا لم تضع حدوداً بعد، يشرح تشغيل وكيل برمجي بأمان على خادم مستويات العزل التي يعتمد عليها هذا الدليل.

نموذج التهديد بعبارات واضحة

شغّل هذا بصلاحيات المستخدم الذي يعمل به وكيلك.

tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'

كل سطر يطبعه يمثّل طلب HTTP واحداً يفصل بين خادمك وخادم شخص غريب. انظر الآن إلى ما هو موجود على القرص بالقرب من الوكيل.

grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'

لا يحتاج وكيل يملك shell إلى استغلال معقد لإخراج تلك البيانات. تكفي أربع طرق عادية، وتبدو الطرق الأربع كلها كعمل طبيعي في السجل:

  • curl أو fetch صادر إلى أي مضيف، مع وضع القيمة في سلسلة استعلام.
  • git commit وgit push إلى مستودع يستطيع الوكيل الكتابة فيه.
  • برنامج تثبيت حزمة يشغّل تعليماً برمجياً عشوائياً بصلاحيات مستخدم الوكيل.
  • استعلام DNS عن اسم مضيف يحتوي على القيمة، ويؤدي الغرض حتى عند حظر الاتصالات الصادرة عبر HTTP.

لا يمكنك معالجة هذا بالمراجعة وحدها. الحل هو التأكد من عدم وجود أي بيانات قيّمة يمكن للوكيل الوصول إليها.

سرّ في شجرة العمل يصبح سرّاً ضمن نافذة السياق

يقرأ الوكيل الملفات. سيقرأ ملف .env الموجود في المستودع الذي يعمل فيه، وبمجرد قراءته يصبح ضمن نافذة السياق. وهذا يعني أنه يظهر في السجل، وفي أي سجل تحتفظ به، وفي كل ما يكتبه الوكيل بعد ذلك.

قبل ذلك، عندما يكون المفتاح موجوداً في شجرة العمل التي يعمل فيها الوكيل:

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

لن يعود مستخدم الوكيل قادراً على فتح الملف، لأن شجرة العمل لم تعد تحتوي عليه. قواعد المنع في إعدادات الوكيل نفسه طبقة ثانية، وليست الطبقة الأولى. يقرأ Claude Code قواعد الصلاحيات من .claude/settings.json في المشروع:

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
  }
}

يمنع ذلك الوكيل من فتح ملف عن طريق الخطأ أثناء الاستكشاف. وتمتد هذه القواعد إلى ما بعد أدوات الملفات: يفحص Claude Code هذه القواعد أيضاً مقابل أوامر الملفات التي يتعرّف إليها في Bash، مثل cat وhead، ومقابل أهداف عمليات إعادة التوجيه مثل < .env. لكنه لا يمنع تعليمة محقونة من تشغيل grep -r KEY . داخل مجلد المشروع، ولا يمنع نص Python قصيراً يفتح .env بنفسه، لأن وثائق الصلاحيات توضّح أن قواعد المنع لا تنطبق على الأوامر التي تقرأ الملفات دون تسميتها، ولا على العمليات الفرعية التي تفتح الملفات بنفسها. بالنسبة إلى هذه الحالات، تشير الوثائق إلى وضع الحماية في Claude Code، الذي يفرض المنع على مستوى نظام التشغيل. يعتمد طلب التأكيد قبل تشغيل مثل هذا الأمر على وضع صلاحيات الجلسة. وبما أن الوضع التلقائي هو الآن وضع البدء المضمّن لجلسات Claude Code التفاعلية، فسيشغّل خادم لا تراقبه عدداً أكبر من هذه الأوامر دون طلب تأكيد. ينطبق الحد نفسه على كل ما يشكّل عادات الوكيل بدلاً من صلاحياته: المهارة التي تلزم الوكيل بأصغر تغيير ينجح تمنع التشغيل من التوغّل في ملفات لم يكن ينبغي له فتحها، لكنها تظل توجيهاً يمكن إقناع النموذج بتجاوزه. تعامل مع الإعدادات بوصفها حاجز حماية، ومع صلاحيات نظام الملفات بوصفها الجدار. وينطبق الفصل نفسه داخل الحاويات: تغطي ملفات البيئة والأسرار في Docker Compose إصدار هذه المشكلة في طبقة أدنى.

امنح كل agent مستخدمًا خاصًا غير مميّز

إذا شغّلت 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. إذا طبع مفتاحًا بدلاً من ذلك، فهذا يعني أن مستخدم agent يستطيع الوصول إلى الملف. يتطلب فتح ملف باسمه صلاحية البحث (x) على كل دليل يقع فوقه، وليس صلاحية القراءة. لذلك، يظل بإمكان مفتاح موجود في home directory بالوضع 711، ولا يستطيع أي مستخدم آخر إدراج محتوياته، أن يُفتح إذا كان ملف المفتاح نفسه قابلاً للقراءة. يمنع chmod 700 ~ الوصول عبر المسار، ويصلح chmod 600 ~/.ssh/id_ed25519 المفتاح. لا تضف مستخدم agent إلى sudo، ولا تمنحه قاعدة NOPASSWD أوسع من الأمر الوحيد الذي يحتاج إليه فعلاً. يشرح المستخدمون ذوو أقل قدر من الصلاحيات على VPS تفاصيل المجموعات وsudoers. تذكّر هذا الفصل عند تشغيل أكثر من جلسة على الخادم، لأن جلسة Claude Code واحدة يمكنها إرسال نص مباشرة إلى جلسة أخرى، ويمكن لأي شيء تحتفظ به الجلسة الأولى أن يعبر هذه القناة في رسالة واحدة.

توجد حدود أخرى ينبغي إضافتها على cloud VPS. تستجيب خدمة بيانات التعريف الخاصة بالمثيل على عنوان link-local ثابت، وغالبًا ما تمنح بيانات اعتماد الدور لأي جهة تطلبها. لا يتحقق الطرف النهائي من المستخدم المحلي الذي يجري الاتصال. في EC2، توثّق AWS أن طلبات IMDSv1 لا تحتاج إلى token، وأن توصيتها الخاصة بمنع مستخدم واحد من الوصول إلى الخدمة هي قاعدة جدار ناري محلية تعتمد على معرّف ذلك المستخدم.

sudo iptables -I OUTPUT 1 -p tcp -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECT

أدرج القاعدة في أعلى السلسلة باستخدام -I بدلاً من إلحاقها باستخدام -A. على مضيف يشغّل ufw مع سياسة السماح الافتراضية بالاتصالات الصادرة، تقبل قاعدة OUTPUT الأخيرة في ufw كل اتصال TCP جديد، ولذلك لن تصل المعالجة أبدًا إلى قاعدة ملحقة. تختفي قواعد iptables أيضًا عند إعادة التشغيل ما لم تحفظها، وإذا فعّلت نقطة النهاية IPv6 الخاصة بخدمة بيانات التعريف، فأضف القاعدة نفسها باستخدام [fd00:ec2::254] وip6tables.

تحقق من ذلك من جهة agent. يجب أن يطبع sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ لا شيء وأن يخرج بحالة غير صفرية، لأن الحزمة تُرفض قبل أن تغادر الخادم. إذا طبع قائمة، فهذا يعني أن المعالجة لا تصل إلى القاعدة.

حقن بيانات الاعتماد عند الحد الفاصل

النمط الذي يحل المشكلة فعلياً هو حقن بيانات الاعتماد. لا يحتفظ الوكيل بمفتاح حقيقي قط. بل يرسل طلبه عبر بوابة محلية، وتستبدل البوابة قيمة نائبة بالسر الحقيقي أثناء خروجه. يُخزَّن السر في مساحة تخزين البوابة، ضمن عملية مختلفة، ويملكه مستخدم مختلف.

OneCLI أحد التطبيقات مفتوحة المصدر لهذا النمط. يخضع لترخيص Apache-2.0 باستثناء أدلة ee/، التي تحمل ترخيصاً مؤسسياً منفصلاً يتطلب اشتراكاً للاستخدام في الإنتاج، ويعمل في حاويات بجانب الوكيل. اعتباراً من أكتوبر 2026، يوثّق المشروع إعداد .env هذا، الذي يقرأ 3 أسرار مُنشأة من ملف بجانب ملف compose:

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 --wait

أنشئ نسخة احتياطية من ملف .env هذا، لأن فقدان SECRET_ENCRYPTION_KEY يؤدي إلى عزل كل سر مخزّن معه. تستمع لوحة المعلومات على المنفذ 10254، وتستمع البوابة على المنفذ 10255، ويرتبط كلاهما افتراضياً بالعنوان 127.0.0.1. تخزّن بيانات الاعتماد الحقيقية مرة واحدة، ثم تمنح كل وكيل قيمة نائبة بدلاً من المفتاح، بالإضافة إلى رمز وصول مقيّد بنطاقه، ويرسله في ترويسة Proxy-Authorization. تطابق البوابة الطلب الصادر وفق المضيف والمسار، ثم تفك تشفير بيانات الاعتماد المطابقة وتستبدلها. لا تحتوي بيئة الوكيل على أي قيمة تستحق السرقة.

القيمة هنا ليست التشفير. بل إن السؤال «ما الذي استخدمه هذا الوكيل ومتى؟» يتحول إلى استعلام في السجل. تقرأ مسار تدقيق واحداً بدلاً من التخمين بشأن أي من 6 بيئات احتوت على نسخة من المفتاح.

سلِّم السر إلى العملية، لا إلى البيئة

إذا شغّلت الوكيل باستخدام systemd، فلست بحاجة إلى متغيرات البيئة على الإطلاق. يضع LoadCredential= السر في دليل خاص لا يستطيع قراءته إلا مستخدم الخدمة، ويُعرَض باسم %d في ملف الوحدة وباسم $CREDENTIALS_DIRECTORY داخل العملية. هذا التقييد يعتمد على المستخدم، لا على الخدمة. لذلك امنح كل خدمة وكيل مستخدماً خاصاً بها. يمكن لأي عملية أخرى تعمل بالمستخدم نفسه فتح الدليل، ما لم تستخدم الوحدة أيضاً عزل مساحة أسماء نظام الملفات، مثل PrivateMounts=yes، الذي يخفيه عن الخدمات الأخرى. لا تظهر القيمة في /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. وهذا يثبت أن الملف المشفّر يمكن فك تشفيره على هذا المضيف. ثم أشر إليه من الوحدة:

[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_KEY_FILE عندما يحتاج إلى القيمة. قراءة الملف تحدث في لحظة محددة. أما متغير البيئة فيستمر طوال مدة العملية، وفي كل عملية فرعية ينشئها.

فضّل الرموز المميّزة قصيرة العمر على المفاتيح طويلة العمر

يبقى المفتاح الذي لا تنتهي صلاحيته صالحاً كلما ظهر لاحقاً، بعد أشهر، في سجل أو نص مُفرَّغ. عندما تتيح الخدمة رمزاً مميّزاً للجلسة، استخدم رمز الجلسة وحدّد أقصر مدة تسمح بها المهمة.

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/agent-readonly \
  --role-session-name agent-review \
  --duration-seconds 900

تقبل AWS STS (خدمة رموز الأمان) مدة لا تقل عن 15 دقيقة، ويكون ذلك كافياً عادةً لمهمة وكيل واحدة. بالنسبة إلى GitHub، امنح مستخدم الوكيل حساب تسجيل دخول خاصاً به عبر gh، مع رمز مميّز دقيق النطاق ومقيد بالمستودع الواحد الذي يعمل عليه، بحيث يعيد gh auth token داخل تلك الجلسة قيمة لا يمكنها الوصول إلى أي شيء آخر. حدّد النطاق حسب المورد أولاً، ثم حسب المدة.

تحقّق، ثم واصل التحقّق

من المفيد تشغيل ثلاثة اختبارات بعد أي تغيير في إعدادات الوكيل. شغّلها كمستخدم الوكيل، وليس كمستخدمك.

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، لكن ذلك يثبت فقط تعذّر عرض محتويات الدليل؛ أما اختبار cat السابق فهو الذي يثبت تعذّر قراءة ملف داخله. يوضّح الاختبار الثالث الهوية التي يقدّمها مسار الوكيل على الشبكة، وهذا هو السؤال الذي صُمّم نمط البوابة للإجابة عنه: تعني 401 أن الطلب وصل إلى GitHub من دون بيانات اعتماد يقبلها GitHub، إما لعدم وجود بيانات اعتماد أصلاً أو لانتهائها أو عدم صلاحيتها، بينما تعني 200 أن الطلب يحمل بيانات اعتماد صالحة، ولذلك يجب أن تعرف أي token يستخدمه. إذا كنت تشغّل الوكلاء دون إشراف، يوضّح التحكم في تكاليف وكلاء الذكاء الاصطناعي على VPS حدود الميزانية التي تقترن بحدود الوصول هذه.

FAQ

هل يمكنني الوثوق بالنموذج ببساطة كي لا يسرّب مفاتيحي؟

لا، لأن النموذج ليس المهاجم في نموذج التهديد هذا. يقرأ الوكيل النصوص من صفحات الويب والمستودعات وأنظمة تتبع المشكلات، وقد تحتوي هذه النصوص على تعليمات. لا يملك النموذج طريقة موثوقة للتمييز بين تعليماتك والنص الذي جلبه. يفشل أي إجراء أمني يعتمد على اختيار النموذج الصحيح في أول مرة تكون فيها التعليمات المحقونة مقنعة، لذلك يجب أن يكون الإجراء داخل نظام التشغيل أو الشبكة بدلاً من ذلك.

هل متغيرات البيئة سيئة فعلاً بالنسبة إلى أسرار الوكلاء؟

هي سيئة بطريقة محددة: إذ ترثها العمليات. تحصل كل عملية فرعية ينشئها الوكيل على نسخة منها، بما في ذلك script البناء وtest runner وأي hook لتثبيت الحزم. ويمكن للمستخدم نفسه أيضاً قراءة هذه المتغيرات عبر /proc/<pid>/environ، لذلك يستطيع أي شيء يشغّله الوكيل قراءتها من دون أن يمررها الوكيل إليه. أما قراءة ملف عند لحظة الاستخدام، باستخدام LoadCredential= أو gateway، فتحدّ من التعرض إلى تلك اللحظة.

هل يحل وضع الأسرار في vault هذه المشكلة بمفرده؟

يحلها جزئياً فقط. يعالج vault مشكلة التخزين. وإذا استضفت هذا الـvault بنفسك، فإنه يحتاج إلى تشديد أمني خاص به، لأن اختراق خادم Vaultwarden يحدث عادةً عبر admin token أو ملف النسخ الاحتياطي، وليس عبر العناصر المشفّرة التي يحتفظ بها. ولا يعالج ذلك الخطوة الأخيرة، حين يسحب شيء ما السر من الـvault ويسلّمه إلى الوكيل باعتباره متغير بيئة، فتعود إلى الوضع نفسه. المهم هو الجهة التي تنفذ الاستبدال. إذا جلب الوكيل السر، فالسر بحوزته. وإذا نفّذ gateway أو نظام init الاستبدال خارج عملية الوكيل، فلن يحتفظ الوكيل بالسر.

كيف أعرف ما إذا كان وكيل قد سرّب شيئاً من قبل؟

لا يمكنك عادةً معرفة ذلك بعد وقوعه، وهذا هو سبب استخدام gateway. من دونه، تكون الأدلة موزعة بين سجل shell history، وسجل محادثة الوكيل، وسجلات الاتصالات الصادرة التي من المحتمل أنك لا تحتفظ بها. مع credential gateway، يكون كل استخدام لبيانات اعتماد في سطر واحد يتضمن هوية الوكيل وطابعاً زمنياً. إذا اشتبهت في حدوث تسريب، فقم بتدوير المفتاح أولاً ثم ابدأ التحقيق. تدوير المفتاح رخيص، أما اليقين فليس كذلك.

ما الحد الأدنى الذي ينبغي أن أفعله اليوم؟

انقل كل ملف .env خارج الأدلة التي تعمل فيها وكلاؤك، وأنشئ مستخدماً واحداً غير مميّز لكل وكيل. يستغرق هذان التغييران نحو عشر دقائق، ويغلقان المسار الأكثر شيوعاً، وهو قراءة الوكيل لملف بيانات اعتماد لم يكن هناك سبب لوجوده بجانب الشيفرة. أما gateway والرموز قصيرة العمر فهما الخطوة التالية، وليسا الخطوة الأولى. تنطبق نقطة البداية نفسها على أي runtime للوكيل، بما في ذلك تشغيل وكيل مستقل بأمان على VPS.

هل كان هذا المقال مفيدًا؟