דלגו לתוכן

או התחילו מנקודה בטוחה: אינדקס הכלים המלא · מפת הטבלאות המרכזיות

מערכת חדשה — 14 יום חינם

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

עודכן 30.08.2026

לפני שמישהו “מתקן” הרשאות בטבלה כדי שטופס יעבוד — כדאי לקרוא את העמוד הזה. שלוש עובדות שאומתו בפועל על טננט בדיקה נקי (08.2026) קובעות את הארכיטקטורה של כל פיצ’ר שבו גולש לא מזוהה כותב ל‑CRM.

1 · כתיבה אנונימית ישירה ל‑REST חסומה — ופתיחת CLP לא עוזרת

Section titled “1 · כתיבה אנונימית ישירה ל‑REST חסומה — ופתיחת CLP לא עוזרת”

הניסיון הטבעי של כל מפתח: “יש REST API, אשלח POST ישירות לטבלה עם ה‑Application Id”.

Bash
# ✘ כתיבה אנונימית ישירה — נדחית
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":"ישראל ישראלי"}'
JavaScript
// ✘ כתיבה אנונימית ישירה — נדחית
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: 'ישראל ישראלי' }),
});
Python
# ✘ כתיבה אנונימית ישירה — נדחית
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
<?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);
JSON
{ "code": 119, "error": "This action is not allowed without a valid CAPTCHA" }

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

הנקודה הקריטית: הדחייה הזו נשארת גם אחרי שפותחים את ההרשאות של הטבלה. גם כשה‑CLP מוגדר create: {"*": true} — הבקשה עדיין נדחית בקוד 119 (אומת שוב 08.2026 על טבלה מותאמת).

הדלת החוקית לכתיבה אנונימית היא רק פונקציות הטפסים: web2table, web2case, ו‑getlead (בחומרים ישנים — “web2lead”). כולן פונקציות צד שרת עם ולידציה משלהן, שנשלטות בדגל 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──▶ אובייקט העסק
(אנונימי) (יומן גולמי, (טבלה סגורה
נפתחת לקליטה) לחלוטין)
  1. הדף הציבורי יוצר שורה בטבלת ביניים ייעודית — טבלה שנועדה לקליטה בלבד.
  2. טריגר מסוג data change על טבלת הקליטה משליך את הערכים על אובייקט העסק האמיתי — פעולת update-object עם connection: "source.<שדה Pointer>".
  3. טבלת העסק עצמה נשארת סגורה לכתיבה אנונימית — כפי שאומת שהיא ממילא חייבת להיות.

שלוש השלכות שכדאי לתעד מראש

Section titled “שלוש השלכות שכדאי לתעד מראש”

אלה לא פרטים טכניים — הן החלטות שמישהו “יתקן” בעוד שנה אם לא יכתבו אותן:

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

נקודות הקצה אינן מאמתות reCAPTCHA, Turnstile, hCaptcha או מלכודות דבש. הן לא רואות את הטוקן ולא יודעות מה לעשות איתו.

השכבה האחריות שלה
הדפדפן מציג את ה‑widget, מקבל טוקן
השרת שלכם מאמת את הטוקן מול ספק ה‑CAPTCHA — עם ה‑secret שלו — ורק אם עבר, מעביר הלאה
getlead / web2table מקבלת גוף JSON נקי. היא לא יודעת שהיה CAPTCHA
JavaScript
// Cloudflare Worker / Pages Function — מאמת Turnstile ואז מעביר ל-getlead
export 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.

הערך איפה מותר למה
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 צד שרת בלבד חשיפתו מבטלת את ההגנה
  • אין Master Key ואין API Key בשום קוד שרץ בדפדפן או ב‑repository
  • ה‑CLP של טבלת היעד נשאר סגור — לא נפתח “כדי שהטופס יעבוד”
  • ValidatePhoneOrEmail: true יושב בתוך ה‑Value של שורת הדגל ב‑Config — זו ההגנה היחידה בצד השרת
  • אם יש CAPTCHA — האימות נעשה בשרת שלכם, לפני ההעברה
  • “עדכון מקישור ציבורי” מיושם כטבלת קליטה + טריגר, לא כניסיון עדכון ישיר
  • אם טלפון נוסע ב‑URL — זה תועד ואושר מול הלקוח, או הוחלף בטוקן
  • הובן ותועד שכל קריאה מוסיפה שורה, ושאין מסלול עדכון אנונימי
  • הלקוח יודע שנקודת הקצה ניתנת להפעלה גם ב‑GET, ושהגבלת קצב היא באחריותו