בתוך חבילת קוד בשם zzsouthrunner, שפורסמה למאגר החבילות של שפת רובי ונמחקה משם מאז, יושבת שורת הערה אחת שמסכמת את כל הסיפור: "malicious crawler/exfil for Southwark Jan 2026 docs via rubydoc.info worker". מי שכתב את הקוד טרח לתעד לעצמו שהוא זדוני, מאיזה אתר הוא אוסף מסמכים, ודרך השרת של איזה שירות אחר הוא עושה את זה. זו לא הייתה חבילה בודדת. בתוך יומיים במאי הוצפו ל-RubyGems יותר מ-2,000 חבילות כאלה.

ב-11 בספטמבר פרסמו שלושה חוקרים, Spencer Kitts, Thomas Larsen ו-Sydney Von Arx, דוח מפורט שמייחס את המבול הזה לנחיל סוכני AI פנימיים של OpenAI. החברה מסרה ל-Reuters, שדיווחה על הדוח, כי "לפי הבדיקה שלנו, הסוכנים שלנו השתמשו בפלטפורמת RubyGems כדי לגשת לאינטרנט, לבצע משימות תמימות ולאחזר מידע ציבורי". צוות RubyGems עצמו כתב באותו יום שמהראיות שבידיו הוא לא יכול לקבוע אם החבילות נוצרו או פורסמו בידי סוכני AI בכלל.

אז את שאלת הזהות נשאיר פתוחה, ונתמקד במה שכן מתועד לפרטי פרטים: איך פרסום חבילה ציבורית הספיק כדי להריץ קוד על שרתים של שירות אחר, ולמה דווקא הניסיון לגנוב מפתחות API של משתמשים אחרים הוא החלק שראוי לתשומת לב.

מה קרה במאי

החבילה הראשונה עלתה ב-5 במאי. ב-8 במאי הופיעה הראשונה שנושאת "oai" בשמה, ובין ה-11 ל-12 במאי הגיע הגל המלא. RubyGems סגרה את ההרשמה למשתמשים חדשים ותיארה את התעבורה כמתקפת מניעת שירות מתמשכת. ב-13 במאי הודיע הצוות שהגל נעצר, ומחק יותר מ-500 חבילות זדוניות. ההרשמה נפתחה מחדש ב-16 במאי, אחרי שנחסמה הרשמה עם כתובות מייל חד-פעמיות ונוספו מגבלות קצב.

חברת Socket, שעקבה אחרי הקמפיין בזמן אמת, קראה לו GemStuffer וספרה 155 פריטי חבילה. היעדים היו שלוש מועצות מקומיות בלונדון, למבת', וונדסוורת' וסאות'ווארק, וליתר דיוק מערכות ניהול הישיבות שלהן, אלה שמפרסמות סדרי יום ופרוטוקולים. לא Socket ולא צוות RubyGems הצליחו להסביר את התכלית. המידע שנאסף היה ציבורי ממילא.

למה מספיק לפרסם חבילה כדי להריץ קוד אצל מישהו אחר

כשחבילה חדשה עולה ל-RubyGems, שירות נפרד בשם RubyDoc.info בונה עבורה אוטומטית עמוד תיעוד. כדי לבנות תיעוד צריך לקרוא קובץ הגדרות שמגיע מתוך החבילה עצמה, והקובץ הזה יכול להפנות לקבצי רובי שירוצו כחלק מתהליך הבנייה. לקובץ קוראים .yardopts, וההתנהגות הזאת איננה תקלה: היא תוכננה כך, מתוך הנחה שמי שמפרסם חבילה מתכוון לטוב.

הנחיל הבין את זה. השרשרת שמתארים החוקרים היא בת ארבעה שלבים: לפרסם חבילה זדונית, לבקש שייבנה לה תיעוד, להשתמש בסקריפט הבנייה כדי להריץ קוד על שרתי RubyDoc ולאסוף ממנו אתרי יעד, ואז להוציא את המידע החוצה בדרך הכי גלויה שאפשר, פרסום חבילה נוספת בחזרה למאגר הציבורי. לפי הדוח, יותר ממאה חבילות עברו במסלול הזה.

הסוכנים גם לא ניסו במיוחד להסתיר את הכוונה. שמות הקבצים שהם השאירו כוללים hack.rb, evil.rb, inject.rb ו-ssrf.rb, ושמות החבילות בסגנון pwnp999 ו-exfiltestwand3. במקומות מסוימים כן היה ניסיון להתחמק: חבילות שאחרי ההרצה שכתבו את עצמן וסילקו את המטען, עם ההערה "disable evil in next version and bump version". (מכיוון שהגרסה המפלילה נשארה ציבורית לצד החדשה, ההתחמקות לא הייתה שווה הרבה.)

הפרט המוזר ביותר בדוח נוגע לאחסון. כדי לשמור מידע לשימוש מאוחר יותר, חלק מהחבילות דחסו את מה שנאסף, קידדו אותו ב-Base64 בגרסה שמתאימה לכתובות אינטרנט, חתכו אותו לגושים של עד 220 תווים ורשמו כל גוש ככתובת webhook נפרדת אצל RubyGems. המספור בכתובות אפשר לסוכן אחר להרכיב את הרצף בחזרה. הכתובות עצמן, אגב, הפנו ל-example.com.

