Architecture
A mini app runs in a hardened WebView, one per app, at its own synthetic origin. That sentence contains the two decisions that shape everything else.
Why not a two-thread runtime
Section titled “Why not a two-thread runtime”WeChat’s runtime splits a mini program into a render layer in a WebView and a logic layer in a separate JavaScript engine with no DOM, relayed by the native client. It is a well-understood design and Sollar does not use it, on three grounds.
1. Apple requires WebKit, worldwide
Section titled “1. Apple requires WebKit, worldwide”Guideline 2.5.6: apps that browse the web “must use the appropriate WebKit framework and WebKit JavaScript.” The Embedded Browser Engine Entitlement that would permit an alternative exists only in the European Union, and qualifying requires 90% on the Web Platform Tests, 80% on Test262, a memory-safe implementation language, and a 30-day CVE remediation commitment. That is out of reach, and it would not cover the rest of the world in any case.
2. Performance argues the same way, which is counter-intuitive
Section titled “2. Performance argues the same way, which is counter-intuitive”WKWebView’s WebContent process has JIT. A JavaScript engine embedded in the app’s own process does not — Apple does not grant the entitlement for writable-executable memory to ordinary apps.
So a custom two-thread runtime with its own JavaScriptCore instance would be slower on iOS than the WebView it was meant to improve on. This is the same ceiling WeChat’s own logic layer runs into, and it is not a small effect.
3. Lark already tried it and reversed
Section titled “3. Lark already tried it and reversed”Lark’s two-thread “gadget” runtime is deprecated in favour of Web Apps over WebView. When the company with the closest requirements and full control of both clients abandons the approach, that is evidence rather than opinion.
What about Android?
Section titled “What about Android?”Android reaches the same architecture by a different and thinner legal path. Google Play’s Device and Network Abuse policy exempts from its downloaded-code prohibition anything that runs “in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser).”
Note the asymmetry, because it is the reverse of what most people assume: Apple has a dedicated policy that names mini apps and permits them (Guideline 4.7). Google does not. Android depends on a generic interpreter exemption. That constrains the design — every execution path on Android must end in interpreted JavaScript with indirect, mediated access through the Sollar bridge, never a native code loader.
Isolation comes from the origin
Section titled “Isolation comes from the origin”Each mini app is served from a synthetic origin of its own:
| Platform | Mechanism | Origin |
|---|---|---|
| Android | WebViewAssetLoader + shouldInterceptRequest | https://<appid>.miniapp.localhost/ |
| iOS | WKURLSchemeHandler | A per-app scheme, treated as a distinct origin |
From that one decision, the browser engine gives:
- Storage partitioning —
localStorage, IndexedDB, the Cache API and cookies are separated by origin. Not by a directory convention the runtime has to remember; by the same-origin policy. - CSP scope — a policy applies to one mini app and cannot leak to another.
- A bridge boundary —
allowedOriginRuleson Android binds the message channel to exactly one origin.
This is the difference between a rule the runtime remembers to enforce and a rule it cannot violate. Cache reuse between mini apps is the first of the six vulnerability categories in the security model, and it exists in other platforms precisely because they chose shared directories with runtime-enforced paths.
WKWebsiteDataStore(forIdentifier:) gives an additional per-mini-app store on iOS, and
nonPersistent() gives an ephemeral mode. On Android, setDataDirectorySuffix looks like the
equivalent but is not usable for this: it is process-wide and settable once, so it cannot separate
apps within a running client. Origin separation solves what it cannot.
The bridge
Section titled “The bridge”| Platform | Mechanism | Never |
|---|---|---|
| Android | WebViewCompat.addWebMessageListener with allowedOriginRules | addJavascriptInterface |
| iOS | WKScriptMessageHandlerWithReply in a dedicated WKContentWorld | — |
addJavascriptInterface has no origin control and is the vector of CVE-2012-6636, where
attacker-controlled JavaScript reached arbitrary Java methods by reflection. The usual mitigation —
“only affects targetSdk ≤ 16” — is incomplete, and AOSP’s own documentation says so: apps
targeting later versions remain vulnerable when running on Android before 4.2. Sollar’s minSdk
makes that historical, but the operational lesson stands: one API has origin control and the other
does not.
allowedOriginRules matches scheme, host and port. The path is ignored. A design that assumes
the bridge can be restricted to a subdirectory is a design built on a misreading.
Where permission decisions happen
Section titled “Where permission decisions happen”In native code, against the signed manifest. Never in JavaScript.
mini app JS → [sollar.* shim] → native bridge → ┌────────────────────────┐ │ 1. manifest declares │ │ this scope? │ │ 2. user granted? │ │ 3. tenant allows? │ │ 4. within quota? │ └───────────┬────────────┘ ▼ capabilityThe JavaScript shim is ergonomics. If the only thing between a call and a capability were a check in JavaScript, there would be no check — a mini app controls its own JS environment by definition.
Lifecycle
Section titled “Lifecycle” install ──▶ launch ──▶ foreground ⇄ background ──▶ suspended ──▶ destroyed │ │ └──── agent invocation (headless) ─────────┘- Launch — package verified against its Merkle root, manifest parsed, origin established, bridge installed, entry page loaded.
- Background — timers throttled, network paused. The instance is retained for a short window and then suspended.
- Suspended — the renderer may be discarded to reclaim memory;
setRendererPriorityPolicyon Android makes the mini app’s renderer sacrificable before the host process. Returning re-launches and restores route state. - Headless — an agent invocation can start or resume an instance without showing it.
ctxtells your handler which case it is in.
Nothing survives destruction except origin storage and sollar.storage. Treat in-memory state as
disposable.
Network
Section titled “Network”Every request passes a native interceptor that checks the host against manifest.network.connect
before it leaves the device.
CSP is a second layer, not the primary one — a page can rewrite its own CSP at runtime, but it cannot reach the native interceptor. This ordering is what stops the third vulnerability category, silent exfiltration of sensitive data to an undeclared host.
Changing the destination list requires a new version, a new signature, and a new gate. That is the point of it.
Quotas
Section titled “Quotas”A mini app cannot degrade Sollar. Per instance: a ceiling on concurrent connections, on storage, and on memory — with the instance destroyed rather than the host running out — plus a rate limit per bridge method.
What was considered and rejected
Section titled “What was considered and rejected”| Option | Why not |
|---|---|
| A two-thread runtime with an embedded JS engine | 2.5.6, plus the JIT asymmetry above |
| FinClip and similar container SDKs | A third-party runtime inside the client’s trust boundary; opaque to review |
| Quick App | Vendor alliance runtime, Android-only, no iOS story |
| kbone / Taro compile-to-mini-program | Solves cross-compiling to WXML, a problem Sollar does not have |
| React Native or Flutter per mini app | Native code from third parties, downloaded — the exact thing both stores prohibit |
Taro deserves a note because Sollar’s own earlier stack study chose it. It compiles React or Vue into WeChat’s WXML/WXSS dialect. If the runtime is the web platform, there is no dialect and no problem to solve. The Chinese cross-compile ecosystem is also contracting: Remax archived 2024-03-07, WePY 2026-03-18, Chameleon 2026-04-15. Taro returns to the table only if Sollar ever wants to import WeChat mini programs, which is a different goal and should be decided as one.