פתחתם תצוגת השוואה, והפקודה של התוקף רצה פעמיים: ConfigPoisoning עקפה ארבע הקשחות בלקוח git לעבודה עם AI
הקובץ מופיע ברשימת השינויים כמו כל קובץ אחר שערכתם, ואתם לוחצים עליו כדי לראות מה בדיוק השתנה בו. תצוגת ההשוואה נפתחת. בזמן שהיא נבנית, פקודה שמי שהרכיב את המאגר בחר כבר רצה על המחשב שלכם, בהרשאות שלכם, ואז רצה שוב.
קבוצת מחקר האיומים של GitLab פרסמה ב-2 באוקטובר את הניתוח המלא של החולשה הזאת. אבישייק מגש ודניאל אבלס, שחתומים על המחקר, מצאו אותה ב-DeepSeek-Reasonix Studio, לקוח git לשולחן העבודה שנבנה למפתחים שעובדים בצמד עם עוזרי קוד של AI. הם קראו לה ConfigPoisoning, היא קיבלה את המזהה CVE-2026-102437, והיא מתוקנת בגרסה 2.21.0 של Studio ובגרסה 1.39.3 של חבילת ה-npm. מה שהופך אותה למעניינת יותר מעוד חולשה בעוד כלי פיתוח הוא שהכלי הזה כבר התגונן מפני בדיוק סוג המתקפה הזה, בארבעה מקומות שונים, והפער שנשאר היה מתועד בהערה בקוד שלו עצמו. בהמשך נראה מה git מריץ ובשם מי, למה ארבע החסימות לא הספיקו, ולמה החוקרים מצפים למצוא את אותה טעות בכלים נוספים.
למה git מריץ בכלל פקודות של מישהו אחר
ב-git יש מנגנון שמאפשר להצמיד לקובץ תוכנית שתעבד את התוכן שלו בדרך פנימה אל המאגר ובדרך חזרה ממנו. השימוש הקלאסי בו הוא נורמליזציה: להמיר סופי שורות, לנקות שדות שמשתנים בכל שמירה, להצפין ולפענח. התוכנית שרצה בכיוון פנימה נקראת clean, וזאת שרצה בכיוון החוזר נקראת smudge. לצמד הזה קוראים filter driver.
שני קבצים נפרדים קובעים מה יקרה. הפקודה עצמה, כלומר שם התוכנית שתרוץ, רשומה בקובץ ההגדרות המקומי של המאגר, .git/config, תחת מפתח בצורה filter.<שם>.clean. אילו קבצים בכלל נשלחים אל ה-driver הזה נקבע בקובץ אחר לגמרי, .gitattributes, שורה לכל תבנית שמות. הכול מתועד ב-git ועובד בדיוק כפי שתוכנן.
הבעיה היא מי כותב את שני הקבצים. לא אתם. מי שהרכיב את המאגר. כלי פיתוח שמריץ git מול תיקייה שפתחתם יורש את ההגדרות של אותה תיקייה, ולמנגנון הזה יש עוד אחים: core.fsmonitor שמצביע על תוכנית עזר לזיהוי שינויים, core.hooksPath שמכוון לספריית ה-hooks, ו-diff.external שמחליף את מנוע ההשוואה. כל אחד מהם מריץ פקודה חיצונית בשם המשתמש במהלך עבודה שגרתית לחלוטין, ולקוחות git רגילים כבר נושאים התראות אבטחה על החלק הזה.
ארבע חסימות, והחמישית שאי אפשר לסגור ברשימה
המודול שמרכז את כל קריאות ה-git בכלי, internal/gitcmd, לא התעלם מהסיכון. בכל קריאה הוא מנטרל את core.fsmonitor ואת maintenance.auto, ועל כל השוואה הוא מוסיף את הדגלים no-ext-diff ו-no-textconv, שמכבים את ההרצה של מנוע השוואה חיצוני ושל כלי המרה לטקסט. ארבע דרכים להרצת קוד נסגרו שם במכוון.
את filter.<driver>.clean הוא לא נוגע, ולא במקרה. ארבעת האחרים הם מפתחות עם שם קבוע, ולכן אפשר לכתוב רשימה ולכבות אותם אחד אחרי השני. שם ה-driver של ה-filter נבחר על ידי מי שכתב את המאגר, והבחירה נעשית לכל קובץ בנפרד דרך .gitattributes. אין רשימה סופית לחסום, ולכן שיטת ההקשחה שעבדה על האחרים לא עובדת כאן. בדיוק את זה אמרה הערה בקוד של הכלי, עוד לפני שהחולשה נמצאה.
"ידיעה על סיכון וסגירה שלו הן לא תמיד אותו דבר", כתבו החוקרים בסיכום הניתוח. המקרה הזה הוא הדגמה נקייה של המשפט.
אז למה הפקודה רצה פעמיים?
שרשרת הקריאה שהחוקרים שחזרו קצרה. במאגר יש קובץ, בדוגמה שלהם secret.bin, ושורה ב-.gitattributes מצמידה אותו ל-filter driver בשם pwn. ב-.git/config מוגדר שה-clean של pwn הוא סקריפט שהתוקף בחר. מפתח פותח את תצוגת ההשוואה של הקובץ הזה באפליקציית שולחן העבודה, פעולה שגרתית לכל קובץ עם שינוי שעוד לא נשמר. הכלי מריץ את פקודת ההשוואה המוקשחת שלו, עם כל הדגלים במקום, מתוך desktop/workspace_changes.go.
ואז git מפעיל את filter.pwn.clean בעצמו. כדי להציג השוואה הוא צריך לייצר את ה-blob שאיתו ישווה, כלומר את התוכן אחרי שעבר את ה-filter, ואת השלב הזה אף אחד מהדגלים לא נוגע. כשהחוקרים הריצו מחדש את הפקודה המוקשחת, המטען רץ פעמיים: ההשוואה בונה blob מסונן לכל אחד משני הצדדים, ולכן ה-filter נורה פעם אחת לכל צד.
כאן נכנס פרט שמדייק את ההיקף. git status, הפקודה שסוכני קוד מריצים בדרך כלל כדי לדעת מה השתנה, רק בודק אילו קבצים שונו. הוא לא בונה את ה-blob הזה בכלל, ולכן ה-filter לא נוגע בו. זה מבודד את החולשה לרינדור של ההשוואה דווקא, ומתאים למסלול הקוד האמיתי בכלי.
איך קובץ ההגדרות מגיע אליכם בכלל
לשני הקבצים יש גורל שונה בשכפול, וזה מה שקובע כמה אתם בפועל חשופים. .gitattributes מנוהל בגרסאות ועובר עם המאגר. .git/config הוא מקומי ולא עובר. GitLab מציינת במפורש שמאגרים שמאוחסנים אצלה אינם חשופים לווקטור הזה, כי שכפול ב-HTTPS או ב-SSH לא מעביר קובצי הגדרות מקומיים.
אז איך מגיע החצי השני? ארכיון שמישהו שלח, תיקייה מסונכרנת, cache של CI, או בנייה של devcontainer. החוקרים אימתו את הממצא על commit ea28602 ועל טרום-הגרסה המתויגת studio-v2.9.0, וכתבו שהחולשה קיימת גם באפליקציית שולחן העבודה וגם בחבילת ה-npm.
יש גם מסלול ישיר יותר, וזה החלק שמעניין דווקא בהקשר של עבודה עם סוכנים. סוכן קוד שכבר רץ על המחשב של המפתח, בין אם הוא עצמו זדוני, נפרץ, או פשוט נכנע להזרקת פרומפט, מחזיק את אותה גישה לקבצים שיש למפתח עצמו. הוא יכול לכתוב את קובץ ההגדרות המורעל ישר לתוך מאגר ששוכפל בצורה תקינה לגמרי. שום ארכיון ושום תיקייה משותפת לא נדרשים.
שווה לשים לב שהדירוג של המקרה אינו אחיד. ההתראה של הפרויקט עצמו מסמנת את החולשה כקריטית בלי לפרסם ציון. רשומת ה-CVE, שהוגשה על ידי GitLab עצמה, נותנת 7.8 בדירוג גבוה, עם וקטור שדורש גישה מקומית ופעולה של המשתמש. CISA הוסיפה לאותה רשומה הערכה שלפיה לא נצפה ניצול בפועל.
זאת לא הפעם הראשונה שאותם חוקרים רואים את זה
הדפוס שמגש ואבלס מתארים חוזר, והם כבר פגשו אותו בקובץ אחר. באוגוסט הם פרסמו חולשה דומה ב-Serena, כלי שמחבר עוזרי קוד כמו Claude, Cursor ו-Copilot אל קוד מקומי דרך MCP. שם הקובץ שנתן את ההרצה היה .serena/project.yml, קובץ ההגדרות של הפרויקט, ומנוע התבניות שעיבד אותו עשה את השאר. אותם שני חוקרים, אותה טעות אמון, קובץ אחר.
ההבחנה שנופלת בין האצבעות היא בין הגדרות של הכלי להגדרות של המאגר. קובץ שיושב בתוך הפרויקט, ליד הקוד, נראה כאילו הוא חלק מהסביבה שלכם. הוא לא, הוא נכתב על ידי מי שהרכיב את הפרויקט. GitLab אומרת שמצאה את אותו סוג חולשות בכמה כלי קוד נפוצים נוספים, שנמצאים כרגע בתהליך גילוי מתואם, ושהמקרה של DeepSeek-Reasonix הוא הראשון שהיא מפרסמת במלואו.
מה זה אומר בפועל
אם אתם עובדים עם DeepSeek-Reasonix, עדכנו ל-Studio 2.21.0 או לחבילת ה-npm בגרסה 1.39.3. על גרסה ישנה יותר ההמלצה של החוקרים פשוטה: לא לפתוח תצוגת השוואה במאגר שלא שכפלתם בעצמכם ישירות.
מי שבונה כלי שמריץ git מול תיקייה שהוא לא שולט בה, וזה כמעט כל כלי פיתוח שנשען על סוכן, מקבל כאן המלצה קונקרטית. לקרוא תוכן של קבצים דרך git cat-file או git show ולהשוות בתוך התהליך עצמו, במקום לייצר תת-תהליך. ואם בכל זאת מריצים git בחוץ, לדרוס בכל קריאה את כל המפתחות הרלוונטיים, ביניהם core.fsmonitor, core.pager, core.editor, core.hooksPath, diff.external, core.sshCommand, וגם clean ו-smudge של כל שם driver, בכל המקומות שבהם הגדרה כזאת יכולה להופיע. דגל אחד לא מכסה על אחר: ה-no-ext-diff שמנטרל את diff.external לא עושה כלום ל-core.sshCommand.
ולמי שאחראי על אבטחה בארגון שבו מפתחים מריצים סוכנים, יש כאן שאלה מעשית לשאול ספק. כלי שמריץ git על המחשב של המפתח רץ עם כל ההרשאות שלו, ולכן לא מספיק לדעת איך הוא מתייחס למאגר מרוחק שלא סומכים עליו. צריך לדעת איך הוא מנטרל את ההגדרות המקומיות של התיקייה שהוא נפתח בה.
תגובות