WorksComputer Science
A Hybrid Information Flow Analysis Framework for Android Hybrid Apps
Abstract
The WebView interface in Android (hybrid) applications enables the native side (e.g., Java) to exchange data with the Web (e.g., JavaScript). This creates cross-language information flow channels that existing single-language analyzers cannot analyze. Existing hybrid app analyses address only part of the problem: some build bridge-aware call graphs but provide limited information-flow analysis, some rely on coarse assumptions, and some focus only on web vulnerabilities. We present MifDroid, a static-analysis- guided dynamic-analysis framework with a lifecycle-aware per-WebView call graph, a bridge-aware type-and-effect system, and a modular JavaScript analysis engine. We evaluated MifDroid on 338 apps from the F-Droid repository (out of 4,126 total) that use addJavascriptInterface, enabling active communication between Java and JavaScript. Our guided dynamic phase adds 662 entries to the 444 produced by static analysis, raising the resolution rate from 36.5% to 74.5%, produces the resolved bridge-related entries for 115 apps that static analysis leaves empty. MifDroid reported 14 vulnerable cross-language information flows, five of which have been accepted and fixed by the maintainers.
CCS Concepts: • Security and privacy → Information flow control; • Theory of computation → Program analysis; • Software and its engineering → Dynamic analysis.
Introduction
Android applications (apps) are increasingly hybrid, with Java/Kotlin code on the native side (as the host) and JavaScript on the Web (as the guest) communicating through a shared bridge. This communication is facilitated by the WebView [9]. The host side can invoke JavaScript/Web via dedicated APIs, such as loadUrl or evaluateJavascript. The communication is not merely one-sided. WebView also enables the guest side (JavaScript) to access the native side functionality via a special host object. We refer to this unique host object as bridge object. Although these bridge objects belong to the native-side Java/Kotlin class, they are exposed to JavaScript as if they were its own objects. Bridge objects were introduced to facilitate code reuse across the native side and the web. Unfortunately, this host-to-guest bridge communication also exposes sensitive host data (e.g., location) to a potentially untrusted web page [23, 25].
Detecting such flows is difficult for two reasons. First, a precise call graph for a hybrid app depends on communication endpoints of the WebView, such as loaded URLs and injected scripts. In practice, these values are often computed at
Authors’ Contact Information: Jyoti Prakash, University of Southern Denmark, Odense, Denmark, [email protected]; Mikkel Baun Kjærgaard, University of Southern Denmark, Odense, Denmark, [email protected]; Abhishek Tiwari, University of Southern Denmark, Odense, Denmark, [email protected].
Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. Copyrights for components of this work owned by others than the author(s) must be honored. Abstracting with credit is permitted. To copy otherwise, or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. Request permissions from [email protected].
© 2026 Copyright held by the owner/author(s). Publication rights licensed to ACM.
Manuscript submitted to ACM runtime [10], for example, through string concatenation, resource lookup, or framework code. Consequently, static analyses may miss bridge communication entirely or merge multiple WebView instances to over-approximate communication context [25]. Second, dynamic analysis suffers from coverage issues. Even strong test-generation techniques, such as Test [10], cannot guarantee that all WebView-relevant paths will be exercised. A purely dynamic approach may therefore miss a flow simply because the triggering execution never occurs.
Existing hybrid app analyses only address part of this problem. HybriDroid [11] presented semantics of Java-JavaScript communication. However, their analysis mainly focused on type-mismatch bugs. LUDroid [23] targeted information flows via the Java-JavaScript bridge. It treats all sensitive data that leaves the host as a leak, flooding results with false positives. The most relevant related work [25] proposes a demand-driven IFC analysis for WebView bridges. However, their approach is based on static analysis, leaving dynamically computed targets unresolved. Additionally, their analysis relies on pre-existing call graphs that do not model WebView-specific lifecycle operations. WebViewJSdetect [28] advances the state of the art on the JavaScript side by leveraging concurrent abstract interpretation to detect vulnerabilities, such as unauthorized API access and privilege escalation, within the WebView’s execution context. However, it operates entirely within the guest and does not precisely model flows originated on the host.
In this paper, we present MifDroid, a static-analysis-guided dynamic-analysis framework for detecting cross-language information flows in Android hybrid apps. The main insight is that static and dynamic analysis are not alternatives but collaborators. The static phase identifies which components host WebViews, collects statically resolvable information, and marks the statically unresolved sites. The dynamic phase is then guided toward those sites to obtain concrete runtime values. This resolved data is leveraged to create a lifecycle-aware, per-WebView call graph. A type-and-effect system leverages this call graph to compute confidentiality and integrity effects at the bridge boundary. Finally, a modular JavaScript analysis phase translates the computed effects into synthetic stubs so that existing JavaScript analyzers can detect information flows.
In summary, this paper makes the following contributions:
Contributions
A hybrid analysis that constructs a per-WebView summary by combining static analysis, guided dynamic analysis, and conservative approximation.
A lifecycle-aware, per-WebView call graph that builds one communication context for each WebView instance. This avoids spurious cross-WebView edges and models WebView-specific communication semantics.
A type-and-effect system that runs on the program closed by the static and dynamic resolution of the first phase. It summarizes each bridge method by a confidentiality effect, recording whether the method may return a confidential value, and by integrity alarms, recording whether guest-controlled arguments may reach internal host state. This enables static detection of cross-language information flow violations.
A modular JavaScript analysis that translates effect summaries into instrumented stubs. This reduces cross-language flows to a standard single-language taint analysis.
An evaluation on 338 large-scale F-Droid apps that exercise the WebView bridge. We show that guided dynamic analysis adds 662 entries to the 444 produced by static analysis, raising the resolution rate from 36.5% to 74.5%, finds many evaluated JavaScript targets that static analysis misses, resolves bridge-related entries for 115 apps that static analysis leaves empty. MifDroid reported 14 vulnerable cross-language information flows. At the time of submission, five have been accepted and fixed by the corresponding maintainers, and the remaining are still open.
Motivation
Information-flow analysis of an Android hybrid app must follow data through both native code and JavaScript [11, 25]. A bridge method may return sensitive native data to JavaScript or use a JavaScript argument to update native state. Tracking these flows requires determining which scripts can call which native methods through each WebView.
Listing 1 illustrates this problem with a simplified credential leak. The Activity reads the interface name from R.string.bridge_name (line 14) and registers an API object under that name (line 15). It constructs the URL and loads a bundled HTML file on lines 16–18. Assume that the resource contains "bridge" and that Config.PAGE depends on runtime state, with value "app" in the execution shown. We label password on line 2 as sensitive () and treat System.out.println on line 7 as a public sink (). In app.html, bridge.getPassword() (line 22) calls the native method on line 4 and returns the password to JavaScript. JavaScript then passes it back to Java through bridge.log(pwd) (line 23), which reaches the public sink. The flow crosses the language boundary twice before reaching the sink.
A call graph is crucial for tracking this flow. The target of bridge.getPassword() depends on the object and name passed to addJavascriptInterface, together with the code loaded into the WebView. Static analysis can recover bridge from the resource table, but following Config.PAGE may yield only the URL prefix and suffix. Helper methods and runtime inputs can leave interface names or injected scripts unresolved [23]. Omitting an unresolved interaction can miss the flow; connecting it to every bridge in the app can introduce infeasible paths.
The WebView instance also matters. In Listing 1, interface registration on line 15 and page loading on line 18 use the same WebView, wv. Suppose a second WebView loads another page and registers a different object under the same name bridge. Calls through bridge in that page refer to the second object, not the API object registered on wv. An app-wide bridge model can incorrectly connect those calls to API.getPassword() in the first WebView. The analysis
class API {
private String password = "secret123"; // H
@JavascriptInterface
public String getPassword() { return password; }
@JavascriptInterface
public void log(String msg) {
System.out.println(msg); // L sink
}
}
class MainActivity extends Activity {
protected void onCreate(Bundle state) {
WebView wv = new WebView(this);
wv.getSettings().setJavaScriptEnabled(true);
String prefix = getString(R.string.bridge_name);
wv.addJavascriptInterface(new API(), prefix);
String url = "file:///android_asset/" +
Config.PAGE + ".html";
wv.loadUrl(url);
}
}
// --- app.html ---
var pwd = bridge.getPassword(); // pwd: H
bridge.log(pwd); // H → L
Listing 1. Java (top) and JavaScript (bottom). therefore needs to associate registrations and loaded scripts with the corresponding WebView instances. It must also track aliases, since two variables can refer to the same WebView instance.
Execution order adds another difficulty. Tiwari et al. show that updates to a registered object’s fields after loadUrl can affect the JavaScript loaded by that call [25]. Consider a variant of Listing 1 where the API class has a native setter for password and the Activity keeps a reference to the registered object. Immediately after loadUrl on line 18, the Activity uses the setter to store a new sensitive password. The JavaScript call on line 22 may receive this password through the getter on line 4, even though the native write appears after the loading call. An analysis that considers only the object’s state at registration or loading can miss the dependency from this later write to the getter’s return value. The analysis must therefore include subsequent native writes that may affect exposed bridge methods.
Dynamic execution can record concrete interface names, URLs, and injected scripts, but only for the paths it executes. Reaching the relevant WebView can require a sequence of UI actions or input supplied in another Activity [10]. In Listing 1, an execution with Config.PAGE="app" records file:///android_asset/app.html at the loadUrl call on line 18. This observation does not establish which other pages the same call may load in different executions.
The observed values must also be matched to native call sites. Suppose the same Activity also contains wv2.loadUrl(otherUrl), which loads only help.html into a second WebView, wv2. Assume static analysis cannot determine either filename. A log that records only a page URL and MainActivity does not identify which of the two calls produced that entry. If the observed URL of app.html is retained for both calls, the analysis treats that page as possibly loaded into wv2 as well. Suppose wv2 registers a different object as bridge, with its own getPassword method. The JavaScript call on line 22 can then be incorrectly connected to that object’s getter, even though app.html never runs in wv2. Dynamic refinement must therefore account for both execution coverage and ambiguity when matching observations to static call sites.
MifDroid uses static analysis to recover interface names, URLs, and JavaScript code passed to WebView APIs. It records arguments that remain unresolved and guides execution of the instrumented app toward the Activities and Fragments that create the relevant WebViews. The observed values are matched to unresolved call sites using the API, the Activity or Fragment, and any string fragments recovered statically. If several sites match, each keeps the observed value as a candidate. For each WebView allocation site, the analysis constructs a summary of the bound native objects, their interface names, and the JavaScript code that may execute there. The native analysis also identifies the exposed bridge methods and field updates that may affect those methods. Together, this information is used to connect JavaScript calls to native bridge methods in the inter-language call graph. Arguments that remain unresolved are handled by the fallback rules described in the next section.
Approach
Figure 1 gives an overview of MifDroid. Our analysis is centered on the WebView instance, since this is the point where Java–JavaScript communication is configured in a hybrid app. A WebView instance determines which interface objects are shared with JavaScript, under which names they are exposed, which bridge methods and object-state updates become visible through them, and which JavaScript artifacts may execute in the same communication context. Recovering this information is challenging because interface names, loaded URLs, injected scripts, and object updates are often computed at runtime. In addition, WebView follows peculiar semantics, such as asynchronous interaction and flow-insensitive updates of interface objects [25].

