הגדרת SAST באמצעות CodeQL ב-GitHub Actions: איתור פגיעויות בקוד

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

היכולות שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1112

הגדרת CodeQL לא משתרשת: התראות שווא ורעש בדוחות

פורמלית, הבדיקה קיימת ב-CI, הענף מוגן, הדוח נפתח — ובכל זאת אף אחד לא מסתכל בו. הגדרת CodeQL מצטמצמת לרוב להפעלה של סט שאילתות מוכן «להכול» בלי לציין שלבי build: המנתח מפרסר את הקוד באופן חלקי, נתיב הנתונים נשבר בגבולות המודולים, ובדוח צפות עשרות ממצאים שאינם קשורים לפגיעויות.

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

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

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

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

מה משתנה אחרי ההטמעה: זמן הבדיקה וכיסוי הקוד

הבדיקה מפסיקה להיות צוואר הבקבוק של המיזוג

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

הכיסוי של הניתוח נראה במספרים

מגדירים את ההיקף: קומיטים חדשים, ענפי שחרור, שפות הפרויקט. המדד הוא שיעור השורות שהשתנו ועברו בדיקה; בהתחלה הוא בדרך כלל מתחת למחצית, אחרי הפעלת בדיקת חובה על מיזוג הוא שואף ל-100% בקוד חדש, וההיסטוריה נמשכת פנימה במנות. אזורים לא מכוסים נראים לעין, ולא «איפשהו, כנראה».

סוגי פגמים שלא מגיעים למיזוג

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

פגמים הופכים למדד, ולא לפעולה חד-פעמית

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

מצבי הפעלה של הבדיקות: השוואה בין אפשרויות ומגבלות

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

אפשרות הפעלה מתי מתאימה מגבלות הניתוח
ניתוח קבצים שהשתנו ב-push ענפי משימות, משוב מהיר לכותב התיקון שרשראות בין-קובציות לא נבנות: דליפה דרך שניים-שלושה מודולים תישאר בלתי מזוהה
בדיקה על כל קומיט ב-pull request בדיקת חובה לפני מיזוג לענף הראשי בסיס הנתונים נבנה מחדש בכל עדכון; בעריכות תכופות ה-builds עומדים בתור
הרצה מלאה לפי לוח זמנים בקרה לילית של כל המאגר, כולל מודולים שלא נגעו בהם זמן רב הממצא מגיע באיחור, וקשה יותר למצוא את האשם בין עריכות מקבילות
ניתוח ענפי שחרור ותגים לפני בניית הארטיפקט שיוצא לפרודקשן גילוי מאוחר: התיקון כבר מונח מעל ודורש בחינה מחדש
הפעלה ידנית חקירת אירוע או בדיקה לפני ביקורת חיצונית אין סדירות: הרצה שהוחמצה לא תובחן על ידי איש

איך המצבים משתלבים

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

הגדרת CodeQL שלב אחר שלב: מביקורת המאגרים ועד למסירה

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

  1. מיפוי מלאי של המאגרים. אוספים רשימה של שירותים, שפות וענפי שחרור, ומסמנים בנפרד פרויקטים ארכיוניים — עליהם לא מריצים ניתוח.
  2. בניית בסיס הקוד. פורשים בסיס נתונים לכל שפה: לשפות interpreted — ניתוח בלבד, לשפות build — מעבר עם קומפילציה; תלויות ויצירת קוד מתעדים בתיאור המשימה.
  3. סט כללי שאילתות. לוקחים סט טיפוסי כבסיס, מוסיפים כללים לסטאק שלנו ומכבים את הרועשים — והתוצאה מנוהלת בגרסאות כקובץ תצורה, ולא כעריכות ידניות בממשק.
  4. אינטגרציה ל-CI. על שינויים — מעבר מהיר, בהרצה הלילית — מלא; הדוחות לא חוסמים מיזוג אם המנתח עצמו נשבר.
  5. סינון התראות שווא. כל מקבץ ממצאים עוברים עליו עם המפתחים: חלק סוגרים בתיקון קוד, חלק בהחרגה עם סיבה ובעלים.
  6. בדיקה על שינויים אמיתיים. מריצים על ענפים אמיתיים: האם הניתוח תופס פגיעות חדשה והאם אינו שובר את ה-build במקום שבו הוא לא אמור.
  7. מסירה. מוסרים תצורות, רשימת החרגות עם נימוקים, מדריך לקריאת הדוחות ונוהל הסלמה של ממצאים של אחרים.

מה מוסרים בסיום: תצורות, כללים ונהלים

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

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

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

מקרה בוחן: צמצמנו את הרעש ואת זמן הבדיקה בזרימת השינויים

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

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

שיעור התראות השווא ירד מ-62% ל-14%. זמן הבדיקה על בקשת מיזוג התקצר מ-18–25 דקות ל-4–6 — התוצאה מגיעה לפני שהכותב עבר למשימה הבאה. שיעור הממצאים שנסגרו לפני המיזוג עלה מ-30% ל-87%: תיקון ברגע כתיבת הקוד אורך דקות, וטיפול בהשלכות בסביבת העבודה — שעות.

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

מה לעשות אם הבדיקות כבר פרוסות, אבל מתעלמים מהדוחות?

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

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

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

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

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

ספקות טכניים: build, מאגרים סגורים ועומס

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

ה-build לא שגרתי — הניתוח בכלל יעבוד?

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

המאגרים סגורים, והקוד לא אמור לצאת החוצה

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

כמה זה יאט את ה-CI?

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

יש לנו כמה שפות בפרויקט אחד

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

אפשר להוסיף כללי בדיקה משלנו?

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

נדבר על המשימה: נתאים את מערך הבדיקות לפרויקט שלכם

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