// docs / baas security / supabase service role exposure
מפתח Supabase service role חשוף ב-JavaScript: מה זה אומר ואיך למצוא אותו
מפתח ה-Supabase service role הוא המפתח הראשי למסד הנתונים שלך. כל מי שמחזיק בו עוקף Row-Level Security, יכול לקרוא כל עמודה של כל טבלה, ויכול לכתוב או למחוק כל דבר שיבחר. הוא מתוכנן לחיות אך ורק בקוד שרת — לעולם לא בדפדפן. כשכלי קוד מבוסס-AI שולח אותו לחבילת ה-JavaScript, מסד הנתונים שלך הוא, בפועל, ציבורי. המאמר הזה מסביר את צורת ה-JWT שמזהה מפתח שדלף, את שלושת דפוסי כלי ה-AI שמייצרים את הדלף, מה לעשות בשעה הראשונה לאחר הזיהוי, ואיך לסרוק עבורו אוטומטית לפני שהמשתמשים יעשו זאת.
מה זה מפתח ה-service role
Supabase מנפיק שני מפתחות שונים לכל פרויקט: מפתח anon (שנקרא גם מפתח publishable בפרויקטים חדשים יותר) ומפתח service_role. שניהם JSON Web Tokens החתומים בסוד ה-JWT של הפרויקט שלך. ההבדל הוא ה-claim role שאפוי בתוך payload ה-JWT — anon עבור המפתח הציבורי, service_role עבור המפתח הראשי. PostgREST, Supabase Storage ו-Supabase Auth כולם עוברים למצב עקיפה-של-הכל כשהם רואים את ה-claim service_role.
פענח כל מפתח Supabase ב-jwt.io והסתכל על ה-payload. צורת JWT של service-role היא בלתי ניתנת לטעות:
Payload מפוענח של JWT service-role (מוצג כבלוק עם הדגשת תחביר מטה).
{
"iss": "supabase",
"ref": "[project-ref]",
"role": "service_role",
"iat": 1700000000,
"exp": 2000000000
}פרויקטי Supabase חדשים יותר מנפיקים מפתחות בסגנון secret עם הקידומת sb_secret_ במקום JWT. ההתנהגות זהה — כל דבר שנושא sb_secret_ בחבילה ציבורית הוא קטסטרופלי באותה מידה.
איך כלי קוד מבוססי-AI מדליפים את מפתח ה-service role
ראינו את אותם שלושה דפוסים על פני אלפי אפליקציות vibe-coded. כל אחד מתחיל במפתח שמבקש מכלי AI עזרה ומסתיים במפתח ה-service מוטמע בתוך חבילה.
דפוס 1: קובץ .env יחיד עם קידומת NEXT_PUBLIC_
המפתח מבקש מכלי ה-AI "להגדיר Supabase" ומקבל .env יחיד עם שני המפתחות. כלי ה-AI — שאומן על קורפוס שבו רוב משתני הסביבה נחשפים דרך NEXT_PUBLIC_* — מקדים את שניהם ב-NEXT_PUBLIC_. Next.js מטמיעה כל דבר שתואם לקידומת הזו לחבילת הלקוח בזמן build. שולחים ל-Vercel, ומפתח ה-service נמצא ב-main.[hash].js.
דפוס 2: מפתח שגוי בקריאת createClient
המפתח מדביק את שני המפתחות לקובץ config.ts שה-AI יצר, וה-AI מאכלס את קריאת ה-createClient() שצד-הדפדפן ב-process.env.SUPABASE_SERVICE_ROLE_KEY בטעות. ה-build מושך את המשתנה פנימה, וה-JWT נוחת בחבילה.
דפוס 3: מפתח service-role מוטמע בקוד בתסריטי seed
המפתח מבקש מכלי ה-AI לכתוב תסריט שמזין נתונים למסד הנתונים. ה-AI מוטמע את מפתח ה-service-role ישירות לתוך הקובץ (במקום לקרוא מהסביבה), מבצע commit לקובץ למאגר, ומאגר ה-GitHub הציבורי או נתיב /scripts/seed.js של האפליקציה המותקנת משרת כעת את המפתח.
איך סריקת החבילה של FixVibe מזהה את הדלף
בדיקת bundle-secrets של FixVibe מורידה כל קובץ JavaScript שהאפליקציה המותקנת מפנה אליו — chunks של entry, chunks הנטענים בעצלות, web workers, service workers — ומריצה אותם דרך זיהוי שמפענח כל דבר שתואם לצורת JWT (eyJ[base64-header].eyJ[base64-payload].[signature]). אם ה-payload המפוענח מכיל "role": "service_role", הסריקה מדווחת זאת כממצא קריטי עם נתיב הקובץ והשורה המדויקת שבה מופיע המפתח. אותה בדיקה גם תואמת את הדפוס החדש יותר sb_secret_* לפי קידומת.
הסריקה לעולם לא מתאמתת עם המפתח שהתגלה. היא מזהה את הצורה ומדווחת על הדלף — שימוש במפתח כדי להוכיח ניצולת היה גישה לא-מורשית למסד הנתונים שלך. ההוכחה נמצאת ב-payload של ה-JWT עצמו.
התגלה — מה לעשות בשעה הראשונה
מפתח service role שדלף הוא חירום runtime. הנח שהמפתח גרוף — תוקפים מנטרים חבילות ציבוריות בזמן אמת. התייחס למסד הנתונים כאל פרוץ עד שתסבב את המפתח ותבצע audit לפעילות האחרונה.
- סבב את המפתח מיד. ב-Dashboard של Supabase, עבור ל-Project Settings → API → Service role key → Reset. המפתח הישן מבוטל תוך שניות. כל קוד צד-שרת שמשתמש במפתח חייב להתעדכן ולהיפרס מחדש לפני שהסבב נכנס לתוקף.
- בצע audit לפעילות מסד הנתונים האחרונה. פתח Database → Logs ב-dashboard. סנן ל-7 הימים האחרונים. חפש שאילתות
SELECT *חריגות מול טבלאות עם PII, הצהרותUPDATEאוDELETEגדולות, ובקשות מ-IPs מחוץ לתשתית הידועה שלך. Supabase מתעד את header ה-x-real-ipבכל בקשה. - בדוק אובייקטים של אחסון. בקר ב-Storage → Logs וסקור הורדות קבצים אחרונות. מפתח service-role שדלף מעניק גישה של עקיפה-של-הכל גם לדליים פרטיים.
- הסר את המפתח מבקרת המקור. אפילו אחרי סבב, השארת ה-JWT בהיסטוריית ה-git שלך אומרת שהוא ניתן לגילוי במאגר הציבורי. השתמש ב-
git filter-repoאו ב-BFG Repo-Cleaner כדי לקרצף אותו מההיסטוריה, ואז בצע force-push (הזהר משתפי פעולה תחילה). - סרוק מחדש אחרי התיקון. הרץ סריקת FixVibe חדשה מול האפליקציה הפרוסה מחדש. ממצא ה-bundle-secrets אמור להיעלם. אשר שאין JWT של
service_roleושאין מחרוזתsb_secret_*בכל chunk.
מניעת הדלף מלכתחילה
הפתרון המבני הוא משמעת בשמות פלוס מעקות בטיחות ברמת הכלי:
- לעולם אל תקדים את מפתח ה-service ב-
NEXT_PUBLIC_*,VITE_*, או כל קידומת אחרת של הטמעה בחבילה. מוסכמת השמות היא הגבול — כל פריימוורק מכבד אותה. - שמור את מפתח ה-service לחלוטין מחוץ ל-
.envבמכונת המפתח. קרא אותו ממנהל סודות (Doppler, Infisical, משתני סביבה מוצפנים של Vercel) בזמן deploy, לעולם אל תבצע commit מקומית. - <strong>Mark every Supabase client construction with explicit context.</strong> Files named <code>supabase/browser.ts</code> use the anon key; files named <code>supabase/server.ts</code> use the service-role key with <code>import 'server-only'</code> at the top. The <code>server-only</code> import causes a build error if a client component tries to consume the module.
- <strong>Add a pre-commit hook that greps for JWT-shaped strings.</strong> <code>git diff --staged | grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'</code> catches both anon and service tokens before they leave your machine.
- הוסף שער CI שסורק את פלט ה-build. אחרי
next build, חפש בפלט.next/static/chunks/את המחרוזתservice_role. כשל את ה-build אם משהו תואם.
# Pre-commit hook: refuse any staged JWT-shaped string.
git diff --staged \
| grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' \
&& echo "JWT detected in staged changes — refusing commit" \
&& exit 1
# CI gate: fail the build if "service_role" shipped to the static bundle.
grep -RE 'service_role|sb_secret_' .next/static/chunks/ \
&& echo "Service-role credential leaked into bundle" \
&& exit 1שאלות נפוצות
מה מהירות התוקפים בפועל למצוא מפתחות Supabase service-role שדלפו?
סורקי חבילות ציבוריות סורקים פריסות חדשות תוך דקות. חוקרים תיעדו ניצולים פעילים מול פרויקטי Supabase חדשים תוך פחות משעה מה-deploy הראשון. התייחס לכל חשיפת service-role כחלון של 60 דקות, לא 60 ימים.
האם סבב המפתח מספיק, או שאני חייב להניח חילוץ נתונים?
סבב מבטל את המפתח שדלף אך לא מבטל נתונים שכבר נמשכו. אם הטבלאות שלך מכילות PII, נתוני תשלום, או כל נתון מפוקח, ייתכן שיש לך חובת הודעה תחת GDPR (72 שעות), CCPA, או HIPAA. בצע audit ליומנים והתייעץ עם יועץ משפטי אם ה-audit מראה גישה חשודה.
האם RLS יכול להגן עליי אם מפתח ה-service-role דולף?
לא. Row-Level Security נעקף לחלוטין על-ידי ה-claim service_role. זה בעיצוב — המפתח קיים בדיוק כדי לאפשר לקוד backend לדלג על RLS לפעולות admin. ההפחתה היא לוודא שהמפתח לעולם לא מגיע להקשר שבו תוקף יכול לקרוא אותו.
האם זה חל על מודל המפתח publishable / secret החדש של Supabase (<code>sb_publishable_</code> / <code>sb_secret_</code>)?
כן — אותה מחלקת סיכון. מפתח sb_secret_* הוא הפורמט החדש של מפתח-סוד שמחליף את ה-JWT של service-role עבור פרויקטים חדשים יותר. כל דבר שנושא sb_secret_* בחבילה הוא קטסטרופלי באותה מידה כמו JWT service-role שדלף. זיהוי ה-bundle-secrets של FixVibe תואם את שתי הצורות.
מה עם מפתח ה-anon / publishable — האם הוא בטוח בחבילה?
כן, בעיצוב. מפתח ה-anon מיועד לחיות בדפדפן והוא מה שכל לקוח web של Supabase משתמש בו. הבטיחות שלו תלויה לחלוטין ב-RLS שמוגדר נכון על כל טבלה ציבורית. ראה את המאמר סורק Supabase RLS למה לבדוק.
צעדים הבאים
הרץ סריקת FixVibe מול URL הייצור שלך — בדיקת ה-bundle-secrets חינמית, ללא הרשמה, ומדווחת על חשיפת service_role תוך פחות מדקה. צמד את זה עם המאמר סורק Supabase RLS כדי לאמת ששכבת ה-RLS עושה את עבודתה, ועם רשימת בדיקה לאבטחת דליי אחסון של Supabase כדי לנעול את הגישה לקבצים. לרקע על מדוע כלי AI מייצרים את מחלקת הדלף הזו באמינות כזו, קרא מדוע כלי קוד מבוססי-AI משאירים פערי אבטחה.