Figure 1. Overall Framework of MifDroid
Our approach has four steps. First, we construct a per-WebView summary (detailed in § 2.1). The summary captures the communication relation of a WebView instance: the interface summaries attached to it, where each such summary records the interface name, the bound object, its exposed bridge methods, and the relevant updates on that object, together with the JavaScript artifacts that may run in the same WebView. Second, we use these summaries to build a lifecycle-aware inter-language call graph (§ 2.2). Third, we run a type-and-effect analysis to compute the type, sensitivity, and effects of the bridge methods. Finally, we project these effects to JavaScript and reuse existing single-language analyzers to check JavaScript-side safety (§ 2.4).
Phase 1: WebView Summary Construction
In this phase, we construct a summary for each WebView instance. The summary captures the part of the native-side code that is relevant for Java–JavaScript communication. It records which interface objects may be attached to the WebView, under which names they are visible to JavaScript, which bridge methods they expose, which updates on these objects may affect communication, and which JavaScript artifacts may execute in the same WebView. At this stage, we do not need the full behavior of the app. We only need the communication-relevant part of the WebView and its associated native-side state.
We begin by identifying the WebView API calls that establish this communication. These include addJavascriptInterface for bridge registration, loadUrl, loadData, loadDataWithBaseURL, and evaluateJavascript for loading web content, including JavaScript. For each WebView instance, we keep track of the calls that may act on it and trace them backward in the native-side code. This recovers interface names, interface-object bindings, updates on bound interface objects, loaded resources, and injected scripts. Since these values are often resolved at runtime, Phase 1 proceeds in three steps: static value recovery, guided dynamic refinement, and conservative approximation.
Definition 3.1 (Tracked WebView APIs). The native side configures Java–JavaScript communication through the WebView APIs below. Each row fixes the kind of a call and the string argument that we record for it. The kind is determined by the invoked API alone. For addJavascriptInterface, we also record the bound native-side object as , since the interface name alone is not enough to recover the exposed bridge methods and the updates on that object.
| kind() | WebView API | Recorded argument |
| bridge | addJavascriptInterface(o, n) | , the interface name |
| load | loadUrl(u) | , the loaded URL |
loadData(d, ... ) | , the inline content | |
loadDataWithBaseURL(b, d, ... ) | , the base URL, and , the inline content | |
| inject | evaluateJavascript(s, ... ) | , the injected JavaScript code |
Table 1.
Definition 3.2 (Communication Sites). A communication site of an app is a pair of a WebView API call occurring in and one of the arguments that Theorem 3.1 records at ; a call contributes one site per recorded argument, and collects them all. We write for the recorded argument , for the WebView reference that is invoked on, and for the native-side method whose body contains . For the call wv.loadUrl(url) of Listing 1,
Let be the set of WebView allocation sites of , one for each new WebView(...) statement, and let be the allocation sites that a reference may denote. Each WebView then owns the sites that may act on it:
The alias information is obtained through light-weight one-object sensitive [17], flow-insensitive, field-sensitive points-to analysis.
Static analysis rarely recovers a string in full, so it records whatever part it does know as a pattern : literal text with wildcards standing for the parts it could not determine. The URL assembled on § 1 of Listing 1, for instance, comes out as file:///android_asset/*.html. A concrete string is compatible with , written , if substituting text for the wildcards of can produce . The pattern above is compatible with file:///android_asset/app.html, the value this app in fact loads. A pattern with no wildcard is exact: exactly one string is compatible with it. A bare wildcard, written , is unknown: every string is compatible with it. A pattern that mixes text and wildcards, like the one above, is partial.
Definition 3.3 (Resolution Map). A resolution map gives, for each communication site, the candidate values recovered for its argument: . A site whose value is not yet settled is kept in the unresolved set , paired with the pattern Step 1 recovered for it.
Step 1: Static value recovery. The static resolver is selective. It starts from communication-relevant WebView API calls and recovers only the values that affect Java–JavaScript communication. For each communication site , it starts from and traces the definitions that may reach that argument. Along this slice, it recovers string values through local assignments, recoverable field reads, common string constructors, and bounded flows through helper-method parameters and return values. When the slice reaches a bound interface object, it also collects updates on that object that may affect Java–JavaScript communication. These updates are kept as abstract field assignments and later become part of the WebView summary. It does not model full heap analysis, reflection, or arbitrary string solving. Step 1 is therefore focused on WebView communication, not on general-purpose string analysis.
For each site , the analysis computes one pattern . If it is exact, say "bridge", the value is settled and we set ; such sites need no further processing. Otherwise is partial, when part of the string is known but at least one component is not, or , when the value depends on runtime inputs, reflection, or flows the resolver does not model. We keep the unresolved part explicit rather than forcing an imprecise concrete value, and record the site as the pair in the unresolved set , which is passed to Step 2.
Step 2: Guided dynamic refinement. Step 1 leaves some values unresolved. These sites depend on values computed only at runtime. We therefore rewrite the app and insert logging calls around communication-relevant WebView APIs. During refinement, we consult only the runtime events that are relevant to unresolved sites. We instrument loadUrl, loadData, loadDataWithBaseURL, evaluateJavascript, and addJavascriptInterface. The logger records the API type, the observed string value, and the enclosing native-side context. At addJavascriptInterface, it also records the interface name and the runtime class of the bound interface object. At method entry, it emits the current context once and records nearby static URL or interface hints when they are available.
We then execute the instrumented app in a guided way. The static phase tells us which activities and fragments create the relevant WebView instances. We first try to launch these candidates directly. Next, we issue guided VIEW intents and perform a small deterministic sequence of UI actions. Finally, we use a seeded Monkey run as a fallback. The probe is not meant to cover the whole app. Its role is to expose unresolved WebView communication sites and record the concrete values that reach them.
Each runtime observation yields an event , where is the API type, is the native-side class observed at the logging point, and is the observed value. We use this information to match the event back to unresolved sites in the same WebView summary. In our implementation, ctxCompatible(, ) is checked first with the full native-side class.
Algorithm 1: Phase 1: Per-WebView Summary Construction. Steps 1 and 3 over-approximate; Step 2 narrows.
Input: APK a
Output: Per-WebView summary map Σ = {σ_w | w ∈ W}
1 W ← WebView allocation sites in a; W_bnd ← bundled .html/.js assets // Defs. 3.2, 3.4
2 C ← communication sites in a; Φ ← ∅; U ← ∅ // nothing recovered yet
3 Step 1: static value recovery
4 foreach c ∈ C do
5 ŝ ← StaticResolve(arg(c)) // one pattern, by slicing back from arg(c)
6 if ŝ is exact, say v then Φ(c) ← {v} // nothing left to determine
7 else U ← U ∪ {(c, ŝ)} // keep the pattern with the site
8 Step 2: guided dynamic refinement
9 Φ, U ← DynamicRefine(a, Φ, U) // Algorithm 2
10 Step 3: conservative over-approximation — Def. 3.4
11 foreach (c, ŝ) ∈ U do
12 switch kind(c) do
13 case bridge do Φ(c) ← bridgeCandidates(W_bnd)
14 case load do Φ(c) ← {s ∈ W_bnd | compatible(s, ŝ)}
15 case inject do Φ(c) ← {⊤} // any bridge call is possible
16 Summary assembly
17 foreach w ∈ W do Σ[w] ← (I_w, S_w) // Def. 3.5
18 return Σ
Algorithm 2: Step 2: Guided Dynamic Refinement
Input: APK a, resolution map Φ, unresolved set U
Output: Refined map Φ, residual unresolved set U
1 instrument the WebView calls of Theorem 3.1
2 execute the instrumented app with guided exploration
3 E ← collected runtime events ⟨τ, κ, v⟩
4 foreach (c, ŝ) ∈ U do
5 V ← {v | ⟨τ, κ, v⟩ ∈ E, τ = wvApi(c), ctxCompatible(κ, c)}
6 if ŝ is partial then V ← {v ∈ V | compatible(v, ŝ)}
7 if V ≠ ∅ then
8 Φ(c) ← V
9 U ← U \ {(c, ŝ)}
10 return Φ, U
name and then, when needed, with its simple name or source hint. If Step 1 has already recovered a partial value, we use it as an extra compatibility check. Algorithm 2 summarizes this refinement step.
For each , we collect the observed values in a set . If is partial, we discard from the values that do not match the structure already recovered statically, keeping . If , we refine the map by setting and remove from . The remaining sites proceed to Step 3. In this way, the dynamic phase refines the WebView summary without changing its structure.
Steps 1 and 3 over-approximate. Step 2 does the opposite. Replacing by a finite set of observed values is a narrowing, and it is justified only for the executions we exercised. We make this choice deliberately, since an unresolved site carries no usable information while an observed one does. § 2.5 states the resulting guarantee precisely.
Step 3: Conservative over-approximation. After Steps 1 and 2, some sites may still remain unresolved, i.e., they could not be resolved statically or from observed executions. For them, we apply a conservative fallback that depends on the API type.
Definition 3.4 (Fallback Rules). Let be the .html and .js files shipped inside the APK. For each remaining after Steps 1 and 2, we set
Here is the set of identifiers that occur as the receiver of a method call in a bundled asset.
The first rule scans the app’s bundled assets for interface-object uses, since the JavaScript code that invokes bridge methods is often shipped with the app itself. An interface name that stays is not usable, because the cross-language edges of § 2.2 reach a native method only through a concrete name. The second rule assumes that the WebView may load any bundled artifact that matches the partial information recovered in Step 1; if nothing is known statically, it may load any bundled artifact. The third rule records that an unresolved injected script may invoke any registered bridge method, which § 2.2 handles through a single unknown-JavaScript node.
Summary assembly. Once has been populated, we construct the summary for each WebView allocation site . It records the JavaScript artifacts associated with and one interface binding for each object that may be bound to the same WebView. Each binding fixes the interface name and the bound object, and thereby the bridge methods exposed through that object and the field updates on that object that may affect Java–JavaScript communication. It also records whether any artifact of is unavailable for analysis, which is the only distinction the construction needs to draw among unavailable artifacts, because a remote page and an unresolved script are equally opaque to a static JavaScript analyzer.
Definition 3.5 (Per-WebView Summary). The summary of a WebView has two components, : the objects JavaScript can reach through , and the JavaScript artifacts that may run in it.
A bridge site registers an object under a name, so the bindings of are
What else a binding contributes follows from and , so we look it up rather than store it: is the set of methods of , declared or inherited, that carry @JavascriptInterface, and collects the updates
Algorithm 3: Phase 2: Synthetic Entry Point Generation
Input: Per-WebView summaries
Output: Synthetic entry points
foreach with do /* Stage 1: setup */
create a fresh WebView abstraction for
enable JavaScript execution on /* Stage 2: registration */
foreach do
introduce a representative native-side object for
apply the summarized updates to
bind to on /* Stage 3: content loading */
foreach do dispatch into the execution context of /* Stage 4: interaction (non-deterministic) */
while do non-det. choose T1 | T2 | T3 | T4 | T5 // Def. 3.6 /* Stage 5: teardown */
terminate
on recovered along communication-relevant slices rooted at sites in , each a pair of a field and the pattern assigned to it.
The second component collects the JavaScript artifacts associated with :
The function assigns each artifact to a simple content abstraction. It yields for app-shipped or inline HTML and JavaScript. It yields for remote URLs. It yields for code injected through evaluateJavascript and for loadUrl("javascript:..."). If the artifact remains unresolved, it yields .
Phase 2: Inter-Language Call Graph Construction
Phase 2 takes the per-WebView summaries from Phase 1 and constructs the inter-language call graph. By this point, the communication relation of each WebView has already been recovered. Each summary tells us which interface bindings are available in the WebView and which JavaScript artifacts may execute in the same communication context. Phase 2 then connects the native-side edges, the JavaScript-side edges, and the cross-language edges induced by the summary.
The main challenge is that these cross-language edges do not follow ordinary language-level dispatch. A JavaScript-side call such as n.m(...) reaches a native-side method only because the interface name was previously bound to an object whose exposed methods contain . Standard call graph construction does not model this dispatch directly. We therefore synthesize a dedicated entry point for each WebView instance, inspired by FlowDroid’s dummy-main abstraction [1]. Algorithm 3 makes the relevant lifecycle and interaction behavior explicit for that WebView.
A WebView progresses through initialization, interface-object registration, content loading, interaction, and teardown. The first three stages are sequential. The analysis creates the WebView abstraction, enables JavaScript execution, introduces a representative object for each binding together with its summarized field state, and dispatches each JavaScript artifact in according to its abstraction. The interaction stage is the critical part. Here, the native side and JavaScript side execute asynchronously and may interleave non-deterministically.
Definition 3.6 (Non-deterministic Interaction Model). After registration and content loading, the interaction phase is a non-deterministic loop over five transitions, where ranges over :
| Transition | Direction |
| T1 JavaScript side calls for | JavaScript Native |
| T2 native side calls | Native JavaScript |
| T3 native side calls or | Native JavaScript |
| T4 apply a summarized update for | Native writes |
| T5 JavaScript side calls an exposed getter , , that reads for | JavaScript reads (via getter) |
T1 is unconditional. It applies even when contains or a RemoteScript. In such cases the JavaScript code is not known precisely, so we do not enumerate its control-flow graph. Instead we introduce an unknown-JavaScript abstraction, which may call any bridge method exposed through the same bindings. Transition T4 ranges over the field updates already summarized in Phase 1. We replay them flow-insensitively on the representative object so that the exposed getters called from JavaScript observe the same native-side state. JavaScript cannot access the fields of a bridge object directly, since WebView exposes only the methods annotated with @JavascriptInterface. T5 therefore reads a field only through an exposed getter; it is a special case of T1 that we list separately because it observes the updates replayed by T4. This preserves the native-side call edges needed for sound cross-language reasoning while keeping the construction modular.
The interaction loop is followed by a final teardown stage (), which bounds the model’s scope. Together, these stages give a per-instance abstraction of WebView that is sound when the communication relation is fully recovered statically, and otherwise conservative but coverage-dependent, since some edges may be learned from dynamic executions. The behavior is precise enough for downstream information-flow analyses and avoids the spurious merging that arises in app-wide models.
Cross-language edge semantics. The summary and the interaction model together define a per-instance call graph . Here and are the reachable native-side nodes and edges, and and are the JavaScript-side nodes and edges induced by , together with a summary node for unresolved JavaScript behavior. The set contains the cross-language edges. All three components are scoped to the same WebView instance, so JavaScript loaded in one WebView cannot spuriously invoke bridges registered on another.
The cross-language edges serve three purposes. First, they model JavaScript-to-native dispatch, from a call to the native-side method , but only when and . Second, they model native-to-JavaScript transfer for evaluateJavascript and loadUrl("javascript:..."). Third, they model flow-insensitive field edges, from the summarized native-side updates in to the exposed getters that read the same field and return its value to JavaScript, following the peculiar semantics of WebView [25].
We further apply two conservative rules. First, JavaScript code may redefine an exposed method by assigning a new JavaScript function to it, so a call through is connected to both the original Java implementation and any JavaScript-defined replacement [5, 18]. Second, deleting an individual bridge method does not remove the underlying Java entry point in the Android runtime; only deletion of the entire bridge object (delete ) removes those edges [25].
Stub Generation with Type and Effect System
In order to analyse the JavaScript part of the application by itself, we first have to determine the information flow effects of the bridge function. From the previous stage we have the call-graph and use it to compute the information flow labels, treating the bridge functions as entry-points. In principle, this could be achieved through a backward dataflow analysis; we instead use a type-and-effect system, which yields a compositional summary for each method that can be translated directly into a stub. In the following, we present the type-and-effect [8] system which computes each bridge method’s confidentiality and integrity effects over the inter-language call graph . The effects are translated into stubs which are then used by the modular JavaScript analysis.
The typing rules themselves follow the classic discipline of security type systems [26]: labels are propagated through explicit flows, a program-counter label captures implicit flows, and subtyping relates labeled types. Such type systems assume a closed world, in which the entire program is known statically and no code is generated at run time. A hybrid app violates this assumption: the names under which bridge objects are exposed, the pages that are loaded, and the scripts that are injected are frequently computed only at run time. Our contribution therefore does not lie in the individual rules, but in making this discipline applicable to an open program. The per-WebView summaries of Phase 1, refined by dynamic resolution and completed by the conservative fallback, close the program: they fix the bridge bindings and JavaScript artifacts, and hence the inter-language call graph over which the type system operates. Conversely, the type system complements the dynamic phase: it computes effects for all paths of the resulting call graph, including those that the dynamic exploration never executed, and exports them as stubs, so that the guest side can be analysed by existing single-language analysers.
We first define WV-PolyDSL, which encodes the constructs required for the discussion. Figure 2 shows the syntax of host statements in WV-PolyDSL. Variables are denoted by and , denotes a field name, a type, an object identifier, and a dynamically evaluated code fragment. The language includes object creation (), assignment (), field writes (), field reads (), and method calls (), where denotes a method; a call binds the formal parameters of to the actual arguments , executes , and stores the value returned by in . The construct registers a host-side object under the guest-side name “name”, making it accessible from guest code. The construct models dynamic evaluation of a JavaScript code fragment whose result is stored in ; in the host-side type-and-effect system, we conservatively treat this construct as skip, because the dynamically executed guest code is analyzed through the modular JavaScript phase rather than through additional host-side transfer rules. The statement performs a non-blocking update of field of the object identified by [25]. The statement returns the value of to the caller of the enclosing method; for a bridge method, this is how host data is handed back to the guest. We model it as a write of to a distinguished location of the enclosing method. To keep the semantics simple, we assume that occurs only in tail position, i.e., no statement of the method executes after it (it may still end both branches of a final conditional), so writing coincides with leaving the method. Early returns are eliminated beforehand by the standard transformation that stores the returned value in a fresh variable, sets a flag, and guards the remaining statements with that flag; since the flag becomes a guard, the dependence of the rest of the method on an early return is tracked through and . Sequential composition (), conditional execution (if then else ), and looping (while do ) complete the language with the standard semantics.

