אפליקציית-על עם מיני-תוכניות היא למעשה מערכת הפעלה בתוך מערכת הפעלה. אפליקציית המארח טוענת ומריצה קוד ממפתחים צד-שלישי. אם הקוד הזה יכול לקרוא נתונים ממיני-תוכניות אחרות או מהאפליקציה הראשית, כל ארכיטקטורת האבטחה קורסת. אנו מציעים הטמעה מלאה של ארגז חול מבודד, המבטיחה שכל מיני-תוכנית פועלת בסביבה מוגבלת בהחלט. זה מפחית את הסיכון להפסדים כספיים כתוצאה מפריצות מידע, שיכולים להגיע למיליוני דולרים. צרו קשר — נבחן את הפרויקט שלכם תוך יום אחד.
מדוע בידוד מיני-תוכניות הוא אתגר טכני
ב-WeChat Mini Programs, Grab SuperApp, Gojek — לכל אחת יש יישום בידוד משלה. הבעיה המרכזית: קוד מקורי ב-iOS וב-Android לא יכול "לבודד" קוד JS או Dart שרירותי ללא מנגנונים מיוחדים. WebView מבודד את ה-DOM אבל לא את הזיכרון, והוא לא מגביל בקשות רשת.
אנטי-דפוס טיפוסי: טעינת ה-JS של המיני-תוכנית לתוך WKWebView / WebView, חשיפת addJavascriptInterface עבור ה-APIs הנדרשים — ולחשוב שזה ארגז חול. זה לא ארגז חול. כל XSS במיני-תוכנית מקבל גישה לכל האובייקטים הרשומים דרך addJavascriptInterface, כולל גשרים לקוד מקורי. הניסיון שלנו מראה שדפוס כזה מוביל לדליפות ב-9 מתוך 10 ביקורות. העלות של הטמעת ארגז החול שלנו מחזירה את עצמה במניעת תקריות כאלה.
רמות בידוד שאנו מיישמים
בידוד ביצוע
ב-Android, מיני-תוכניות JS עדיף להריץ בתהליך נפרד באמצעות מאפיין android:process במניפסט. כל מיני-תוכנית מקבלת תהליך משלה עם ערימת זיכרון משלה. קריסה בתוכנית אחת לא מפילה את המארח. עבור Dart/Flutter, אנו משתמשים ב-Isolate עם ReceivePort API מוגבל.
למיני-תוכניות מבוססות WebView: WebView עם setJavaScriptEnabled(true) בתהליך נפרד בתוספת WebViewClient עם רשימת היתרים למארח:
class SandboxedWebViewClient(
private val allowedHosts: Set<String>
) : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
val host = request.url.host ?: return blockRequest()
if (host !in allowedHosts) {
auditLogger.logBlockedRequest(miniProgramId, request.url)
return blockRequest()
}
return null // продолжаем
}
private fun blockRequest() = WebResourceResponse(
"text/plain",
"UTF-8",
ByteArrayInputStream("blocked".toByteArray())
)
} גשר JavaScript עם מודל יכולות
במקום class SandboxedWebViewClient( private val allowedHosts: Set<String> ) : WebViewClient() { override fun shouldInterceptRequest( view: WebView, request: WebResourceRequest ): WebResourceResponse? { val host = request.url.host ?: return blockRequest() if (host !in allowedHosts) { auditLogger.logBlockedRequest(miniProgramId, request.url) return blockRequest() } return null // продолжаем } private fun blockRequest() = WebResourceResponse( "text/plain", "UTF-8", ByteArrayInputStream("blocked".toByteArray()) ) } פתוח, אנו משתמשים בגשר הצהרתי עם רשימת הרשאות מפורשת. המיני-תוכנית מבקשת API; המארח בודק אם הוא מותר במניפסט של אותה תוכנית:
class CapabilityBridge(
private val miniAppManifest: MiniAppManifest,
private val userId: String
) {
@JavascriptInterface
fun callNative(apiName: String, params: String, callbackId: String) {
val capability = Capability.fromString(apiName) ?: run {
sendError(callbackId, "UNKNOWN_API")
return
}
if (!miniAppManifest.hasPermission(capability)) {
auditLogger.logUnauthorizedApiCall(miniAppId, apiName)
sendError(callbackId, "PERMISSION_DENIED")
return
}
nativeApiRouter.dispatch(capability, params, callbackId)
}
}המניפסט של המיני-תוכנית מתאר את ה-APIs המבוקשים — בדומה ל-addJavascriptInterface ב-Android, אבל עבור מערכת האקוסיסטם של מיני-אפליקציות.
בידוד אחסון
כל מיני-תוכנית מקבלת מרחב שמות מבודד ב-class CapabilityBridge( private val miniAppManifest: MiniAppManifest, private val userId: String ) { @JavascriptInterface fun callNative(apiName: String, params: String, callbackId: String) { val capability = Capability.fromString(apiName) ?: run { sendError(callbackId, "UNKNOWN_API") return } if (!miniAppManifest.hasPermission(capability)) { auditLogger.logUnauthorizedApiCall(miniAppId, apiName) sendError(callbackId, "PERMISSION_DENIED") return } nativeApiRouter.dispatch(capability, params, callbackId) } } וספרייה נפרדת ב-uses-permission:
/app/mini_programs/
/{mini_app_id}/
/storage/
← SharedPreferences namespace
/files/
← файловое хранилище
/cache/
← очищается при нехватке местаגישה לאחסון של מיני-תוכנית אחרת דורשת Intent מפורש עם אישור משתמש. גישה חוצת-תוכניות לנתונים מחוץ למנגנון זה אסורה ברמת SharedPreferences עם אימות filesDir.
בידוד רשת
ב-Android 8 ומעלה, אנו משתמשים ב-/app/mini_programs/ /{mini_app_id}/ /storage/ ← SharedPreferences namespace /files/ ← файловое хранилище /cache/ ← очищается при нехватке места עם ContentProvider כדי לקשור חיבור ספציפי לפרופיל VPN עבור המיני-תוכנית. אפשרות פחות אגרסיבית היא פרוקסי עם רשימת היתרים ברמת המארח ו-HTTPS pinning לשרתי המיני-תוכנית דרך callingUid מותאם אישית. אנו גם מיישמים ניטור בקשות עם מגבלה של 1000 בקשות לדקה לכל מיני-תוכנית; בחריגה, אנו חוסמים ומודיעים.
ב-iOS, ConnectivityManager (iOS 14+) מאפשר להריץ את ה-JS של כל מיני-תוכנית בעולם מבודד עם אובייקט גלובלי נפרד. לפי התיעוד של Apple, זה מבטיח בידוד מלא של הקשר הביצוע.
let miniAppWorld = WKContentWorld.world(withName: "mini_app_\(miniAppId)")
webView.evaluateJavaScript(miniAppCode, in: nil, in: miniAppWorld) { result, error in
// код выполняется в изолированном контексте
}מופעי NetworkCapabilities שונים לא רואים את המשתנים של זה, אפילו באותו X509TrustManager.
כיצד מובטח השקה מהימנה?
לפני ההשקה, אנו בודקים את חתימת ה-bundle. כל bundle נחתם על ידי המפתח ומאומת מול המפתח הציבורי הרשום בפלטפורמה:
fun verifyMiniAppBundle(bundle: ByteArray, signature: ByteArray, publisherKey: PublicKey): Boolean {
val sig = Signature.getInstance("SHA256withECDSA")
sig.initVerify(publisherKey)
sig.update(bundle)
return sig.verify(signature)
}השקה של bundle לא חתום או שונה נדחית עם רישום אירוע.
ניטור בזמן אמת
ארגז חול אינו מבנה סטטי. אנו זקוקים לניטור בזמן אמת: זמן CPU לכל מיני-תוכנית, זיכרון מוקצה, מספר בקשות רשת. מיני-תוכנית שעושה 500 בקשות בשנייה היא או שבורה או כורה מטבעות.
ב-Android, אנו משתמשים ב-WKContentWorld + let miniAppWorld = WKContentWorld.world(withName: "mini_app_\(miniAppId)") webView.evaluateJavaScript(miniAppCode, in: nil, in: miniAppWorld) { result, error in // код выполняется в изолированном контексте } עבור כל תהליך מיני-תוכנית. ספים מוגדרים בקונפיגורציית הפלטפורמה (לדוגמה, 200 אלפיות שנייה CPU בשנייה, 50 MB זיכרון, 100 בקשות רשת בדקה).
| רמת בידוד | טכנולוגיה | אפקט |
|---|---|---|
| ביצוע | תהליך נפרד (android:process) / Isolate | קריסה לא מפילה את המארח |
| גשר JavaScript | CapabilityBridge עם מניפסט | בקרת גישה ל-API |
| אחסון | מרחב שמות + ContentProvider | בידוד נתונים |
| רשת | WebViewClient allowlist / WKContentWorld | הגבלת בקשות |
מה כלול בעבודה
- תיעוד ארכיטקטוני של תוכנית הבידוד (תהליכים, גשרים, אחסון)
- הטמעת רכיבי ליבה: SandboxedWebViewClient, CapabilityBridge, StorageManager
- שילוב מנגנון אימות bundle עם חתימות ECDSA
- הגדרת ניטור בזמן אמת עם ספי CPU/זיכרון/רשת
- ביקורת אבטחה ובדיקות חדירה לארגז החול לפני ההשקה
- תמיכה והדרכת צוות למשך שבועיים לאחר ההטמעה
לוח זמנים ועלות
ארגז חול בסיסי עם בידוד תהליכי WebView וגשר יכולות לוקח 2–3 שבועות. פלטפורמה מלאה עם בידוד רשת, אימות bundle, ניטור בזמן אמת וקונסולת ניהול הרשאות לוקחת 2–3 חודשים. העלות מחושבת באופן אישי לאחר הגדרת היקף העבודה. קבלו ייעוץ — צרו קשר לביקורת. הזמינו הטמעת ארגז חול לאפליקציית-העל שלכם — נספק הערכה מפורטת.







