איך להגן על מפתחות API מפני סוכני AI
סוכני AI עלולים לחשוף מפתחות API דרך הזרקת פרומפט. למדו כיצד להשתמש באסימונים בעלי הרשאות מוגבלות וזמן חיים קצר מאחורי שער אימות כדי למנוע דליפת סודות מהתהליך.
מה המשמעות של הרחקת סודות מסוכני AI
סוכן AI הוא תהליך Linux רגיל שמריץ פקודות. כל משתנה סביבה (environment variable) שמוחזק על ידי התהליך קריא עבור הקוד שהוא מריץ; לכן, מפתח API שנמצא בסביבת הסוכן הוא מפתח שהסוכן יכול לשלוח לכל מארח שאליו הוא מסוגל להגיע. הרחקת סודות מהסוכן משמעותה מתן "ידית" (handle) במקום המפתח עצמו: אסימון (token) בעל טווח הרשאות מוגבל וזמן חיים קצר, או מציין מיקום (placeholder) שגורם אחר מחליף בערך האמיתי בגבול הרשת.
אין כאן סיפור על מודל שהופך לעוין. המנגנון בנאלי הרבה יותר. סוכן קורא דף אינטרנט, קובץ 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יוצאת לכל מארח, כאשר הערך מוצמד למחרוזת השאילתה (query string). - פקודות
git commitו-git pushלמאגר (repository) שהסוכן מורשה לכתוב אליו. - סקריפט התקנת חבילה, המריץ קוד שרירותי תחת המשתמש של הסוכן.
- שאילתת 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המשתמש של הסוכן אינו יכול עוד לפתוח את הקובץ, כיוון שעץ העבודה כבר לא מכיל אותו. חוקי מניעה (deny rules) בהגדרות הסוכן הם שכבת הגנה שנייה, לא הראשונה. Claude Code קורא חוקי הרשאות מתוך .claude/settings.json בפרויקט:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}זה מונע טעות בתום לב שבה הסוכן פותח קובץ בזמן סריקה. ההגנה חורגת מעבר לכלי הקבצים: Claude Code בודק את החוקים הללו גם מול פקודות קבצים שהוא מזהה ב-Bash, כגון cat ו-head, ומול יעדים של הפניות (redirections) כמו < .env. זה לא מונע מהוראה מוזרקת להריץ grep -r KEY . בתוך תיקיית הפרויקט, או מסקריפט Python קצר שפותח את .env בעצמו, כיוון שתיעוד ההרשאות מציין שחוקי מניעה לא חלים על פקודות שקוראות קבצים מבלי לציין את שמם, או על תהליכי משנה שפותחים קבצים בעצמם. עבור אלו, התיעוד מפנה לארגז החול (sandbox) של Claude Code, אשר אוכף את החסימה ברמת מערכת ההפעלה. השאלה האם תתבקשו לאשר לפני הרצת פקודה כזו תלויה במצב ההרשאות של הסשן, ו-מצב אוטומטי הוא כעת מצב ברירת המחדל המובנה לסשנים אינטראקטיביים של Claude Code, כך ששרת שאינכם מנטרים יריץ יותר פקודות כאלו ללא בקשת אישור. אותה מגבלה חלה על כל דבר שמעצב את הרגלי הסוכן במקום את הרשאותיו: מיומנות שמחייבת את הסוכן לבצע את השינוי המינימלי שעובד מונעת מהרצה לגלוש לקבצים שלא היה לו צורך לפתוח, אך זו עדיין עצה שהמודל יכול להשתכנע להתעלם ממנה. התייחסו לקובץ התצורה כאל מעקה בטיחות ולהרשאות מערכת הקבצים כאל החומה. אותה הפרדה חלה בתוך מכולות: קובצי env וסודות ב-Docker Compose מכסה את הגרסה של בעיה זו שכבה אחת למטה.
הקצו לכל סוכן משתמש ללא הרשאות מיוחדות (unprivileged)
אם הסוכן רץ תחת המשתמש שלכם, הוא יורש את מפתחות ה-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. אם היא מדפיסה מפתח, המשתמש של הסוכן מסוגל לגשת לקובץ. פתיחת קובץ לפי שם דורשת הרשאת חיפוש (x) בכל תיקיית אב בנתיב, ולא הרשאת קריאה; לכן, תיקיית בית במצב 711 שאיש אינו יכול להציג את תוכנה עדיין מאפשרת גישה למפתח אם הקובץ עצמו קריא. chmod 700 ~ חוסם את הנתיב, ו-chmod 600 ~/.ssh/id_ed25519 מתקן את המפתח. אל תוסיפו את משתמש הסוכן ל-sudo, ואל תתנו לו כלל NOPASSWD רחב יותר מהפקודה הבודדת שהוא באמת צריך. משתמשים בעלי הרשאות מינימליות ב-VPS מפרט את נושא הקבוצות וקובצי ה-sudoers. זכרו את ההפרדה הזו ברגע שאתם מריצים יותר מסשן אחד על השרת, כיוון ש-סשן Claude Code אחד יכול לשלוח טקסט ישירות לאחר, וכל מה שהסשן הראשון מחזיק יכול לעבור בערוץ הזה בהודעה אחת.
כדאי להוסיף גבול נוסף ב-VPS בענן. שירות המטא-דאטה של המופע (instance metadata service) מגיב בכתובת link-local קבועה, ולעיתים קרובות הוא מחלק הרשאות תפקיד לכל מי שפונה אליו. נקודת הקצה לא בודקת איזה משתמש מקומי מבצע את הקריאה: ב-EC2, חברת AWS מציינת שקריאות IMDSv1 אינן דורשות אסימון (token), וההמלצה שלהם למניעת גישה של משתמש לשירות היא כלל firewall מקומי המבוסס על ה-ID של המשתמש.
sudo iptables -I OUTPUT 1 -p tcp -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTהכניסו את הכלל בראש הרשימה בעזרת -I במקום להוסיף אותו לסוף בעזרת -A. במארח שמריץ ufw עם מדיניות ברירת מחדל של אישור תעבורה יוצאת (allow outgoing), שרשרת ה-OUTPUT האחרונה של ufw מאשרת כל חיבור TCP חדש, ולכן כלל שנוסף לסוף לעולם לא יגיע לידי ביצוע. כללי iptables נמחקים גם הם בעת reboot אלא אם תשמרו אותם, ואם הפעלתם את נקודת הקצה IPv6 של שירות המטא-דאטה, [fd00:ec2::254], הוסיפו את אותו כלל עם ip6tables.
בדקו זאת מהצד של הסוכן. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ אמור לא להדפיס דבר ולצאת עם קוד שגיאה שאינו אפס, כיוון שהחבילה נדחית לפני שהיא עוזבת את השרת. אם הפקודה מדפיסה רשימה, הכלל לא הגיע לידי ביצוע.
הזרקת אישורי גישה בנקודת הקצה
התבנית שפותרת זאת בפועל היא הזרקת אישורי גישה (credential injection). הסוכן לעולם אינו מחזיק מפתח אמיתי. הוא שולח את הבקשה שלו דרך gateway מקומי, וה־gateway מחליף מציין מיקום (placeholder) בסוד האמיתי בדרך החוצה. הסוד נשמר באחסון של ה־gateway, בתהליך נפרד, בבעלות משתמש אחר.
OneCLI הוא מימוש קוד פתוח אחד לשיטה זו. הוא מופץ תחת רישיון Apache-2.0, למעט ספריות ה-ee/ שלו, הנושאות רישיון ארגוני נפרד המחייב מנוי לשימוש בסביבת ייצור, והוא רץ במכולות לצד הסוכן. נכון לאוקטובר 2026, הפרויקט מתעד את הגדרת ה-Docker Compose הבאה, הקוראת שלושה סודות שנוצרו מקובץ .env הנמצא לצד קובץ ה-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 וה-gateway בפורט 10255, שניהם קשורים כברירת מחדל ל-127.0.0.1. אתם שומרים את אישור הגישה האמיתי פעם אחת, ולאחר מכן נותנים לכל סוכן ערך מציין מיקום במקום המפתח, בצירוף אסימון גישה (access token) מוגבל משלו, אותו הוא שולח בתוך header מסוג Proxy-Authorization. ה-gateway מבצע התאמה של הבקשה היוצאת לפי host ו-path, מפענח את אישור הגישה התואם ומחליף אותו. סביבת הסוכן אינה מכילה דבר ששווה לגנוב.
הערך כאן אינו ההצפנה. הערך הוא שהשאלה "במה השתמש הסוכן הזה, ומתי" הופכת לשאילתת לוגים. אתם קוראים נתיב ביקורת (audit trail) אחד במקום לנחש איזו מתוך שש סביבות החזיקה עותק של המפתח.
העברת הסוד לתהליך, לא לסביבה
אם אתם מריצים את ה-agent תחת systemd, אין צורך במשתני סביבה כלל. LoadCredential= מציב את הסוד בספרייה פרטית שרק המשתמש של השירות יכול לקרוא, והיא נחשפת כ-%d בקובץ ה-unit וכ-$CREDENTIALS_DIRECTORY בתוך התהליך. הגבלה זו מבוססת על משתמש, לא על שירות, לכן הקצו לכל שירות agent משתמש משלו: כל תהליך אחר שרץ תחת אותו משתמש יכול לפתוח את הספרייה, אלא אם ה-unit משתמש גם ב-filesystem namespacing, כגון 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. זה מוכיח שהקובץ המוצפן מפוענח בהצלחה במארח זה. לאחר מכן, הפנו אליו מקובץ ה-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 כאשר הוא זקוק לערך. קריאת קובץ היא פעולה רגעית. משתנה סביבה נשאר פעיל לאורך כל חיי התהליך, בכל תהליך בן שהוא יוצר.
העדיפו אסימונים בעלי תוקף קצר על פני מפתחות בעלי תוקף ארוך
מפתח שלעולם אינו פג תוקף נותר תקף בכל פעם שהוא מופיע, גם חודשים לאחר מכן, בתוך לוג או תמלול. במקום שבו השירות מציע אסימון הפעלה (session token), השתמשו באסימון זה והגדירו לו את משך החיים הקצר ביותר שהמשימה מאפשרת.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900חמש-עשרה דקות הן פרק הזמן המינימלי ש־AWS STS (שירות אסימוני אבטחה) מקבל, וזה בדרך כלל מספיק למשימת סוכן אחת. עבור GitHub, העניקו למשתמש הסוכן חשבון gh משלו עם אסימון בעל הרשאות מוגדרות היטב (fine-grained), המוגבל למאגר (repository) היחיד שבו הוא פועל, כך ש־gh auth token בתוך אותה הפעלה יחזיר משהו שלא יכול לגשת לשום דבר אחר. הגבילו תחילה לפי משאב, ולאחר מכן לפי זמן.
אימות, ולאחר מכן אימות חוזר
כדאי להריץ שלוש בדיקות לאחר כל שינוי בהגדרות של סוכן (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, אם כי זה רק מראה שלא ניתן להציג את תוכן הספרייה; הבדיקה cat שבוצעה קודם לכן היא זו שמוכיחה שלא ניתן לקרוא קובץ בתוכה. הבדיקה השלישית מציגה את הזהות שמציג נתיב הרשת של הסוכן, וזו השאלה שתבנית ה-gateway נועדה לענות עליה: 401 משמעו שהבקשה הגיעה ל-GitHub ללא אישור ש-GitHub מקבל, כלומר ללא אישור כלל או עם אישור שאינו תקף או שפג תוקפו; 200 משמעו שהבקשה נושאת אישור תקף, ולכן עליך לדעת באיזה token מדובר. אם אתה מריץ סוכנים ללא השגחה, שליטה בעלויות של סוכני AI ב-VPS מכסה את מגבלות התקציב שמשתלבות עם מגבלות גישה אלו.
FAQ
האם אני יכול פשוט לסמוך על המודל שלא ידליף את המפתחות שלי?
לא, כיוון שהמודל אינו התוקף במודל האיומים הזה. הסוכן קורא טקסט מדפי אינטרנט, ממאגרים וממערכות לניהול משימות, וטקסט זה עלול להכיל הוראות. למודל אין דרך אמינה להבדיל בין ההוראות שלך לבין טקסט שהוא משך. כל מנגנון בקרה שתלוי בבחירה נכונה של המודל ייכשל ברגע שתופיע הוראה מוזרקת משכנעת; לכן, הבקרה חייבת להתבצע ברמת מערכת ההפעלה או הרשת.
האם משתני סביבה הם באמת כל כך גרועים עבור סודות של סוכנים?
הם גרועים בהיבט ספציפי אחד: הם עוברים בירושה. כל תהליך בן שהסוכן מפעיל מקבל עותק שלהם, כולל סקריפט בנייה, מריץ בדיקות, או כל hook של התקנת חבילות. המשתנים ניתנים לקריאה גם דרך /proc/<pid>/environ על ידי אותו משתמש, כך שכל דבר שהסוכן מריץ יכול לקרוא אותם מבלי שהסוכן יעביר אותם הלאה. קריאת קובץ ברגע השימוש, באמצעות LoadCredential= או gateway, מגבילה את החשיפה לאותו רגע בלבד.
האם אחסון סודות בכספת (vault) פותר את הבעיה כשלעצמו?
רק באופן חלקי. כספת פותרת את בעיית האחסון. אם אתה מארח את הכספת בעצמך, היא זקוקה לתהליך הקשחה משלה, כיוון ש-שרת Vaultwarden נפרץ בדרך כלל דרך אסימון הניהול או קובץ הגיבוי שלו, ולא דרך הפריטים המוצפנים שהוא מחזיק. זה לא פותר את השלב האחרון, שבו גורם כלשהו מושך את הסוד מהכספת ומעביר אותו לסוכן כמשתנה סביבה, מה שמחזיר אותך לנקודת ההתחלה. מה שקובע הוא מי מבצע את ההחלפה. אם הסוכן מושך את הסוד, הסוכן מחזיק בסוד. אם gateway או מערכת ה-init מבצעים את ההחלפה מחוץ לתהליך של הסוכן, הסוכן לעולם לא מחזיק בו.
איך אדע אם סוכן כבר הדליף משהו?
בדרך כלל לא ניתן לדעת זאת בדיעבד, וזו הטענה בעד שימוש ב-gateway. בלעדיו, הראיות שלך מפוזרות בהיסטוריית ה-shell, בתמלול של הסוכן, ובלוגים של חיבורים יוצאים שסביר להניח שאינך שומר. עם gateway לאימות הרשאות, כל שימוש בפרטי גישה מתועד בשורה אחת עם זהות הסוכן וחותמת זמן. אם אתה חושד בדליפה, בצע תחילה החלפת מפתח (rotation) ורק לאחר מכן חקור. החלפת מפתח היא פעולה זולה, בעוד שוודאות אינה כזו.
מהו המינימום שעלי לעשות היום?
העבר כל קובץ .env אל מחוץ לספריות שבהן הסוכנים שלך עובדים, וצור משתמש ללא הרשאות מיוחדות עבור כל סוכן. שני השינויים האלה לוקחים כעשר דקות וחוסמים את הנתיב הנפוץ ביותר, שבו סוכן קורא קובץ הרשאות שלא הייתה לו סיבה להימצא ליד הקוד. ה-gateway והאסימונים קצרי-הטווח הם השלב הבא, לא הראשון. אותה נקודת התחלה תקפה לכל סביבת הרצה של סוכנים, כולל הרצה בטוחה של סוכן אוטונומי על גבי VPS.