Figure 2. WV-PolyDSL: Host Syntax
Running example. In WV-PolyDSL, the host side of Listing 1 consists of the method onCreate, which registers the bridge and loads the page, and of the two bridge methods of API:
onCreate() : api = new API(); register(api, “bridge”); r = eval(app.html)
getPassword() : t = this.password; return t
log(msg) : u = println(msg)
The interface name “bridge” and the loaded page app.html are the values recovered for prefix and url in Phase 1, and loading the page is modeled by eval. The guest side, pwd = bridge.getPassword(); bridge.log(pwd), is not part of WV-PolyDSL; it is analyzed later by the JavaScript analyser.
Figure 3 defines the typing rules of WV-PolyDSL that statically capture whether a bridged method may result in an information leak. A labeled type pairs a base type () with a security label . A method type with is a parametric summary of a method , computed once and independently of its callers. Its parameter types are , where the label variable stands for the confidentiality label of the -th actual argument; likewise, inside the body of each formal parameter depends on itself, , standing for the bridge parameters on which the -th actual argument depends. Labels in the return type and the effect, the alarms in the effect, and the provenance of may mention and , and collects the constraints that the body imposes on fields and that mention them, e.g., and when the -th parameter is written to the field . These constraints are not solved inside but instantiated with the actual arguments at each call site (T-Call). The method type also annotates the signature with program-counter bounds and : the body of the method is typed under them, so the method may only be called in contexts whose program-counter label is at most and whose guards are included in . Typing judgments have the form , where is the program-counter label, an upper bound on the confidentiality of the guards that control whether executes, is the integrity program-counter context, the set of guard variables that control whether executes, is the typing environment, is the integrity environment, is a statement, and is the effect of . The type system is flow-insensitive: and are fixed for an entire method body, and the rules only constrain them instead of updating them from statement to statement. Each local variable, field, and return location therefore receives a single label (and provenance set) that over-approximates all assignments to it within the method. A method is therefore typed once as a whole and yields a single effect summary, which is all that stub generation requires. The effect consists of a confidentiality component and an integrity alarm predicate for external-to-internal flows. The confidentiality component states whether the method may return a confidential value: iff some return in may return a value that depends on data, either explicitly or because the return is controlled by an guard. The integrity component is a set of alarms , each recording that a value that may depend on the bridge parameter flows into the internal location . Let be the set of formal parameters of bridge methods, i.e., of methods exposed via addJavascriptInterface (@JavascriptInterface); these are the external inputs controlled by the guest. The integrity environment records, for each access path in , the bridge parameters on which its value may depend; a location is external iff , and each bridge parameter depends on itself (T-REGISTER). Recording the parameters themselves, rather than only whether a location is external, lets every alarm name the parameter responsible for the flow, which is exactly what stub generation needs. The internal locations , whose integrity is protected, are the host fields and the parameters of sink APIs.

