דפי תוכן יכלו להכיל סקריפט
תוכן עשיר שהוזן במערכת הניהול הוצג בחנות כ־HTML גולמי, פתח ל־Stored XSS.
ניקוי ה־HTML של התוכן לפני ההצגה, באותו שבוע שבו החנות נכנסה לריפו.
רוכשי דירות חדשות של יזמית נדל״ן ישראלית גדולה יכולים עכשיו לרהט ולשדרג את הבית מספקים נבחרים, בחנות אחת בעברית. הם מזינים כרטיס אשראי פעם אחת, והמערכת מפצלת את התשלום בין הספקים, מסנכרנת כל לילה את הקטלוגים של כולם, ונותנת לצוות של היזמית מערכת ניהול אחת לכל זה.
היזמית רצתה להציע לרוכשי הדירות שלה קטלוג נבחר: מטבחים, תאורה, מכשירי חשמל ועבודות גמר, שנמכרים על ידי ספקים עצמאיים, במחירים ששונים לפני מסירת הדירה ואחריה. זה יוצר שלוש בעיות בבת אחת. כל ספק מפרסם את המוצרים שלו בגיליון או בטבלת אינטרנט משלו, ולכן צריך לבנות את הקטלוג מחדש כל לילה מקבצים שמשתנים, בלי לאבד את השינויים שהצוות של היזמית עשה. הרוכשים צריכים להתחבר מול ה־CRM של היזמית ולראות רק את מה שרלוונטי לדירה שלהם. ועסקה אצל ספק התשלום שייכת למוכר אחד בלבד, כך שסל עם שלושה ספקים לא יכול להיות חיוב אחד פשוט.
התרשים רחב מהמסך: החליקו הצידה לצפייה בכולו →
ה־API הציבורי וה־API הניהולי רצים כשני hosts נפרדים, כך שהצד שמול הרוכשים לא חושף אף נקודת קצה של ניהול. שניהם, וגם ה־worker, רושמים את אותה ליבה דרך קריאת רישום משותפת אחת, ולכן החוקים העסקיים לא יכולים להתפצל ביניהם. כל שירות חיצוני יושב מאחורי interface, והגדרה ריקה בוחרת stub שנכשל בצורה בטוחה, וכך מחשבי פיתוח לא נוגעים בתשלומים, SMS או דואר אמיתיים.
הכרטיס של הרוכש מאושר פעם אחת על סכום הסל, מול המוכר של הפלטפורמה עצמה. אחרי האישור נוצרת ההזמנה, והחלק של כל ספק נגבה בנפרד מול חשבון המוכר שלו. חיוב שנכשל משאיר את ההזמנה ב־Pending להתאמה, ולא מוחק הזמנה שהרוכש כבר שילם עליה. המחיר: כל ספק צריך KYC מלא אצל ספק התשלום, ולכן דגל אחד מאפשר לחזור לחיוב של כל הסל ולהתחשבנות עם הספקים מחוץ למערכת.
ה־callback של התשלום אנונימי, וכל אחד יכול לשלוח אותו. הוא משמש רק כהתראה: השרת קורא את העסקה מחדש מספק התשלום, מתאים את האסמכתא שהוא עצמו הנפיק לסל, בודק שהסכום שווה לסכום הסל, ובמשלוח חוזר מחזיר את ההזמנה הקיימת. פרטי ההזמנה נשמרים על הסל לפני ההפניה לתשלום, כך שה־callback אף פעם לא צריך לשאת אותם.
הצוות יכול לדרוס חמישה שדות של מוצר ספק (תיאור, שני המחירים, סטטוס פעיל וקטגוריה). הדריסות נשמרות בטבלה נפרדת, ובכל סנכרון ובכל עריכה הן מחושבות לעמודות Effective*. החנות קוראת רק את העמודות האלה, השינוי חל מיד, והוא שורד את הייבוא של הלילה הבא. המחיר: כל מסלול כתיבה חייב להפעיל את החישוב, והבדיקות אוכפות את זה.
מוצר שנעלם מהפיד מוסתר ולא נמחק, ואם הוא חוזר הוא חוזר עם הדריסות שלו. כל הזמנה שומרת תמונת מצב מלאה של פריטים, מחירים, ספקים, וריאנטים וכתובות, כך שהיסטוריית ההזמנות נשארת נכונה גם כשהקטלוג משתנה.
הסנכרון הלילי רץ כתהליך עצמאי עם הגדרות משלו, מחוץ ל־IIS, כך ש־app pool שממחזר את עצמו או נרדם לא יכול לדלג על לילה. ייבואים של אותו ספק רצים אחד אחרי השני ונשמרים בטרנזקציה אחת, כך שסנכרון ידני שמתנגש בריצה הלילית לא יכול להכניס מוצר פעמיים או להשאיר חצי קטלוג.
רוב העבודה הקשה הייתה בקצוות: ספק התשלום, שרת הדואר של הלקוח, וקבצי ספקים שאף אחד לא שולט בהם. מבחר:
תוכן עשיר שהוזן במערכת הניהול הוצג בחנות כ־HTML גולמי, פתח ל־Stored XSS.
ניקוי ה־HTML של התוכן לפני ההצגה, באותו שבוע שבו החנות נכנסה לריפו.
אינדקסים ייחודיים על שמות ו־slugs ספרו גם שורות שנמחקו, כך שקטגוריה מחוקה חסמה קטגוריה חדשה באותו שם.
מעבר לאינדקסים ייחודיים מסוננים שמתעלמים משורות מחוקות, ותיקון באגים מאומתים שנמצאו בסקירה מההתחלה ועד הסוף של 7 זרימות ניהול.
שרת הדואר של הלקוח התחיל לדחות את האפליקציה כשולח לא מורשה. האבחנה הראשונה, תעודת TLS חסרה, התבררה כשגויה.
הסיבה האמיתית הייתה כתובת ה־IP של השרת: ברגע שהיא נוספה לרשימת ההיתרים, הדואר יצא בלי שום תעודה. תיעדתי למה שאר ההגדרות חייבות להישאר ריקות, הוספתי סקריפט בדיקת SMTP שעוצר לפני השליחה, ונקודת קצה לאבחון (למנהלים בלבד) ששולחת דרך המסלול האמיתי ומדווחת מי ענה.
ספק התשלום מקשר כל עסקה למוכר אחד בדיוק, ולכן סל עם כמה ספקים לא יכול להיות חיוב אחד.
סיכמתי עם ספק התשלום זרימה דו־שלבית (אישור מסגרת ואז multi-capture) והרצתי אותה מההתחלה ועד הסוף ב־sandbox: אישור אחד ושני חיובים לשני ספקים. ההרצה מדדה את מה שהתיעוד השאיר פתוח: סכומים באגורות, אישור מסגרת שמחזיק 168 שעות, מגבלה של 10 קריאות ל־10 שניות, ועסקת מקור שנשארת ”authorized” אחרי חיוב חלקי.
ספק התשלום שולח callback מחדש עד 3 פעמים, ולכן תשלום אחד יכול להגיע יותר מפעם אחת.
אינדקס ייחודי על מזהה העסקה ו־callback אידמפוטנטי: משלוח חוזר מחזיר את ההזמנה שכבר קיימת.
פיד שחסרה בו עמודת חובה אחת יצר שגיאה לכל שורה. אלפי שורות זהות הסתירו כל תקלה אחרת בריצה, ועד שמישהו הסתכל, הקובץ של הספק כבר השתנה.
דוח הכשל בעברית מקבץ עכשיו את השגיאות לפי סיבה, עם מספר שורות ודוגמאות של מזהי מוצר, וכל ריצה שומרת עותק משלה של קובץ המקור ומקשרת אליו מהדוח.
הלקוח העביר את התחברות הרוכשים מתעודת זהות למספר טלפון. חיפוש טלפון לפני שנשלח קוד יכול לשמש כדי לגלות מי רוכש.
מעבר לזיהוי לפי טלפון ב־Backend ובחנות, ומגבלת ניסיונות ונעילה לכל מספר בשלב החיפוש, בנפרד מהמגבלה על ניחוש הקוד.
ב־QA, גיליון פגום זרק חריגה של ה־framework ששמה זהה לשם של חריגת הפיד של המנוע עצמו. היא חמקה מה־handler: לא נכתב דוח, לא יצאה התראה, ו־stack trace הגיע לתשובת ה־API.
תפיסה מפורשת של החריגה. קובץ פגום מייצר עכשיו דוח סנכרון כושל והתראה, והקטלוג הקיים נשאר באוויר.
המחשה של זרימת התשלום עם סכומים וספקים בדויים. אין כאן נתונים או קוד של הלקוח. עברו בין השלבים כדי לראות איפה כל בדיקה קורית.
מה שנשאר פתוח במסירה, כולו בצד הלקוח: KYC לחשבון המוכר של כל ספק, אחוז העמלה, וכתובת callback ציבורית שרשומה אצל ספק התשלום.
שיחת אבחון חינם · 30 דקות · ללא התחייבות
כתבו בכמה שורות: איזה תהליך או מערכת, מה כואב היום (זמן, טעויות, עלויות או פיילוט AI שנתקע) ומי עובד עם זה. אחרי שיחה קצרה מקבלים תוך כ־3 ימים תוכנית כתובה והצעת מחיר סגורה.
נפתח מייל מוכן מראש · מענה תוך יום עסקים
ה־AI קורא. הקוד סופר.
כל הפרויקטים