מודל האבטחה של כתיבה אנונימית
לפני שמישהו “מתקן” הרשאות בטבלה כדי שטופס יעבוד — כדאי לקרוא את העמוד הזה. שלוש עובדות קובעות את הארכיטקטורה של כל פיצ’ר שבו גולש לא מזוהה כותב ל‑CRM.
1 · כתיבה אנונימית ישירה ל‑REST חסומה — ופתיחת CLP לא עוזרת
Section titled “1 · כתיבה אנונימית ישירה ל‑REST חסומה — ופתיחת CLP לא עוזרת”הניסיון הטבעי של כל מפתח: “יש REST API, אשלח POST ישירות לטבלה עם ה‑Application Id”.
# ✘ כתיבה אנונימית ישירה — נדחיתcurl -sS -X POST "https://api.mbapps.co.il/parse/classes/Accounts" \ -H "X-Parse-Application-Id: $APP_ID" \ -H "Content-Type: application/json" \ -d '{"Name":"ישראל ישראלי"}'// ✘ כתיבה אנונימית ישירה — נדחיתconst res = await fetch("https://api.mbapps.co.il/parse/classes/Accounts", { method: 'POST', headers: { 'X-Parse-Application-Id': APP_ID, 'Content-Type': 'application/json', }, body: JSON.stringify({ Name: 'ישראל ישראלי' }),});# ✘ כתיבה אנונימית ישירה — נדחיתimport requests
res = requests.post( "https://api.mbapps.co.il/parse/classes/Accounts", headers={ "X-Parse-Application-Id": APP_ID, "Content-Type": "application/json", }, json={"Name": "ישראל ישראלי"},)<?php// ✘ כתיבה אנונימית ישירה — נדחית$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => "https://api.mbapps.co.il/parse/classes/Accounts", CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Parse-Application-Id: {$APP_ID}", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode(["Name" => "ישראל ישראלי"], JSON_UNESCAPED_UNICODE),]);$response = curl_exec($ch);curl_close($ch);{ "code": 119, "error": "This action is not allowed without a valid CAPTCHA" }אותה תשובה בדיוק חוזרת על Sales, על טבלה מותאמת, ואפילו על שם מחלקה שאינו קיים — השער לא מסתכל על הטבלה בכלל.
הנקודה הקריטית: הדחייה הזו נשארת גם אחרי שפותחים את ההרשאות של הטבלה. גם כשה‑CLP מוגדר create: {"*": true} — הבקשה עדיין נדחית בקוד 119, גם על טבלה מותאמת.
הדלת החוקית לכתיבה אנונימית היא רק פונקציות הטפסים: web2table, web2case, ו‑getlead. כולן פונקציות צד שרת עם ולידציה משלהן, שנשלטות בדגל Config ולא בהרשאות טבלה.
2 · web2table רץ מוגבה ועוקף CLP — ולכן טבלת היעד נשארת סגורה
Section titled “2 · web2table רץ מוגבה ועוקף CLP — ולכן טבלת היעד נשארת סגורה”הפונקציה רצה בצד השרת עם Master Key מוזרק, ולכן ה‑CLP לא חוסם אותה. היא יוצרת שורות גם בטבלה שה‑CLP שלה הוא create: role:Admin בלבד.
| ההנחה השגויה | המציאות |
|---|---|
| “צריך לפתוח את הרשאות טבלת היעד כדי ש‑web2table יוכל לכתוב” | לא. הוא עוקף את ה‑CLP ממילא — גם מול CLP סגור לחלוטין |
| “אם הטופס מחזיר שגיאה, זו בעיית הרשאות” | לא. השגיאה היא 400 "… is Disabled" — דגל ה‑Config, לא CLP |
זהו גם ההסבר לחלוקת התפקידים: הפונקציה היא הגבול בין העולם האנונימי לנתונים. היא זו שמחליטה מה מותר לכתוב, לאילו שדות, ובאיזה טיפוס — ולכן היא, ולא ההרשאות, מה שצריך להגדיר.
3 · web2table הוא create‑only — אין סמנטיקת עדכון
Section titled “3 · web2table הוא create‑only — אין סמנטיקת עדכון”לנקודת הקצה אין דרך לפנות לשורה קיימת. שליחת objectId או table_objectId לא מעדכנת — היא פשוט מתעלמת: כל קריאה מוסיפה שורה. קריאה שנייה עם ה‑objectId של השורה הקיימת מחזירה מזהה חדש, ובטבלה יהיו שתי שורות לאותו טלפון. השורה המקורית לא משתנה.
וגם המסלול הישיר סגור: PUT אנונימי על שורה קיימת נדחה, וכך גם DELETE (Permission denied for action delete on class …). כלומר אין בכלל מסלול עדכון או מחיקה אנונימי בפלטפורמה.
המשמעות: בקשות כמו “שהלקוח יאשר את הפגישה מקישור במייל”, “שימלא דירוג אחרי שירות”, “שיעדכן כתובת מקישור” — לא נבנות כעדכון ישיר.
מה עושים כשצריך בכל זאת “עדכון מקישור ציבורי”
Section titled “מה עושים כשצריך בכל זאת “עדכון מקישור ציבורי””התבנית היא טבלת קליטה + טריגר השלכה:
דף ציבורי ──web2table──▶ טבלת קליטה ייעודית ──טריגר data-change──▶ אובייקט העסק(אנונימי) (יומן גולמי, (טבלה סגורה נפתחת לקליטה) לחלוטין)- הדף הציבורי יוצר שורה בטבלת ביניים ייעודית — טבלה שנועדה לקליטה בלבד.
- טריגר מסוג data change על טבלת הקליטה משליך את הערכים על אובייקט העסק האמיתי — פעולת
update-objectעםconnection: "source.<שדה Pointer>". - טבלת העסק עצמה נשארת סגורה לכתיבה אנונימית — כפי שהיא ממילא חייבת להיות.
שלוש השלכות שכדאי לתעד מראש
Section titled “שלוש השלכות שכדאי לתעד מראש”אלה לא פרטים טכניים אלא החלטות שמישהו “יתקן” בעוד שנה אם לא יכתבו אותן:
- הקישור חייב לשאת
phone(שדה חובה ב‑web2table). כלומר מספר טלפון של לקוח נוסע ב‑URL ונוחת בלוגים של שרתים, ב‑Referer ובהיסטוריית הדפדפן. זו החלטת פרטיות שצריך להעלות מול הלקוח, לא להסתיר. החלופה: טוקן אטום לכל רשומה, שנפתר בצד שרת ומתורגם לטלפון לפני הקריאה לנקודת הקצה. - אינטראקציה דו‑שלבית מייצרת שתי שורות קליטה לתגובה אחת (דירוג עכשיו, הערה כעבור רגע). זו התנהגות נכונה: טבלת הקליטה היא היומן הגולמי, ואובייקט העסק מחזיק את התשובה הסופית. תעדו את זה.
- טריגר שמגיב לקריאה הראשונה ירוץ לפני שהנתונים של השנייה קיימים. אל תרכיבו גוף הודעה, משימה או התראה משדה שמגיע בקריאה המאוחרת — הפנו את הקורא לרשומה עצמה.
CAPTCHA — הפרדת האחריות
Section titled “CAPTCHA — הפרדת האחריות”נקודות הקצה אינן מאמתות reCAPTCHA, Turnstile, hCaptcha או מלכודות דבש. הן לא רואות את הטוקן ולא יודעות מה לעשות איתו.
| השכבה | האחריות שלה |
|---|---|
| הדפדפן | מציג את ה‑widget, מקבל טוקן |
| השרת שלכם | מאמת את הטוקן מול ספק ה‑CAPTCHA — עם ה‑secret שלו — ורק אם עבר, מעביר הלאה |
getlead / web2table |
מקבלת גוף JSON נקי. היא לא יודעת שהיה CAPTCHA |
// Cloudflare Worker / Pages Function — מאמת Turnstile ואז מעביר ל-getleadexport async function onRequestPost({ request, env }) { const body = await request.json();
const verify = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ secret: env.TURNSTILE_SECRET, response: body.token }), }).then((r) => r.json());
if (!verify.success) return new Response('captcha failed', { status: 400 });
delete body.token; // הטוקן לא נשלח הלאה — הוא לא שדה בסכימה return fetch(`https://api.mbapps.co.il/functions/${env.APP_ID}/getlead`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Parse-Application-Id': env.APP_ID }, body: JSON.stringify(body), });}מלכודת דבש היא החלופה הזולה: שדה מוסתר שאדם לעולם לא ימלא ובוט פשוט כן. היא לא מחליפה CAPTCHA מול תוקף ממוקד, אבל היא חינמית, בלי חיכוך לגולש, ובלי סודות. דוגמה מלאה בעמוד getlead.
מה כן ומה לא נחשב סוד
Section titled “מה כן ומה לא נחשב סוד”| הערך | איפה מותר | למה |
|---|---|---|
| Application Id | גם בדפדפן, גם ב‑repository ציבורי | מזהה אפליקציה, לא אישור גישה. כתיבה, עדכון ומחיקה אנונימיים נדחים ב‑code 119 בכל מקרה. קריאה תלויה ב‑CLP של הטבלה: כש‑find סגור מתקבל code 119, וכשהוא פתוח ("*": true) הקריאה מחזירה נתונים. בדקו את ה‑CLP אצלכם |
| API Key | צד שרת בלבד | עוקף הרשאות בקריאה ובכתיבה. מספיק גם לקריאת סכימות — GET /parse/schemas/Accounts עם API Key מחזיר 200 (ראו סכימות) |
| Master Key | צד שרת בלבד, ורצוי מנהל סודות | גישה מלאה לכל טבלה, כולל Config והטבלאות החסומות: קריאה וכתיבה ל‑Config, שינוי CLP, קריאת סכימות |
| CAPTCHA secret | צד שרת בלבד | חשיפתו מבטלת את ההגנה |
רשימת בדיקה אבטחתית
Section titled “רשימת בדיקה אבטחתית”- אין Master Key ואין API Key בשום קוד שרץ בדפדפן או ב‑repository
- ה‑CLP של טבלת היעד נשאר סגור — לא נפתח “כדי שהטופס יעבוד”
-
ValidatePhoneOrEmail: trueיושב בתוך ה‑Valueשל שורת הדגל ב‑Config— זו ההגנה היחידה בצד השרת - אם יש CAPTCHA — האימות נעשה בשרת שלכם, לפני ההעברה
- “עדכון מקישור ציבורי” מיושם כטבלת קליטה + טריגר, לא כניסיון עדכון ישיר
- אם טלפון נוסע ב‑URL — זה תועד ואושר מול הלקוח, או הוחלף בטוקן
- הובן ותועד שכל קריאה מוסיפה שורה, ושאין מסלול עדכון אנונימי
- הלקוח יודע שנקודת הקצה ניתנת להפעלה גם ב‑
GET, ושהגבלת קצב היא באחריותו
- דגלי Config — השער האמיתי של נקודות הקצה
- אבחון תקלות —
code 119,code 111ושאר התסמינים - web2table — הפרטים המלאים של נקודת הקצה
- שגיאות ופתרון תקלות — קוד 119 בהקשר של כל השכבות