איך מונעים מסוכני AI לדלוף מפתחות API
מפתח API בסביבת סוכן AI עלול להישלח החוצה בקריאת כלי אחת. למדו להשתמש באסימונים קצרי חיים ומוגבלים דרך שער הרשאות, בלי לחשוף את המפתח המקורי.
מה המשמעות של הוצאת סודות מסוכני AI
סוכן AI הוא תהליך Linux רגיל שמריץ פקודות. כל משתנה סביבה שהתהליך מחזיק נגיש לקוד שהוא מריץ. לכן, מפתח API בסביבת הסוכן הוא מפתח שהסוכן יכול לשלוח לכל מארח שאליו הוא יכול להגיע. הוצאת סודות מהסוכן פירושה לספק לו ידית במקום את המפתח: אסימון מוגבל בהיקפו ובמשך חייו, או מציין מקום שרכיב אחר מחליף בערך האמיתי בגבול הרשת.
אין כאן סיפור על מודל שהופך לעוין. המנגנון פשוט יותר. סוכן קורא דף אינטרנט, קובץ README או תגובה לבעיה שמכילים הוראות, ופועל לפיהן, משום שמבחינת מודל שפה אין הבדל בין טקסט שכתבת לבין טקסט שהוא שלף. זו הזרקת פקודות למודל. לאחר שהדבר קורה, היקף הנזק מוגבל בדיוק לדבר אחד: למה שהתהליך יכול לקרוא. אם עדיין לא הגדרת גבול, הרצת סוכן תכנות באופן בטוח בשרת מתארת את שכבות הבידוד שעליהן מדריך זה מתבסס.
מודל האיום במונחים פשוטים
הריצו זאת בשם המשתמש שבו הסוכן שלכם פועל.
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'סוכן עם מעטפת פקודה אינו זקוק לניצול מתוחכם כדי להוציא את הנתונים הללו. ארבעה נתיבים רגילים מספיקים לכך, וכולם נראים כמו פעילות שגרתית ביומן:
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)"]
}
}כך נמנעת טעות אנוש של הסוכן, כגון פתיחת קובץ במהלך סריקת הקבצים. עם זאת, הדבר אינו מונע מהוראה שהוזרקה להפעיל את base64 .env, משום שמדובר בפקודת shell ולא בקריאת קובץ. התייחסו לתצורה כאמצעי הגנה, ולהרשאות מערכת הקבצים כמחסום. אותה הפרדה חלה גם בתוך מכלים: קובצי סביבה וסודות ב-Docker Compose עוסק בגרסה של בעיה זו בשכבה אחת עמוק יותר.
תנו לכל סוכן משתמש בלתי־מורשה משלו
אם הסוכן פועל תחת המשתמש שלכם, הוא יורש את מפתחות ה־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. אם במקום זאת היא מדפיסה מפתח, ספריית הבית שלכם ניתנת לקריאה לקבוצה או לכל המשתמשים, ו־chmod 700 ~ מתקן זאת. אין להוסיף את משתמש הסוכן ל־sudo, ואין להעניק לו כלל NOPASSWD רחב יותר מהפקודה האחת שהוא באמת זקוק לה. משתמשים עם הרשאות מינימליות ב־VPS מפרט את נושא הקבוצות ואת הגדרות sudoers.
כדאי להוסיף גבול אבטחה נוסף ב־VPS בענן. שירות מטא־הנתונים של המופע עונה בכתובת link local קבועה, ולעיתים קרובות מוסר פרטי גישה לתפקיד לכל מי שמבקש אותם.
sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTבדקו זאת מצד הסוכן. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ אמורה להדפיס דבר ואמורה להסתיים בקוד שאינו אפס, משום שהחבילה נדחית לפני שהיא יוצאת מהמחשב.
הזרקת פרטי האימות בגבול המערכת
התבנית שפותרת את הבעיה בפועל היא הזרקת פרטי אימות. הסוכן אינו מחזיק מפתח אמיתי. הוא שולח את הבקשה דרך שער מקומי, והשער מחליף מציין מיקום בסוד האמיתי בדרך החוצה. הסוד מאוחסן בשער, בתהליך אחר ובבעלות משתמש אחר.
OneCLI הוא מימוש קוד פתוח של תבנית זו, ברישיון Apache-2.0, והוא פועל כמכל לצד הסוכן. נכון ליולי 2026, הפרויקט מתעד את ההגדרה הזו:
git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --waitלוח הבקרה מאזין ביציאה 10254, והשער מאזין ביציאה 10255. אתם מאחסנים את פרטי האימות האמיתיים פעם אחת, ולאחר מכן נותנים לכל סוכן ערך מציין מיקום במקום המפתח, וכן אסימון גישה מוגבל בהיקפו. הסוכן שולח את האסימון בכותרת Proxy-Authorization. השער מזהה את הבקשה היוצאת לפי המארח והנתיב, מפענח את פרטי האימות התואמים ומחליף אותם. בסביבת הסוכן אין שום דבר שכדאי לגנוב.
הערך כאן אינו ההצפנה. הערך הוא שהשאלה "באילו פרטי אימות השתמש הסוכן ומתי" הופכת לשאילתת יומן. אתם קוראים נתיב ביקורת אחד במקום לנחש באיזו משש סביבות נשמר עותק של המפתח.
העבירו את הסוד לתהליך, ולא לסביבה
אם אתם מפעילים את הסוכן באמצעות systemd, אינכם זקוקים כלל למשתני סביבה. LoadCredential= מציב את הסוד בתיקייה פרטית שרק אותו שירות יכול לקרוא ממנה, חושף אותו כ-%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. בכך ניתן לוודא שהקובץ המוצפן מפוענח בהצלחה במארח הזה. לאחר מכן הפנו אליו מקובץ היחידה:
[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 900Fifteen minutes הוא משך הזמן המינימלי ש-AWS STS (שירות אסימוני האבטחה) מקבל, ובדרך כלל הוא מספיק למשימה אחת של סוכן. עבור 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. הבדיקה השלישית מציגה איזו זהות מציג נתיב הרשת של הסוכן. זו השאלה שתבנית השער נועדה לענות עליה: 401 פירושו שהסוכן אינו נושא אישור GitHub משלו, ואילו 200 פירושו שהוא נושא אישור כזה. לכן עליכם לדעת באיזה אסימון מדובר. אם אתם מריצים סוכנים ללא השגחה, שליטה בעלויות של סוכני AI ב-VPS מסביר את מגבלות התקציב הנלוות למגבלות הגישה האלה.
FAQ
האם אפשר פשוט לסמוך על המודל שלא ידליף את המפתחות שלי?
לא, משום שהמודל אינו התוקף במודל האיום הזה. הסוכן קורא טקסט מדפי אינטרנט, ממאגרים וממערכות מעקב אחר תקלות, וטקסט זה עלול להכיל הוראות. למודל אין דרך אמינה להבחין בין ההוראות שלכם לבין טקסט שהוא הוריד. כל בקרת אבטחה שתלויה בכך שהמודל יבחר נכון תיכשל בפעם הראשונה שבה הוראה שהוזרקה תהיה משכנעת. לכן הבקרה חייבת להיות במערכת ההפעלה או ברשת.
האם משתני סביבה באמת מסוכנים כל כך עבור סודות של סוכנים?
הם מסוכנים באופן מסוים: הם עוברים בירושה. כל תהליך-בן שהסוכן מפעיל מקבל עותק שלהם, כולל סקריפט בנייה, מריץ בדיקות וכל hook של התקנת חבילה. אפשר לקרוא את המשתנים גם דרך /proc/<pid>/environ על ידי אותו משתמש, ולכן כל דבר שהסוכן מפעיל יכול לקרוא אותם בלי שהסוכן יעביר אותם אליו. קריאת קובץ ברגע השימוש, באמצעות LoadCredential= או gateway, מגבילה את החשיפה לאותו רגע.
האם אחסון סודות ב-vault פותר את הבעיה בפני עצמו?
רק באופן חלקי. vault פותר את בעיית האחסון. הוא אינו פותר את השלב האחרון, שבו רכיב כלשהו שולף את הסוד מה-vault ומעביר אותו לסוכן כמשתנה סביבה. כך חוזרים לאותה נקודה. השאלה החשובה היא מי מבצע את ההחלפה. אם הסוכן שולף את הסוד, הסוד נמצא בידיו. אם gateway או מערכת האתחול מבצעים את ההחלפה מחוץ לתהליך של הסוכן, הסוכן אינו מחזיק בו לעולם.
כיצד אפשר לדעת אם סוכן כבר הדליף משהו?
בדרך כלל אי אפשר לדעת זאת בדיעבד, וזו הסיבה להשתמש ב-gateway. בלעדיו, העדויות מפוזרות בין היסטוריית ה-shell, תמליל הסוכן ויומני חיבורים יוצאים שסביר שאינכם שומרים. עם credential gateway, כל שימוש באישור גישה מתועד בשורה אחת הכוללת את זהות הסוכן וחותמת זמן. אם אתם חושדים בדליפה, החליפו את המפתח תחילה וחקרו לאחר מכן. החלפת מפתח זולה, ודאות אינה זולה.
מהו המינימום שעליי לעשות היום?
הוציאו כל קובץ .env מהספריות שבהן הסוכנים שלכם פועלים, וצרו משתמש ללא הרשאות יתר עבור כל סוכן. שני השינויים האלה נמשכים כ-10 דקות וסוגרים את נתיב התקיפה הנפוץ ביותר: סוכן שקורא קובץ אישורים שלא הייתה סיבה שישב לצד הקוד. gateway ואסימונים קצרי-חיים הם השלב הבא, לא הראשון. אותה נקודת התחלה מתאימה לכל סביבת הרצה של סוכן, כולל הפעלת סוכן אוטונומי באופן בטוח ב-VPS.