Figure 3. Typing rules for the type-and-effect system over WV-PolyDSL.
Subtyping on labeled types requires and (Sub-Label); on method types it is contravariant in parameters and in the program-counter bound, and covariant in return type and effect (Sub-Method). The typing environment maps local variables, fields, and the return location of each method to labeled types, and methods to method types; the integrity environment maps the same locations to sets of bridge parameters. Security labels form a lattice ordered by with join operator . Sets of bridge parameters are ordered by inclusion, with union as join. Effects are ordered component-wise, iff and , and accumulate via across sequencing, where alarm sets are combined by union. For a destination and a set of bridge parameters , we write if , and otherwise. For a set of guards , we write for the bridge parameters on which the guards may depend. Rule Sub-Method defines sub-typing for method types with contravariant parameters and program-counter bounds, covariant return types, and monotonic effects: , , , , and . A method whose body is safe under a high program-counter label can thus be used wherever a lower bound is expected. The subsumption rule T-Sub lets a statement be typed with a larger effect, under a smaller program-counter label, and under fewer guards: over-approximating whether a value is returned confidentially or which alarms are raised is always safe, a statement that only writes locations at least as confidential as also satisfies the weaker requirement imposed by any , and a statement typed as if more guards controlled it raises at least as many alarms. Every rule that moves a value from a source into a location imposes the confidentiality constraint , which rules out both explicit flows and implicit flows through the guards recorded in . The rule also imposes the provenance constraint , so records every bridge parameter on which the value, or a guard controlling the assignment, may depend. T-New, T-Assign, and T-Read follow this pattern and have the trivial effect . Implicit flows are captured because every assignment inside a branch or loop body is typed under the raised and the extended guard set , so its target must be at least as confidential as the guard and its provenance includes that of the guard. T-Eval conservatively treats dynamic guest-code evaluation as skip at the host level, producing no additional effect. The injected script is therefore not analysed by the type system: its content is recovered in Phase 1, statically or from runtime observations (§ 2.1), extracted as a JavaScript artifact of the WebView (InjectedScript(s) in theorem 3.5), and analysed by the JavaScript analyser together with the stubs (§ 2.4). T-Register makes every formal parameter of every method of a registered bridge object depend on itself, . Fields constitute the internal host state that integrity protects. Besides propagating provenance, T-Write and T-Async raise alarms for the written field : one alarm for every bridge parameter on which the written value, or a guard controlling the write, may depend. Since provenance is propagated through local variables, fields, and calls, the alarm names the bridge parameter even when its value reaches only indirectly, e.g., through a local variable or a callee. T-Return constrains like an assignment and raises the confidentiality effect to , since the returned value leaves the method; for a bridge method, a return of data (or a return under an guard) is exactly a confidentiality leak to the guest. T-Seq, T-If, and T-While combine effects compositionally. Sequential composition joins confidentiality levels and combines alarm predicates with disjunction. Conditionals and loops type their branches (resp. loop body) under the raised program-counter label and add their guard to the guard set ; the guard label does not enter the effect directly, but a return under an guard yields through . All other rules have confidentiality component , since only T-Return hands a value back to the caller. Rule T-Call types a call , where raises the label of a type. It instantiates the parametric summary of with the substitution , which maps each label variable to the label of the -th argument, raised by , and each formal parameter to the bridge parameters on which the argument or a controlling guard depends. On alarms, acts as , and it leaves bridge parameters of other methods unchanged. The instantiated field constraints must hold, so that, e.g., an argument may be written by only to an field. The instantiated type must be a subtype of the type determined by the call site: the arguments’ types, raised by , the program-counter label and guard set of the call site as the bounds, and the type of the target . When the derivation tree is built, this premise is discharged by Sub-Method, and its premises by Sub-Label: contravariance requires and , covariance lets the instantiated return value flow into , and effect monotonicity makes the call inherit the instantiated alarms of the callee, which name the bridge parameters on which the actual arguments depend. The bound rules out implicit flows through calls: if is called under a high guard, then , so the body of is typed under and cannot write to low locations. Likewise, if is called under a guard , then , so every write of to an internal field raises for every . The premise accounts for calls under a raised program-counter label, and the provenance constraint passes the instantiated provenance of into , together with that of the guards. The confidentiality component of the call is , because the returned value is stored in rather than returned by the caller; if the caller later returns , T-Return accounts for it. Because the summary of refers to the arguments only through and , it is computed once, and each call site obtains its own instantiation. Finally, T-Sink types a call to a sink API , whose parameters are internal locations: it raises for every argument, i.e., one alarm for every bridge parameter that may reach the sink.
Running example: typing. For the running example, the policy labels the field password as (developer annotation, see below) and classifies println as a sink API, so its parameter println.p₁ is an internal location. Both methods are bridge methods, invoked from the guest with bounds and . For getPassword, the local and the return location receive the label :
The confidentiality component is because T-Return returns the -labeled value . For log, the formal parameter msg has the label variable and depends on itself, ; it reaches the sink:
Since , : the guest-controlled parameter msg reaches an internal location.
To illustrate the instantiation of summaries, suppose that log delegated to a helper, with . Typing write by T-Sink as above yields the parametric summary with , whose alarm names the helper’s own parameter . At the call site in log, T-Call instantiates it:
By effect monotonicity in Sᴜʙ-Mᴇᴛʜᴏᴅ, , so log obtains the same alarm as before, now naming its own bridge parameter . Finally, T-Rᴇɢɪsᴛᴇʀ for register(api, “bridge”) in onCreate requires , i.e., that the parameter of the exposed method log is a bridge parameter, which holds.
Algorithm 4 presents the interprocedural effect computation. It takes as input the call graph and a starting host method . The output is a mapping from methods to their computed effects. The algorithm initializes the typing environment and constructs the reachable call subgraph rooted at . It initializes all method summaries to the least effect . Then, it processes strongly connected components (SCCs) of in bottom-up topological order so that callees are analysed before their callers whenever the call relation is acyclic. For each SCC, it iteratively re-types each function body using TʏᴘᴇSᴛᴍᴛ under the current summary map until no summary changes. In this way, call sites consult the already-computed effects of their callees, while recursive SCCs are handled by fixpoint iteration from . The program-counter bounds and of a method, however, depend on its callers, which the bottom-up order visits later. The algorithm therefore starts from the bounds and, after each bottom-up pass, recomputes them from the call sites (line 15); the outer loop (line 6) repeats until neither the summaries nor the bounds change. Since labels, guard sets, and alarm sets range over finite lattices and all updates are monotone, the iteration terminates. Because the type system is flow-insensitive, TʏᴘᴇSᴛᴍᴛ does not follow the control flow of . It collects the constraints that the rules in Figure 3 impose on and for every statement of , typed under the bounds and of , with parameter labels and parameter provenance . It solves the constraints on the locals and of for the least labels and provenance sets, expressed in terms of and , and returns the parametric summary , where contains the remaining constraints on fields. Because callers only instantiate this summary, a single bottom-up pass in the topological order of the SCCs suffices for the flows of arguments and return values; the outer loop is needed only for the program-counter bounds. The bound is the join of the program-counter labels at all call sites of , and is the union of their guard sets; a bridge method is additionally invoked from the guest as an entry point, which contributes and , joined with the bounds of any host-side call sites. Here, supplies the security policy of the host side: the initial labels of sources, which are labeled , and the sink APIs, whose parameters are internal locations, so that a call to a sink API is typed by T-Sɪɴᴋ and raises an alarm for every bridge parameter that may reach it. The policy is a parameter of the analysis. One option is the Android source and sink classification of SuSi [20], which labels the values returned by source APIs; another is to let developers annotate sensitive variables and fields directly, as for the field password in Listing 1. The type of each callee carries its current summary as its effect. The effect of the whole body becomes the summary of . The analysis is conservative with respect to the constructed inter-language call graph: it may over-approximate flows, for instance because a field receives a single label for all writes to it, but it does not miss flows represented in the call graph.
Running example: summaries. Neither bridge method calls another host method, so Algorithm 4 needs a single pass, and the bounds of both methods remain . The resulting parametric summaries are
with empty field constraints , since neither method writes a field.
Sub-Label
Sub-Method
T-Sub
T-New
T-Eval
T-Register
T-Assign
T-Read
T-Write
T-Async
T-Return
T-Seq
T-If
T-While
T-Call
T-Sink
Algorithm 4: Interprocedural Effect Computation
Input: Call graph ; invoking host method
Output: Effect summaries : method and bounds
initial typing environment
subgraph of reachable from
foreach do
;
end
repeat
foreach SCC of in bottom-up topological order do
repeat
foreach do
end
until no summary changes
end
foreach do
join of over the call sites of in
end
until no summary and no bound changes
return
Modular JavaScript Analysis
MifDroid generates the stub for each bridge method. For each bridge method, it instantiates the summary for an invocation from the guest, mapping each to and each to , i.e., the arguments are public guest data and the bridge parameters themselves; for the resulting effect , it emits a stub object that mirrors the bridge API. For each formal parameter , it emits a sink marker __leak_value__(p_j) iff for some internal location , i.e., the parameter may flow, explicitly or through a guard, into the internal location (a host field or a sink parameter). Correspondingly, it emits a source marker (__sensitive_value__()) as the return value whenever . The generated JavaScript precisely exposes the confidentiality and integrity effects without requiring the Java implementation. Guest-side redefinition and deletion of bridge methods are not modeled in WV-PolyDSL, because they change only guest code. As described in § 2.2, a call is connected both to the original Java method and to any JavaScript replacement, and deleting an individual bridge method does not remove the underlying Java entry point. The stub therefore always carries the summary of the Java method, whereas a JavaScript replacement is ordinary guest code that the JavaScript analyser sees alongside the stub. The instrumented code (the original JavaScript code, including the scripts injected via evaluateJavascript and loadUrl("javascript:...") that Phase 1 recovered, prefixed with the instrumentation) is then provided to any off-the-shelf JavaScript analyser. On the guest side, each JavaScript analyser brings its own policy of sources and sinks; MifDroid adds __sensitive_value__() as a source and __leak_value__() as a sink to that policy. In this way, standard single-language tools detect every boundary-relevant flow, enabling a modular analysis.
Running example: stubs. Instantiating the summaries for an invocation from the guest (, ) leaves them unchanged. Since getPassword has , its stub returns a source marker; since the summary of log contains , its stub passes msg to a sink marker:
var bridge = {
getPassword: function () { return __sensitive_value__(); },
log: function (msg) { __leak_value__(msg); }
};
var pwd = bridge.getPassword(); // original guest code (app.html)
bridge.log(pwd);
A JavaScript analyser that treats __sensitive_value__() as a source and __leak_value__() as a sink now reports the flow from getPassword through pwd to log, i.e., the leak of the password to the public log, without analysing any Java code.
Soundness
Soundness of Interlanguage Callgraph
Phase 2 inherits its call-graph edges from the resolution map computed in Phase 1. We call an app statically resolved when Step 1 already yields only exact values, i.e., after static value recovery. In that case, the inter-language call graph is built entirely from statically recovered interface names, bridge objects, field summaries, and JavaScript artifacts.
Theorem 3.7 (Call-Graph Soundness for Statically Resolved Apps). Let be statically resolved, let be a WebView allocation site of , and let be the per-WebView inter-language call graph generated from . Then every concrete bridge registration, content-loading step, JavaScript-to-native dispatch, native-to-JavaScript transfer, and summarized field access reachable at runtime through the WebView instance represented by is contained in .
Every cross-language edge in Phase 2 comes from three ingredients recovered in Phase 1: (i) concrete bridge registrations, which determine the interface-summary environment ; (ii) concrete content-loading operations, which determine ; and (iii) the summarized bridge fields attached to each registered object. If Step 1 resolves all communication-relevant values exactly, then these ingredients are fixed statically.
Once and are exact, the rest of the construction is syntactic. Equations (1) and (2) enumerate the exact interface-summary and script components of , and Theorem 3.6 adds every transition by which the two sides may interact: bridge registration and loading, JavaScript-to-native calls, native-side callbacks into JavaScript, and summarized read/write effects on exposed bridge state. Therefore every concrete runtime communication event has a corresponding abstract edge in . The graph may still over-approximate JavaScript control flow or merge feasible interaction orders, but it does not miss any bridge-level communication through .
If Step 1 leaves a communication site unresolved, the pipeline delegates to Step 2, which refines with values observed during execution. This improves precision, but the guarantee changes: a dynamically recovered interface name, URL, or script fragment is justified only for the explored executions. A different execution may expose a previously unseen registration or content-loading behavior, and those edges need not appear in the graph.
Step 3 addresses this loss of coverage with a conservative fallback for sites that remain unresolved after dynamic analysis. The fallback does not guess one behavior. It replaces an unknown with a superset of behaviors that the graph construction must consider. This makes the over-approximation sound. The construction of is monotone in : enlarging can only add interface bindings, artifacts, or edges, never remove one. So if the fallback maps an unresolved site to a set containing every behavior still compatible with the static evidence, the resulting graph contains every concrete edge induced by that site.
The three fallback cases in Definition 3.4 all follow this pattern. For an unresolved bridge registration, collects every bridge interface referenced by the app’s bundled artifacts, so the analysis assumes any bundled bridge use may occur. For an unresolved content-loading site, the set contains every bundled artifact consistent with the partial string recovered statically, so any concrete bundled page or script matching that evidence remains represented. For an unresolved injected script, the fallback value is interpreted by the unknown-JavaScript abstraction in Theorem 3.6; transition T1 then allows calls to any bridge method exposed through . Although precise guest-side control flow is lost, every bridge dispatch that such an unseen script could perform remains in the abstract call graph.
At the level of concrete executions, the argument is direct. Consider a runtime event originating at a site still unresolved after Steps 1 and 2. If it is a bridge registration, the fallback includes all bridge candidates compatible with the bundled JavaScript. Equation (1) turns each candidate into an interface summary, so every later JavaScript-to-native call through that unresolved registration is matched by some abstract T1 edge. If it is a load operation, the concrete page or script must be one of the artifacts consistent with the partial static evidence. Step 3 inserts all such compatible artifacts into , and Equation (2) propagates them into . The abstract graph may contain extra pages or scripts, but it cannot omit the concrete one as long as it is consistent with the recovered evidence.
The injected-script case makes the point most clearly. If evaluateJavascript receives a string that the analysis cannot resolve, then no precise JavaScript control-flow graph is available, so the analysis uses instead of inventing a script. In the interaction model, means that the guest side may perform any bridge call allowed by . This is an over-approximation because many such calls may never occur, but it is sound because every call the unknown script could make is included. The same reasoning applies to summarized field accesses: if a field may be read or written along unresolved guest behavior, the abstract model keeps that possibility. Step 3 preserves soundness by turning missing knowledge into nondeterminism. It never resolves unknowns optimistically; it expands them into admissible behaviors and includes edges for all of them.
This conservative treatment does not make the dynamically refined part fully sound, because Step 2 may resolve a site only to the values that happened to be observed. The guarantee is therefore mixed. Static recovery yields genuine soundness. Residual unknowns handled conservatively remain safe. Dynamically sharpened sites are only as complete as the executions used to discover them. For this reason, the overall call-graph generation is sound, in the sense of the “soundness” position articulated by Livshits et al. [15]: it preserves a sound core where the static evidence suffices, and otherwise makes the remaining uncertainty explicit through conservative over-approximation rather than unsound optimistic resolution.
Soundness of Type and Effect System
The soundness of type and effect-system rests on the notion of non-interference for confidentiality and integrity properties. To motivate this, we first discuss the semantics of the WV-PolyDSL and then show the non-interference theorem for confidentiality and integrity properties.
Semantics. Security labels and effects are a static concern of the type system; the operational semantics is defined purely in terms of the concrete memory . Figure 4 defines the big-step relation , meaning that statement executed in memory terminates leaving memory . Branching (conditionals and loops) is resolved by testing the runtime value . We write for the field locations, for the restriction of to , and for updated with the values of on . A call (S-CALL) executes the callee in a fresh frame that shares only the heap with the caller and binds the formal parameters to the arguments; afterwards, the caller’s local variables are restored, the callee’s heap updates are kept, and the value of the callee’s is assigned to . Thus parameters and locals of the callee never overwrite those of the caller, also under recursion. The semantics executes async_update as an ordinary field write (S-ASYNC) and does not model the interleaving of host and guest execution. This suffices for the type system: since it is flow-insensitive, each field receives a single label and provenance set that bound every value written to it, independently of the order in which the writes and the guest-side reads occur. The interleavings themselves are over-approximated by the call graph, whose interaction loop (theorem 3.6) replays the summarized field updates flow-insensitively (T4) and lets the guest call the exposed getters that read these fields at any point (T5). Since such a getter is a bridge method, the value it returns is covered by its confidentiality effect: if the field may hold data, T-READ and T-RETURN give the getter , and its stub emits the source marker.