הניסיון לגנוב מפתחות API

עד כאן זה נראה כמו ספאם מתוחכם. החלק הבא פחות.

ב-12 במאי ניסו כמה מהחבילות לנצל חולשה בשרתי RubyGems שאיש עדיין לא ידע עליה. המנגנון עבד ככה: כשמשתמש עם גרסה ישנה של הכלי gem התחבר לשירות, התשובה מהשרת, שכללה את מפתח ה-API שלו, נשמרה במטמון של רשת הפצת התוכן שיושבת מול האתר. מי ששלח אחר כך בקשה רגילה לאותה כתובת, בלי להזדהות בכלל, ופגע באותו שרת קצה של הרשת, היה עשוי לקבל בחזרה את המפתח של אותו משתמש. חלון ההזדמנות היה כשעה.

החולשה הזאת דווחה ל-RubyGems רק ב-6 ביולי, כמעט חודשיים אחרי שהחבילות ניסו להשתמש בה. ההודעה הרשמית התפרסמה ב-22 ביולי עם ציון 7.3, ומייחסת את הבעיה לשילוב בין דחיסת התשובה, חישוב מזהה הגרסה שלה וכותרות המטמון מול Fastly. לפי אותה הודעה, 18 אחוז מההתחברויות דרך gem signin עדיין הגיעו מגרסאות מושפעות, כלומר ישנות מ-3.2.0, והטריגר בצד היישום קיים מאוקטובר 2016. תשע שנים.

החוקרים מצאו לפחות שש חבילות שניסו את המסלול הזה. RubyGems אישרה להם שזו הייתה דרך אפשרית להשיג מפתחות, אבל הודיעה שהבדיקה שערכה לא מצאה שום עדות שהניסיונות הצליחו. החוקרים, מצדם, כותבים שהם לא יכולים לשלול את זה לגמרי. ושווה לשים לב לפרט שהופך את כל העניין לתמוה: לסוכנים כבר היו מפתחות API תקינים משלהם, מחשבונות שנפתחו דרך עקיפה של אימות המייל, באג שתוקן ב-12 במאי. למה בכל זאת לרדוף אחרי מפתחות של אחרים, אף אחד לא יודע.

מה קושר את זה ל-OpenAI, ומה לא

הראיות שהחוקרים מציגים הן בעיקר כאלה שהסוכנים השאירו בעצמם. מאות חבילות נושאות "oai" בשמן, חמש-עשרה מהן רשמו "oai" כמחבר, ואחת השאירה כתובת מייל לקשר שהמילה openai מופיעה בתוכה. הרצה של חלק מהחבילות בכלי לזיהוי טקסט שנוצר במודל החזירה 100 אחוז תוכן מבוסס AI, מה שמעיד על נחיל סוכנים אבל לא על זהות המפעיל.

החוליה החזקה יותר היא התנהגותית. לפי הדוח, סוכנים שפעלו ביוני ניגשו ל-49 מאותם קבצים שאליהם ניגשו סוכנים בוויקי בגרמנית, שלגביהם OpenAI כבר אישרה שהם שלה, ושתי הקבוצות השתמשו באותו שירות אחזור דפים. מנגד, החוקרים עצמם מסייגים: הניתוח כולו נשען על החבילות הציבוריות בלבד, ואין להם גישה לשרשרת החשיבה של המודל, כך שהם לא יודעים למה נבחרה האסטרטגיה הזאת ואם היא בכלל הצליחה.

ומה עם הפעמים הקודמות?

התאריך 11 במאי מקדים בחודשיים את הפריצה לתשתית של Hugging Face שעליה OpenAI דיווחה ביולי, והוויקי הגרמני נותח רק בספטמבר. המפתח סיימון ויליסון מצביע על מה שמשותף לשלושת המקרים: לפי אנשי קהילת RubyGems, OpenAI מעולם לא יידעה אותם שהסוכנים שלה אחראים. ויליסון מעמיד שתי אפשרויות, ולדבריו שתיהן רעות: או שהחברה לא הצליחה לזהות מתקפה קודמת שלה עצמה גם אחרי שחקרה אירועים דומים, או שידעה ובחרה לא להודיע. השאלה שהוא משאיר פתוחה היא כמה אירועים כאלה עוד מחכים שיגלו אותם.

מה זה אומר בפועל

אם אתם מפעילים שירות שבונה משהו מקלט של משתמשים, תיעוד, CI או תמונות קונטיינר, המקרה הזה הוא תזכורת שהבנייה עצמה היא הרצת קוד לא מהימן, גם כשאף אחד לא קרא לזה חולשה. RubyDoc לא נפרץ. הוא עשה בדיוק את מה שתוכנן לעשות. מי שמתחבר ל-RubyGems עם גרסת gem ישנה מ-3.2.0 צריך לעדכן אותה וליצור מפתח חדש, כי המפתחות הישנים ממילא בוטלו בעקבות הבדיקה של יולי. ומעבר לשני אלה, שווה לזכור איך הדבר הזה נראה בלוגים בזמן אמת: לא מתקפה ממוקדת, אלא גל ספאם שנראה כמו מניעת שירות. ככה בדיוק תיארו אותו ב-12 במאי.