תכונותמחיריםאינטגרציותהשוואהבלוגעזרהצור קשרהתחברותהתחל בחינם
חזרה לבלוג
🔒
אבטחה

איך אנחנו מבטיחים שאין דליפת נתונים בין ארגונים

בהאא טהה25/02/202610 דק' קריאה

למה זה חשוב

WorkPilot היא SaaS multi-tenant. מאות ארגונים שונים חולקים את אותו DB ואותו שרת. הסיבה: עלויות נמוכות, deploy מהיר, scaling קל.

הסיבה הזו גם הסיכון. אם איזשהו באג מאפשר לארגון A לראות נתונים של ארגון B — זה אסון. הנה איך אנחנו מבטיחים שזה לא יכול לקרות.

הגישה: Defense in Depth

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

ב-WorkPilot יש 5 שכבות הגנה:

שכבה 1: Session Cookie עם Validation

ה-cookie של המשתמש מכיל organizationId. אבל אנחנו לא סומכים על הקוקי כ-truth.

בכל request, הקוד עושה lookup ב-DB:

  • האם המשתמש עדיין member של אותו ארגון?
  • האם הארגון עדיין פעיל?
  • האם ה-role שלו השתנה?

אם משהו השתנה — מתחדש fresh מה-DB. ה-cookie הוא רק רמז, לא אמת.

שכבה 2: AsyncLocalStorage עם Tenant Context

לפני שכל route handler רץ:

tenantStorage.run({ organizationId, userId, role }, () => handler())

זה אומר שכל קוד שירוץ בתוך ה-handler יוכל לקרוא את ה-context בלי להעביר אותו פרמטר. הקסם: כל call ל-Prisma שיתבצע, יקבל את ה-tenant context אוטומטית.

שכבה 3: Prisma Extension — הזרקת WHERE

זו השכבה הכי חזקה. בנינו extension של Prisma שמיירט כל query על מודלים מולטי-טננטיים, ומזריק where: { organizationId } אוטומטית.

prisma.customer.findMany({ where: { isActive: true } }) הופך אוטומטית ל-prisma.customer.findMany({ where: { isActive: true, organizationId: '...' } }).

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

זה חל גם על: findUnique, findFirst, count, aggregate, groupBy, updateMany, deleteMany, ועל create (מזריק את ה-organizationId לתוך ה-data).

שכבה 4: Post-fetch Validation

חלק מה-queries (כמו findUnique עם ID ישיר) לא תומכים בהזרקת WHERE. במקרה הזה, ה-extension עושה fetch רגיל ואז מאמת שה-organizationId של התוצאה תואם ל-context. אם לא — מחזיר null.

זה תופס מקרים כמו: "מה אם משתמש מנחש ID של customer מארגון אחר ושולח אותו ב-URL?"

שכבה 5: API Route Wrapper

כל API route עטוף ב-withTenant():

1. בודק שהמשתמש מאומת

2. בודק שיש לו role המתאים

3. מגדיר את ה-AsyncLocalStorage

אין דרך ליצור route בלי ה-wrapper. אם מפתח שכח — TypeScript יצעק עליו.

מה אם מפתח שוכח?

נאמר שמישהו כתב route חדש בלי withTenant(). מה קורה?

1. TypeScript — אזהרה ב-compile time

2. Tenant context לא מוגדר — Prisma extension לא יכול להזריק WHERE

3. התוצאה: השאילתה תרוץ בלי סינון — כל ה-customers של כל הארגונים יחזרו

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

  • בדיקות אוטומטיות ב-CI שמוודאות שכל route תחת /api/ משתמש ב-withTenant
  • קוד reviews מקפידים על זה
  • שמות מתודות — לא משתמשים ב-prisma ישיר, רק דרך ה-extended client

מה עוד אנחנו עושים

Audit Log

כל פעולה רגישה (DELETE, שינוי הרשאות) מתועדת עם userId, organizationId, IP, timestamp, ו-old/new values.

בדיקות תקופתיות

פעם בחודש סקריפט שעובר על כל הטבלאות עם organizationId ובודק:

  • האם כל רשומה משויכת לארגון פעיל?
  • האם יש foreign keys שמצביעים לרשומות מארגון אחר?
  • האם יש memberships יתומים?

עד היום — אפס דליפות.

מה זה אומר לך כלקוח

זה אומר שאתה יכול לישון בשקט. הנתונים שלך:

  • ✅ לא נראים על ידי ארגונים אחרים
  • ✅ לא ניתנים למחיקה בטעות
  • ✅ מגובים יומית
  • ✅ מתועדים בכל פעולה רגישה
  • ✅ עוברים על תשתית עם הצפנה ב-rest
רוצה לראות את ה-Audit Log שלך? בדשבורד → הגדרות → יומן פעילות.

סיכום

Multi-tenant security זה לא תכונה — זו ארכיטקטורה. ב-WorkPilot, יש לנו 5 שכבות הגנה שעובדות במקביל. כל אחת לבדה מספיקה. שילוב שלהן הופך את המערכת לבלתי-חדירה.

אם אתה בונה SaaS משלך — תיקח את הגישה הזו. לעולם אל תסמוך על שכבה אחת.

הפוסט הקודם
מדריך SLA: איך לעקוב אחר זמני תגובה ולסיים בזמן

רוצה לראות את WorkPilot בפעולה?

14 ימי ניסיון חינם, בלי כרטיס אשראי

התחל עכשיו
איך אנחנו מבטיחים שאין דליפת נתונים בין ארגונים | WorkPilot Blog