Figure 4. Big-step operational semantics of WV-PolyDSL over memory . The relation is purely store-transforming; security labels and effects are left to the type system. Conditionals (S-If-T/S-If-F) and loops (S-While-F/S-While-T) branch on the concrete runtime value .
The confidentiality label of each location partitions memory into a low-confidentiality part and a high-confidentiality part:
Two memories are low-confidentiality equivalent under , written , iff they agree on every location whose confidentiality label is :
Note that may differ between the two memories: high-confidentiality data is not observable by a low-confidentiality observer. Because the type system is flow-insensitive, (and likewise below) is a single environment for the whole method, so these partitions do not change during execution. In the theorems, and denote the union of the per-method environments for local variables and , the environment of the fields shared by all methods, and the method types; at each call, the callee’s method type is instantiated by T-CALL.
Dually, the integrity environment records on which bridge parameters each location may depend. For a bridge parameter , two memories are -integrity-equivalent under , written , iff they agree on every location that cannot depend on :
Locations that may depend on , in particular itself, may differ between the two memories, since models guest-controlled input. Thus, -integrity-equivalence means that the two memories differ at most in what the guest controls through .
The two equivalence relations above formalize what an observer is allowed to notice. For confidentiality, the observer can inspect only -labeled locations. A confidentiality violation therefore occurs when two runs start from memories that agree on all -labeled locations but differ on some -labeled locations, and this hidden difference later causes some -labeled output location to differ. For integrity, the protected part is the internal state. An integrity violation occurs when two runs differ only in the guest-controlled value of a bridge parameter , and this difference later changes some internal location. The non-interference theorems below state that well-typed programs rule out exactly these two situations, except that integrity violations are allowed only when the effect system records the corresponding alarm.
Both theorems below are termination-insensitive: they relate only executions that terminate , so information conveyed solely by (non-)termination is not covered.
Theorem 3.8 (Confidentiality Non-Interference). Let . If and for , then (i) ; and (ii) if , then , i.e., the returned value does not depend on high-confidentiality data.
Intuitively, the typing constraints ensure that no -labeled data can influence any -labeled location, so two executions that start from low-confidentiality-equivalent memories—which may have completely different high-confidentiality content—must produce low-confidentiality-equivalent output memories. The proof proceeds by structural induction on the derivation of . Since T-Sub is the only rule that is not syntax-directed and it only lowers and enlarges the effect, we may assume without loss of generality that the typing derivation of ends in the syntax-directed rule for , followed by at most one application of T-Sub; lowering only weakens the hypotheses we use, and enlarging the effect preserves the conclusions, because in part (ii) and alarms are only added. The base cases (S-New, S-Eval, S-Register) either allocate a fresh object or leave unchanged. The assignment cases (S-Assign, S-Read, S-Write, S-Async, S-Return) write to location (for S-Return, ); the constraint of the corresponding typing rule requires every source label to be whenever the target label is , so follows from the low-confidentiality-equivalence hypothesis. For the conditional cases, if the guard has label , then , both executions choose the same branch (S-If-T or S-If-F), and the induction hypothesis applies. If has label , both branches are typed under , so by the constraint they only write -labeled locations and leave unchanged, whichever branch is taken. The loop case (S-While-T/S-While-F) is analogous (for terminating executions), with the induction ordered by the height of the loop’s big-step derivation, equivalently the number of remaining S-While-T unfoldings before reaching S-While-F; this is a well-founded measure on natural numbers. The call case (S-Call) follows from the induction hypothesis for : instantiating the summary of with replaces each by the actual label of the -th argument, so the instantiated constraints and the subtyping premise of T-Call impose on the actual arguments and on the same label discipline that the body imposes on its parameters and , and since Sub-Method requires , the body of is typed under a program-counter label at least as high as that of the call site; in particular, a call under an guard only writes -labeled locations. For part (ii), means that every return in is typed under with ; hence both executions reach the same returns, and the returned values agree by part (i).
Theorem 3.9 (Integrity Non-Interference). Let and . If and for , then: (i) ; and (ii) for every internal location , if but , then . Hence, a bridge parameter can influence an internal location , explicitly or through a guard, only if the effect records .
The proof is again by structural induction on the derivation of , for a fixed . As before, T-Sub needs no separate case: dropping guards and adding alarms only weakens what has to be shown. For part (i), every rule that writes a location from a source imposes (for S-Call, on the parameters and on ). If , then is neither in nor in for any guard , so by the induction hypothesis the source and the guards agree in both executions, and so do the written values. The base cases (S-New, S-Eval, S-Register) are immediate. For conditionals, if , both executions take the same branch and the induction hypothesis applies; otherwise both branches are typed with , so every location they write has in its provenance and is not constrained by . Loops are analogous, with the induction ordered as in the proof of Theorem 3.8. For calls, the body of is typed under , the substitution maps each formal parameter to the provenance of the -th argument, so the instantiated alarms and provenance of refer to the caller’s bridge parameters, and the frame discipline of S-Call restores the caller’s locals. For part (ii), let with and . Then some write to (T-Write, T-Async, or T-Sink) occurs in at least one execution whose source or a controlling guard differs between the two executions; by part (i), applied to the execution up to that write, , so the rule emits . T-Call inherits the alarms of the callee, and T-Seq, T-If, and T-While take the union of the alarms of their parts, so the alarm reaches .
Soundness of stub generation
Let be the original hybrid program and the instrumented JavaScript program obtained by replacing each bridge method with the stub generated from its summary , instantiated for an invocation from the guest. The host-side theorems make the annotations of the stubs sound.
Lemma 3.10 (Soundness of the stubs). Let be a bridge method with formal parameters . (i) If the value that returns to the guest may depend on -labeled host data, then , and the stub of returns __sensitive_value__(). (ii) If the argument may influence an internal location , then , and the stub of passes to __leak_value__.
Part (i) follows from Theorem 3.8 (ii) and part (ii) from Theorem 3.9 (ii); the stub then emits the markers by construction. The stubs may contain additional markers, since the type system over-approximates.
The guest side is analyzed by an off-the-shelf JavaScript analyzer, whose soundness we do not re-establish. Instead, the soundness of MifDroid is compositional: it follows from the soundness of the call graph, of the host-side annotations, and of the guest analyzer.
Theorem 3.11 (Compositional Soundness). Let be a WebView. If (A1) the call graph is complete, (A2) the host policy is correct, and (A3) the guest analyzer is sound for its policy extended with the two markers, then MifDroid reports
every cross-language flow through , i.e., every flow from a confidential host value to a guest sink or an internal host location, and every flow from a guest source to an internal host location.
Here, completeness means that contains every bridge binding, script, and bridge call that occurs at run time through , and correctness of means that every confidential host value originates from an -labeled location and every host sink is in . By (A1), every bridge method invoked at run time through appears in and thus has a stub in , and every script that executes in is part of , or is covered by the unknown-JavaScript abstraction of transition T1. A flow from a confidential host value leaves the host through a return of a bridge method whose value depends on data, which by (A2) originates from an -labeled location; by Theorem 3.10 (i), this return is a source marker in . A flow into an internal host location enters the host through a bridge argument ; by Theorem 3.10 (ii), is passed to a sink marker. The part of the flow between the bridge boundary and the guest source or sink is guest code in , so by (A3) the guest analyzer reports it. Assumption (A1) holds for statically resolved apps by Theorem 3.7; for sites resolved dynamically in Step 2 it holds only for the explored executions, so the overall guarantee inherits the soundy character of the call graph. Host data spliced into injected scripts is excluded, as discussed in § 3.6.
Evaluation
We evaluate MifDroid on a large corpus of apps from F-Droid [7] and investigate four research questions:
RQ1 How prevalent and diverse are bridge-enabled WebViews in practice?
RQ2 How much does the dynamic phase improve over static analysis?
RQ3 How does MifDroid compare against the closest baseline?
RQ4 How effective is the end-to-end pipeline at detecting information flows?
Dataset and Experimental Setup
F-Droid provides a large corpus of open-source Android apps. Our selection of apps was based on two criteria. First, download the latest versions of the apps that use at least one instance of WebView, i.e., hybrid apps. Second, the apps should establish at least one active Java/Kotlin-to-JavaScript communication bridge, i.e., they should use at least one addJavascriptInterface call. After the first filter, we ended up with 4,126 apps; after the second, 338. For the resulting 338 apps, sizes are right-skewed, with a mean of 32.17 MB and a median of 13.65 MB (IQR: 5.10–39.79 MB), ranging from 0.06 MB to 436.60 MB. Table 1 summarizes the resulting corpus.
The static analysis phase extracts 1,047 bridge-binding entries from addJavascriptInterface call sites across these 338 apps. These bridge calls span 313 distinct initiating classes (classes that initiate WebView) and 288 distinct bridge classes (classes whose bridge object is shared with JavaScript), with an average of 2.99 exposed methods per bridge and a maximum of 62. At the string-resolution level, MifDroid produces 1,106 entries across 303 packages. Of the 444 entries that originate from the static phase alone, 162 are resolved precisely (confidence = 1.0), 106 are partially characterized (confidence = 0.3, where the call site and class are identified but argument values remain symbolic), and 176 are unresolved (confidence = 0.0). This mix of quality levels underscores the need for a subsequent dynamic analysis phase.
For the dynamic phase, we instrument and execute all 338 APKs on a single Android emulator. Rather than attempting exhaustive GUI exploration, the dynamic phase follows a bounded, heuristic strategy designed to reach WebView-hosting screens quickly. For each app, we (i) explicitly launch every WebView-relevant activity inferred from the manifest and the static analysis, (ii) issue a bounded number of deep-link VIEW intents, (iii) execute a deterministic sequence of key events, taps, and swipes, and (iv) run a seeded Monkey phase (seed = 42) as fallback exploration. Each app is given a 60-second log-capture window, with 3-second inter-launch delays, 5 seconds of initialization time, and 5 seconds of post-interaction settling time. The Monkey phase generates 180 or 280 events at 150 ms throttle, depending on whether WebView fragments were statically detected. Of the 338 apps, 309 completed successfully, yielding the runtime data analyzed in RQ2. The remaining 29 fail before producing any dynamic evidence, primarily due to repackaging errors, ABI mismatches, or SDK version constraints.
| Metric | Value |
| F-Droid apps after initial filtering | 4,126 |
Apps with at least one addJavascriptInterface call | 338 |
| Bridge-binding records | 1,047 |
| Distinct initiating classes | 313 |
| Distinct bridge classes | 288 |
| Average exposed methods per bridge | 2.99 |
| Packages with string-resolution entries | 303 |
| String-resolution entries | 1,106 |
| Dynamic-analysis apps (successful / total) | 309 / 338 |
| Static phase wall-clock time (5 workers) | 13.1 h |
| Dynamic phase wall-clock time (1 emulator) | 37.5 h |
| Mean dynamic time per app | 6.7 min |
Table 1 (PDF p. 24). Summary of the large-scale evaluation corpus.
Performance. The static analysis phase took approximately 13.1 hours using five parallel workers, corresponding to a throughput of 25.8 apps/hour. In the dynamic phase, each APK undergoes instrumentation, repackaging, installation on the emulator, automated exploration, and log collection. This resulted in approximately 37.5 hours, with a mean of 6.7 minutes per app. The end-to-end pipeline (static + dynamic) thus processes the entire corpus in just over two days on commodity hardware (a MacBook Pro 14-inch with 36 GB RAM and an Apple M3 Max processor).
RQ1: What does the bridge surface look like in real apps?
The Java-JavaScript bridge surface is substantial but unevenly distributed. At the per-app level, the median app registers two Java-JavaScript bridges, 251 apps register between two and five, 32 register between six and ten, and six apps register more than ten, with a maximum of 22. The bridge interface names are diverse: 170 distinct names appear across the corpus, the most common being _cordovaNative (146 occurrences), Android (100), and flutter_inappwebview (41).
Structurally, the bridge surface reflects substantial framework reuse across apps. Of the 313 initiating classes, 42 appear in more than one app, indicating repeated reuse of shared library code, and six appear in ten or more apps. The most recurrent are org.apache.cordova.engine.SystemWebViewEngine (69 apps), com.getcapacitor.MessageHandler (33), com.getcapacitor.plugin.CapacitorHttp and com.getcapacitor.plugin.CapacitorCookies (29 each), com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview.InAppWebView (20), and com.reactnativecommunity.webview.RNCWebView (15). A similar reuse pattern appears among the exposed bridge classes: org.apache.cordova.engine.SystemExposedJsApi alone occurs in 68 apps.
This observation has a direct methodological implication: counting class occurrences would overestimate diversity by repeatedly capturing the same library behavior. To avoid this, we report all metrics at the application and entry levels. Even so, the corpus still shows meaningful variation beyond the recurring Cordova, Capacitor, Flutter, and React Native infrastructures, with a long tail of app-specific bridge calls.
Answer to RQ1. Bridge-enabled WebViews are common in real-world apps, and the overall bridge surface is broad. At the same time, much of this surface is shaped by reused framework code rather than independent implementations. As a result, application-level counts provide a more accurate picture than raw class-level counts.
RQ2: How much does guided dynamic refinement help?
Across the 309 successful runs, the dynamic analysis phase observes 668 runtime WebView calls and retains 662 as usable. These are distributed across four API methods: loadUrl (394, 59.0%), evaluateJavascript (246, 36.8%), loadDataWithBaseURL (18, 2.7%), and loadData (10, 1.5%).
We noticed an interesting pattern when comparing the static and dynamic phases by method type. The 444 static entries come entirely from three methods: loadUrl (333), loadDataWithBaseURL (71), and loadData (40). In the current implementation, the static phase does not produce any evaluateJavascript entries. The dynamic phase contributes all 246 evaluateJavascript-backed entries. In practice, many evaluateJavascript arguments are constructed at runtime through callback dispatch or framework-level plumbing, which makes them difficult to resolve statically. The dynamic phase, therefore, complements the static analysis by capturing these targets.
Dynamic analysis substantially improves runtime WebView resolution. The static analysis phase produces 444 string-resolution entries (URLs/JavaScript Code). The dynamic phase then adds 577 new entries and upgrades 85 existing ones, bringing the final total to 1,106. All 662 dynamic entries are resolved by construction, since the target values are observed at runtime. Combined with the 162 statically resolved entries, 824 of the 1,106 entries (74.5%) carry a concrete resolution. Table 3 summarizes the key metrics.
| Metric | Value |
| Runtime calls observed / retained | 668 / 662 |
| Apps with ≥1 runtime witness | 211 / 309 (68.3%) |
| String-resolution entries (static / dynamic) | 444 / 662 |
| Final entries (full corpus) | 1,106 |
| Resolved at high confidence | 824 (74.5%) |
| New insertions / in-place upgrades | 577 / 85 |
| Unresolved entries before / after dynamic | 248 / 207 (−16.5%) |
| Apps bootstrapped from empty | 115 / 155 |
Table 3. Impact of guided dynamic analysis.
Of the 309 successful runs, 211 apps (68.3%) produce at least one usable runtime entry, and 30 apps resolve at least one previously unknown target. Notably, 155 apps have no string-resolution entries (URL/JavaScript code) from the static phase alone; i.e., the analysis detects addJavascriptInterface but is unable to locate the corresponding URL/JavaScript-invocation site, e.g., a call to the loadURL method. Dynamic analysis successfully resolved these entries for 115 apps.
To illustrate the difference between the two phases, Table 2 shows representative entries from the QuickNote app. The static phase resolves simple constant targets (row 1), but fails when the target depends on runtime state (row 2). The dynamic phase captures the actual values observed at runtime, including the concrete arguments that the static phase could not resolve (row 3).
| Resolved target (abbreviated) | Phase | Conf. |
| javascript:compatibility.sendNextLargeDownload() | S | 1.0 |
| UNRESOLVED_LOW_QUALITY_LITERAL: loadNote | S | 0.0 |
| javascript:window.writer.handleAction('prefill', '') | D | 1.0 |
Table 2. Sample entries for com.spisoft.quicknote, illustrating the three resolution outcomes. S = static, D = dynamic.
Answer to RQ2. The dynamic phase adds 662 entries to the 444 produced by static analysis, raising the overall resolution rate to 74.5%. It contributes all observed evaluateJavascript targets and provides first-time evidence for 115 apps that static analysis could not detect.
RQ3: How does MifDroid compare with IWandroid?
We compare MifDroid against IwanDroid, [25], the closest related work. IwanDroid is a purely static, demand-driven analysis for the information flow analysis via the WebView bridge. To keep the comparison fair, we ran it on our 338 apps. IwanDroid could successfully analyze only 126 of the 338 apps. For string resolution, IwanDroid covers 112 packages versus 303 for MifDroid.
On the 126 apps where both tools produce output, MifDroid detects 455 bridge-registration records compared with 280 for IwanDroid (1.63×). At the bridge-initiating class level, 171 classes are detected by both tools, 46 are found only by MifDroid, and 10 are found only by IwanDroid. The IwanDroid-only classes are exclusively Cordova InAppBrowser variants that do not register @JavascriptInterface methods; they are detected by IwanDroid as WebView-hosting classes but are not true bridge registrations.
IwanDroid produces 249 distinct resolution entries, of which only 83 (33.3%) are successfully resolved (a const-string value). The remaining 66.7% are explicit failure messages: 88 “We do not handle inter-procedural as of now” (35.3%), 39 “move-result-object need to handle it” (15.7%), and 39 other partially extracted values (15.7%). In contrast, MifDroid produces 1,106 entries with 824 resolved at high confidence (74.5%). Even when restricted to its static phase alone, MifDroid resolves 162 of 444 entries precisely (36.5%)—already comparable to IwanDroid’s best case—and the dynamic phase then raises the total resolved count to 824.
IwanDroid produces zero evaluateJavascript-related entries. MifDroid captures 246, all from the dynamic phase. Since evaluateJavascript is one of the key host-to-guest communication channels, this gap means IwanDroid misses an entire class of cross-language interactions. Table 4 summarizes the comparison. MifDroid covers 2.68× as many apps, produces 4.44× as many resolution entries, and resolves 9.93× as many of them. The advantage comes from three sources: (i) a more robust static front-end that handles inter-procedural string propagation, (ii) guided dynamic refinement that resolves targets invisible to any static analysis, and (iii) evaluateJavascript support, which IwanDroid lacks entirely.
| Metric | MifDroid | IwanDroid |
| Apps with bridge records | 338 | 126 |
| Bridge-registration records | 1,047 | 280 |
| Packages with resolution entries | 303 | 112 |
| Distinct resolution entries | 1,106 | 249 |
| Resolved entries | 824 (74.5%) | 83 (33.3%) |
| Unresolved entries | 176 (15.9%) | 166 (66.7%) |
| evaluateJavascript entries | 246 | 0 |
Table 4. Head-to-head comparison of MifDroid and IwanDroid on 338 bridge-bearing apps.
Answer to RQ3. On the same dataset, MifDroid substantially outperforms IwanDroid in both coverage and resolution quality: it analyzes all 338 bridge-bearing apps (vs. 126), resolves 74.5% of entries at high confidence (vs. 33.3%), and is the only tool that captures evaluateJavascript targets.
RQ4: How effective is MifDroid at detecting inter-language bugs?
We evaluated MifDroid’s ability to expose inter-language flows to three JavaScript-analysis backends: CodeQL, Semgrep, and WebViewJSDetect. Table 5 identifies each backend and its tool category. Because CodeQL and Semgrep do not natively recognize MifDroid’s instrumentation markers, we configured their analyses to treat these markers as sources and sinks. The aggregate results are reported in Table 6, and Table 7 provides the corresponding per-application breakdown.
| Tool | Type |
| CodeQL | Commercial |
| Semgrep | Commercial |
| WebViewJSDetect | Research prototype |
Table 5. Tools evaluated.
| Tool | Apps flagged | Apps w/ TP | # | TP | FP | Precision (%) |
| CodeQL | 14 | 11 | 30 | 20 | 10 | 66.67 |
| Semgrep | 13 | 0 | 369 | 0 | 369 | 0.00 |
| WebViewJSDetect | 0 | 0 | 0 | 0 | 0 | – |
Table 6. Summary statistics of detection results.
| Application | CodeQL | Semgrep | WebViewJSDetect | ||||||
| # | TP | FP | # | TP | FP | # | TP | FP | |
| at.tomtasche.reader | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| au.id.micolous.farebot | 1 | 1 | 0 | 4 | 0 | 4 | 0 | 0 | 0 |
| be.lionslink.ucllstudent | 2 | 2 | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
| ch.gassenarbeit.bern.your.rights | 2 | 2 | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
| com.a10miaomiao.bilimiao | 1 | 1 | 0 | 45 | 0 | 45 | 0 | 0 | 0 |
| com.activitymanager | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 |
| com.app.openlib | 1 | 1 | 0 | 48 | 0 | 48 | 0 | 0 | 0 |
| com.concealnetwork.concealmobile | 5 | 1 | 4 | 1 | 0 | 1 | 0 | 0 | 0 |
| com.cradle.iitc_mobile | 6 | 5 | 1 | 4 | 0 | 4 | 0 | 0 | 0 |
| com.duckduckgo.mobile.android | 2 | 2 | 0 | 73 | 0 | 73 | 0 | 0 | 0 |
| com.eightsines.firestrike.opensource | 3 | 3 | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
| com.github.samotari.paynoway | 2 | 0 | 2 | 58 | 0 | 58 | 0 | 0 | 0 |
| com.in.my.district | 2 | 0 | 2 | 66 | 0 | 66 | 0 | 0 | 0 |
| com.komodoplatform.komodowallet | 1 | 1 | 0 | 66 | 0 | 66 | 0 | 0 | 0 |
Table 7. Detection results on benchmark applications.
Table 6 reports, for each backend, the number of applications with findings, the true- and false-positives values, and the precision score. To compute the precision score, we manually inspected the reported flows to assign these classifications. Both authors independently evaluated the reported vulnerabilities.
CodeQL produced 30 unique vulnerabilities, out of which 20 were classified as TPs and 20 as FPs, yielding 66.67% precision. Semgrep produced 369 findings across 13 applications, all classified as FPs because its rules matched instrumentation markers rather than confirmed vulnerabilities. WebViewJSDetect produced no findings, for which precision is not defined. The per-application results in Table 7 show how these totals are distributed across the evaluated applications and report each backend’s total findings, TPs, and FPs. Together, the tables show that CodeQL identifies confirmed flows in a subset of applications, whereas the Semgrep findings require further refinement to distinguish marker matches from vulnerabilities; WebViewJSDetect reports no flows in this benchmark.
Case Study: Bridge-to-Sink Flows in IITC Mobile. MifDroid discovered a bridge-to-sink data flow with the CodeQL backend in the IITC mobile app (com.cradle.iitc_mobile, version code 104), a client for the Ingress Intel map. The app binds an IITC_JSInterface object to the name android. CodeQL found the two flows once MifDroid’s instrumentation markers were modeled as sources, so that every bridge return value counts as untrusted. In both cases MifDroid’s type check labels the Java method leaksData but not carriesConfidentialValues. Both flows are real data flows across the bridge, but neither is exploitable as reported. The interesting part is why, and what the second flow reveals once the Java side is examined. To assess the effectiveness of the analysis, we ran the application with native JavaScript-aware CodeQL, but it still failed to detect the bug.
if(typeof android !== 'undefined' && android && android.getFileRequestUrlPrefix) {
window.requestFile = function(callback) {
do {
var funcName = "onFileSelected" + parseInt(Math.random()*0xFFFF).toString(16);
} while(window[funcName] !== undefined)
window[funcName] = function(filename, content) {
callback(decodeURIComponent(filename), atob(content));
};
var script = document.createElement('script');
script.src = android.getFileRequestUrlPrefix() + funcName;
(document.body || document.head || document.documentElement).appendChild(script);
};
}
IITC lets plugins ask the user for a file. The JavaScript side builds a one-shot global callback and loads a script from a URL whose prefix comes from the bridge. A code snippet from this is shown in section 4.5. The instrumented stub returns __leaked__(result), so CodeQL tracks the return value of getFileRequestUrlPrefix() into script.src. It reports the flow as both js/xss and js/client-side-unvalidated-url-redirection. Taken at face value, whoever controls this value controls which code runs in the WebView. On the Java side, the method delegates to IITC_FileManager.getFileRequestPrefix(), which returns the constant "//file-request.iitcm.localhost/". The forward flow therefore carries no attacker-influenced data, and the alert is not exploitable as stated. Following the request further shows a different flow. The URL is protocol-relative and points at a host the app serves itself. IITC_WebViewClient.shouldInterceptRequest hands every http(s) request to a *.iitcm.localhost host to the file manager. For the file-request host, a FileRequest stores the first URL path segment as mFunctionName and opens a system file chooser. After the user picks a file, it streams back a script of the form
<mFunctionName>('<URL-encoded filename>', '<Base64 file content>');
Manuscript submitted to ACM
The filename is URL-encoded and the content Base64-encoded, but mFunctionName is inserted verbatim. Uri.getPathSegments() also percent-decodes it. The callback name thus makes a round trip, i.e, from JavaScript to Java → executed JavaScript, with no validation. This is the callback-injection pattern familiar from JSONP. In the main frame it grants nothing new, because a script that can choose the request path can already execute code. The Java side does not check which frame issued the request, so any content that can make the WebView fetch this host can trigger the file chooser and supply the callback. It still cannot read a file unless the user selects one. We therefore rate the round-trip weakness low. It matters methodologically more than practically, because it is a JS-to-Java-to-JS flow, and none of the analyses in this paper models that direction.
Answer to RQ4. MifDroid with CodeQL backend is able to find 30 vulnerabilities with the instrumented JavaScript, with a precision of 66.67%, while, Semgrep was ineffective in finding the vulnerabilities due to the limitations of the tool. This shows that MifDroid is effective in leveraging the strength of JavaScript only analysis to analyse information flow vulnerabilities across Android WebView applications. Four of the detected vulnerabilities were reported and confirmed by the developers.
Limitations and Threats to Validity
The coverage of the dynamic analysis phase is one threat to internal validity. In 98 cases, the dynamic analysis produced no meaningful results, i.e., the relevant WebView state is not reached within the bounded exploration budget. Thus, the reported gains should be interpreted as a lower bound of what hybrid design could produce.
A further limitation concerns host data embedded in injected scripts. MifDroid recovers the code of scripts injected through evaluateJavascript and loadUrl("javascript:...") and passes it to the JavaScript analyzer, but it does not track host values spliced into the script string, as in evaluateJavascript("handle("’" + token + "’")"). The recovered script contains the value of token only as a string constant, and the type-and-effect system treats the injection as skip (T-Eval), so no source marker is attached to it. Confidential host data that reaches the guest through this channel, rather than through the return value of a bridge method, is therefore not reported.
Another threat is how well our results generalize. We evaluate MifDroid only on the open-source F-Droid dataset, which does not necessarily reflect commercial apps, where obfuscation, proprietary SDKs, and deployment practices can be quite different. We mitigate this threat in part by evaluating on a large and diverse set of real F-Droid apps. In addition, the core challenges addressed by our approach, such as dynamic bridge targets and Java–JavaScript communication, are not unique to open-source apps.
Related Work
Yuan et al. [28] proposed WebViewJSdetect, which uses concurrent abstract interpretation to model the dynamic features of JavaScript within WebViews. Their approach navigates the dynamic nature of JavaScript analysis and is complementary to our work. Tiwari et al. [25] proposed a demand-driven information flow analysis for WebView bridges in Android hybrid apps with limited string resolution. Their approach relies on a pre-existing call graph that does not model WebView-specific operations, which can lead to imprecision. In contrast, our approach performs more precise string resolution and constructs the call graph using dynamic analysis. Additionally, our modular design translates inferred effects into JavaScript stubs that can be consumed by existing single-language JavaScript security analyzers. Hu et al. [10] introduced Test, a WebView-oriented testing technique that uses dynamic event-sequence generation to detect WebView bugs. However, Test targets bug detection rather than information flow analysis. Our approach instead uses static analysis to guide dynamic analysis and construct per-WebView summaries for inter-language information flow analysis. Polycruise [13] is a dynamic analysis framework for cross-language interactions, but it does not focus on information flow analysis or the specific semantics of WebView bridges. ReUnify [14] performs cross-language static analysis for JavaScript and Java in the context of react native apps. Lee et al. [11] proposed HybriDroid, which constructs an interprocedural CFG for hybrid apps but provides only limited support for IFC analysis, as its primary focus is detecting type-mismatch vulnerabilities. Tiwari et al. also proposed LuDroid [24] for IFC analysis of hybrid apps, but it relies on coarse assumptions (e.g., treating any shared bridge function as leaking), which significantly reduces precision. In contrast, our approach performs explicit dataflow tracking from sources to sinks without such conservative assumptions. AdLib [12], which analyzes data leaks in advertising libraries, is complementary to our work. Our framework can incorporate summaries of ad libraries augmented with integrity and confidentiality labels to generate the stubs for analysis. Bae et al. [2] formalized aspects of WebView semantics but focused on type-related vulnerabilities in bridge interfaces rather than information flow. Prior work has explored WebView security from multiple angles, including code-injection and cross-zone vulnerabilities [5, 21], application-specific analysis [16], and origin-related flaws [27]. Datta et al. [6] studied cross-boundary tracking via Java-to-JavaScript information diffusion, while Zhang et al. [29] focused on identity confusion vulnerabilities. In contrast, our work provides a general IFC-based framework to detect information flow violations across the bridge. Beyond WebViews, Buro et al. [3, 4] develop a general theory of multi-language semantics and abstraction; MifDroid instantiates a bridge-centric decomposition for the concrete case of Android WebViews, where the bridge is configured dynamically. Closer to our modular design, Prakash et al. [19] unify unilingual pointer analyses for multilingual FFI-based programs, and Roth et al. [22] propose AXA, which obtains a cross-language analysis by integrating existing single-language analyses. Like these approaches, MifDroid reuses single-language analyzers rather than building a joint one; it differs in exchanging information-flow summaries across the WebView bridge as JavaScript stubs.
Conclusion
We presented MifDroid, a hybrid analysis framework for detecting inter-language information flow violations in Android hybrid apps. We demonstrate that a combination of static and dynamic analysis achieves good coverage in resolving WebView targets. Modular design of MifDroid helped in detecting 14 vulnerable cross-language information flows and 45 arity mismatches, demonstrating that the hybrid design yields better results than either phase alone.
Data Availability The prototype implementation of MifDroid and the detailed experimental results are available at: https://doi.org/10.5281/zenodo.19248381.
References
- [1]Steven Arzt, Siegfried Rasthofer, Christian Fritz, Eric Bodden, Alexandre Bartel, Jacques Klein, Yves Le Traon, Damien Octeau, and Patrick McDaniel. 2014. FlowDroid: Precise Context, Flow, Field, Object-Sensitive and Lifecycle-Aware Taint Analysis for AndroidApps. In Proceedings of the 35th ACM SIGPLAN Conference on Programming Language Design and Implementation (Edinburgh, United Kingdom) (PLDI ’14). Association for Computing Machinery, New York, NY, USA, 259–269. doi:10.1145/2594291.2594299DOI
- [2]Sora Bae, Sungho Lee, and Sukyoung Ryu. 2019. Towards Understanding and Reasoning About Android Interoperations. In 2019 IEEE/ACM 41st International Conference on Software Engineering (ICSE). 223–233. doi:10.1109/ICSE.2019.00038DOI
- [3]Samuele Buro, Roy L. Crole, and Isabella Mastroeni. 2020. On Multi-language Abstraction. In Static Analysis, David Pichardie and Mihaela Sighireanu (Eds.). Springer International Publishing, Cham, 310–332.DOI
- [4]Samuele Buro, Roy L. Crole, and Isabella Mastroeni. 2023. On multi-language abstraction: Towards a static analysis of multi-language programs. Formal Methods in System Design 65, 1–3 (2023), 4–38. doi:10.1007/s10703-022-00405-8DOI
- [5]Erika Chin and David Wagner. 2014. Bifocals: Analyzing WebView Vulnerabilities in Android Applications. In Revised Selected Papers of the 14th International Workshop on Information Security Applications - Volume 8267 (Jeju Island, Korea) (WISA 2013). Springer-Verlag New York, Inc., New York, NY, USA, 138–159. doi:10.1007/978-3-319-05149-9_9DOI
- [6]Sohom Datta, Michalis Diamantis, Arjun Zafar, Junhua Su, Anupam Das, Jason Polakis, and Alexandros Kapravelos. 2026. Cross-Boundary Mobile Tracking: Exploring Java-to-JavaScript Information Diffusion in WebViews. In Proceedings of the Network and Distributed System Security Symposium (NDSS). San Diego, CA, USA.DOI
- [7]F-Droid Project. 2026. F-Droid. https://f-droid.org/
- [8]Chris Hankin Flemming Nielson, Hanne Riis Nielson. 1999. Principles of Program Analysis. Vol. 1. Springer Berlin, Heidelberg.DOI
- [9]Google. 2026. WebView. https://developer.android.com/reference/android/webkit/WebView. Android Developers Documentation.
- [10]Jiajun Hu, Lili Wei, Yepang Liu, and Shing-Chi Cheung. 2023. Test WebView-Oriented Testing for Android Applications. In Proceedings of the 32nd ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA 2023). ACM, 972–984. doi:10.1145/3597926.3598112DOI
- [11]Sungho Lee, Julian Dolby, and Sukyoung Ryu. 2016. HybridDroid: static analysis framework for Android hybrid applications. In Proceedings of the 31st IEEE/ACM International Conference on Automated Software Engineering (Singapore, Singapore) (ASE ’16). Association for Computing Machinery, New York, NY, USA, 250–261. doi:10.1145/2970276.2970368DOI
- [12]Sungho Lee and Sukyoung Ryu. 2019. Adlib: Analyzer for Mobile Ad Platform Libraries. Association for Computing Machinery, New York, NY, USA, 262–272. https://doi.org/10.1145/3293882.3330562
- [13]Wen Li, Jiang Ming, Xiapu Luo, and Haipeng Cai. 2022. PolyCruise: A Cross-Language Dynamic Information Flow Analysis. In 31st USENIX Security Symposium (USENIX Security 22). USENIX Association, Boston, MA, 2513–2530. https://www.usenix.org/conference/usenixsecurity22/presentation/li-wenDOI
- [14]Yonghui Liu, Xiao Chen, Pei Liu, John Grundy, Chunyang Chen, and Li Li. 2024. ReuNify: A Step Towards Whole Program Analysis for React Native Android Apps. In Proceedings of the 38th IEEE/ACM International Conference on Automated Software Engineering (Echternach, Luxembourg) (ASE ’23). IEEE Press, 1390–1402. doi:10.1109/ASE56229.2023.00113DOI
- [15]Benjamin Livshits, Manu Sridharan, Yannis Smaragdakis, Ondřej Lhoták, José Nelson Amaral, Bor-Yuh Evan Chang, Samuel Z. Guyer, Uday P. Khedker, Anders Møller, and Dimitrios Vardoulakis. 2015. In Defense of Soundness: A Manifesto. Commun. ACM 58, 2 (2015), 44–46. doi:10.1145/2644805
- [16]Amit Kr Mandal, Agostino Cortesi, Pietro Ferrara, Federica Panarotto, and Fausto Spoto. 2018. Vulnerability analysis of Android auto infotainment apps. In Proceedings of the 15th ACM International Conference on Computing Frontiers (Ischia, Italy) (CF ’18). Association for Computing Machinery, New York, NY, USA, 183–190. doi:10.1145/3203217.3203278DOI
- [17]Ana Milanova, Atanas Rountev, and Barbara G. Ryder. 2005. Parameterized Object Sensitivity for Points-to Analysis for Java. ACM Trans. Softw. Eng. Methodol. 14, 1 (Jan. 2005), 1–41. doi:10.1145/1044834.1044835DOI
- [18]Matthias Neugschwandtner, Martina Lindorfer, and Christopher Platzer. 2013. A View to a Kill: WebView Exploitation. In Presented as part of the 6th USENIX Workshop on Large-Scale Exploits and Emergent Threats. USENIX, Washington, D.C. https://www.usenix.org/conference/leet13/workshop-program/presentation/Neugschwandtner
- [19]Jyoti Prakash, Abhishek Tiwari, and Christian Hammer. 2025. Modular unification of unilingual pointer analyses to multilingual FFI-based programs. Science of Computer Programming 243 (2025), 103278. doi:10.1016/j.scico.2025.103278DOI
- [20]Siegfried Rasthofer, Steven Arzt, and Eric Bodden. 2014. A Machine-learning Approach for Classifying and Categorizing AndroidSources and Sinks. In NDSS. doi:10.14722/ndss.2014.23039
- [21]Claudio Rizzo, Lorenzo Cavallaro, and Johannes Kinder. 2018. BabelView: Evaluating the Impact of Code Injection Attacks in Mobile Webviews. In Research in Attacks, Intrusions, and Defenses, Michael Bailey, Thorsten Holz, Manolis Stamatogiannakis, and Sotiris Ioannidis (Eds.). Springer International Publishing, Cham, 25–46.DOI
- [22]Tobias Roth, Julius Näumann, Dominik Helm, et al. 2024. AXA: Cross-Language Analysis through Integration of Single-Language Analyses. In Proceedings of the 39th IEEE/ACM International Conference on Automated Software Engineering (ASE). ACM, 1195–1205. doi:10.1145/3691620.3696193DOI
- [23]Abhishek Tiwari, Jyoti Prakash, Sascha Groß, and Christian Hammer. 2020. A Large Scale Analysis of Android – Web Hybridization. Journal of Systems and Software 170 (2020), 110775. doi:10.1016/j.jss.2020.110775DOI
- [24]Abhishek Tiwari, Jyoti Prakash, Sascha Groß, and Christian Hammer. 2019. LUDroid: A Large Scale Analysis of Android – Web Hybridization. In 2019 19th International Working Conference on Source Code Analysis and Manipulation (SCAM). 256–267. doi:10.1109/SCAM.2019.00036DOI
- [25]Abhishek Tiwari, Jyoti Prakash, and Christian Hammer. 2023. Demand-driven Information Flow Analysis of WebView and Android Hybrid Apps. In 2023 IEEE 34th International Symposium on Software Reliability Engineering (ISSRE). 415–426. doi:10.1109/ISSRE59848.2023.00020DOI
- [26]Dennis Volpano, Cynthia Irvine, and Geoffrey Smith. 1996. A Sound Type System for Secure Flow Analysis. Journal of Computer Security 4, 2–3 (1996), 167–187. doi:10.3233/JCS-1996-4206
- [27]Guangliang Yang, Jeff Huang, Guofei Gu, and Abner Mendoza. 2018. Study and Mitigation of Origin Stripping Vulnerabilities in Hybrid-postMessage Enabled Mobile Applications. In 2018 IEEE Symposium on Security and Privacy (SP). 742–755. doi:10.1109/SP.2018.00043DOI
- [28]Zhanhui Yuan, Zhi Yang, Jinglei Tan, and Nongqi Zhang. 2026. WebViewJSDetect: Javascript vulnerability detection in android webview via coverage-guided thread-adaptive concurrent abstract interpretation. Computer Networks 275 (2026), 111908. doi:10.1016/j.comnet.2025.111908DOI
- [29]Lei Zhang, Zhibo Zhang, Ancong Liu, Yinzhi Cao, Xiaohan Zhang, Yanjun Chen, Yuan Zhang, Guangliang Yang, and Min Yang. 2022. Identity Confusion in WebView-based Mobile App-in-app Ecosystems. In 31st USENIX Security Symposium (USENIX Security 22). USENIX Association, Boston, MA, 1597–1613. https://www.usenix.org/conference/usenixsecurity22/presentation/zhang-lei