Skip to content

fix(web): isolate untrusted remote content - #6545

Open
SeniorZhai wants to merge 4 commits into
masterfrom
agent/isolate-untrusted-web-content
Open

fix(web): isolate untrusted remote content#6545
SeniorZhai wants to merge 4 commits into
masterfrom
agent/isolate-untrusted-web-content

Conversation

@SeniorZhai

Copy link
Copy Markdown
Member

What changed

  • Restrict wallet-enabled WebViews to the reviewed HTTPS origin.
  • Register wallet JavaScript bridges only for injectable pages.
  • Block mixed content and validate fallback navigation.
  • Validate app-card cover URLs and make save/share cache-only.

Why

Wallet bridges could remain available after cross-origin navigation, and the asset-origin check always evaluated true. App-card cover URLs could also trigger requests to local or private targets during rendering or save/share.

Impact

Cross-origin links open in the system browser. Unsafe app-card covers are ignored, and uncached covers cannot be saved or shared. The existing app-wide cleartext/HTTP network policy is unchanged.

Validation

  • ./gradlew --no-configuration-cache :app:testGooglePlayDebugUnitTest --tests one.mixin.android.vo.AppCardDataTest
  • ./gradlew --no-configuration-cache :app:compileGooglePlayDebugKotlin

Restrict wallet bridges to the reviewed HTTPS origin and block mixed content.

Reject unsafe app-card cover targets and avoid fetches during save or share.

Leave the app-wide cleartext network policy unchanged.
Copilot AI review requested due to automatic review settings July 30, 2026 05:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens embedded web content and app-card media handling against untrusted origins and network targets.

Changes:

  • Restricts injectable WebViews to HTTPS origins and blocks mixed content.
  • Validates app-card cover URLs before display.
  • Makes app-card save/share operations cache-only.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
app/src/test/java/one/mixin/android/vo/AppCardDataTest.kt Adds cover URL validation tests.
app/src/main/java/one/mixin/android/vo/AppCardData.kt Introduces app-card image URL filtering.
app/src/main/java/one/mixin/android/ui/web/WebFragment.kt Adds WebView origin restrictions and bridge gating.
app/src/main/java/one/mixin/android/ui/media/pager/MediaPagerActivity.kt Resolves app-card images only from disk cache.
app/src/main/java/one/mixin/android/ui/conversation/holder/AppCard.kt Filters cover URLs before rendering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/src/main/java/one/mixin/android/vo/AppCardData.kt
Comment thread app/src/main/java/one/mixin/android/ui/web/WebFragment.kt
@SeniorZhai SeniorZhai added the testing Now testing, but you can review label Jul 30, 2026
Filter every DNS result and verify the connected route for image requests, including redirects.

Disable proxies for the image client so target resolution cannot bypass the public-address policy. Cover IPv6 ULA and reserved ranges.
Block untrusted subframe requests without launching the system browser.

Track redirects and external navigation only for main-frame requests.
Copilot AI review requested due to automatic review settings July 30, 2026 07:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (3)

app/src/main/java/one/mixin/android/util/image/ImageNetworkPolicy.kt:66

  • This condition blocks the entire 192.0.0.0/16, not just the non-public special-purpose ranges inside it. Globally routable hosts in that /16 (for example, 192.0.66.0/24) will therefore fail every Coil image request now that this policy is installed app-wide. Narrow this check to the actual reserved prefixes (and preserve the globally reachable exceptions in 192.0.0.0/24).
                bytes[0] == 192 && bytes[1] == 0 ||

app/src/main/java/one/mixin/android/util/image/ImageNetworkPolicy.kt:39

  • The HTTPS check is only applied to the original app-card URL. OkHttp follows HTTPS-to-HTTP redirects by default, and this interceptor validates only the connected address, so an accepted cover can still be fetched over cleartext after a redirect. Revalidate the scheme on redirect requests (without blocking unrelated direct HTTP image loads, which the PR says remain supported).
private object PublicImageNetworkInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val address = chain.connection()?.route()?.socketAddress?.address
        if (address == null || address.isBlockedImageAddress()) {
            throw IOException("Blocked non-public image target")
        }
        return chain.proceed(chain.request())

app/src/main/java/one/mixin/android/ui/media/pager/MediaPagerActivity.kt:471

  • This returns the disk-cache backing file after use has closed the snapshot. Once the snapshot is closed, Coil may evict or replace that entry; save copies it shortly afterward, but sharing hands the URI to another app that may open it much later, so the share can fail if the cache changes. Copy the cached bytes to a stable temporary file while the snapshot is open and return that copy.
                imageLoader.diskCache?.openSnapshot(coverUrl)?.use { snapshot ->
                    snapshot.data.toFile()
                }

Comment on lines +1043 to +1046
},
),
"_mw_",
)
…sted-web-content

# Conflicts:
#	app/src/main/java/one/mixin/android/ui/conversation/holder/AppCard.kt
Copilot AI review requested due to automatic review settings July 31, 2026 05:41
@SeniorZhai
SeniorZhai marked this pull request as ready for review July 31, 2026 05:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

app/src/main/java/one/mixin/android/ui/web/WebFragment.kt:995

  • addJavascriptInterface exposes these objects to every frame, not just the trusted top-level origin. The URL gate does not close that boundary: Android does not call shouldOverrideUrlLoading for POST navigations, so a trusted page can submit a cross-origin form into an iframe and that untrusted frame receives MixinContext and _mw_. Use an origin-scoped messaging mechanism (or otherwise ensure untrusted child documents cannot receive wallet capabilities) rather than relying on navigation interception around a frame-global bridge.
            if (injectable) {
                webAppInterface?.let { webView.addJavascriptInterface(it, "MixinContext") }
                webView.addJavascriptInterface(
                    Web3Interface(

app/src/main/java/one/mixin/android/util/image/ImageNetworkPolicy.kt:67

  • This condition blocks the entire 192.0.0.0/16, although the special-purpose blocks here are narrower (for example, 192.0.0.0/24 and 192.0.2.0/24). Because this policy is installed on the app-wide Coil loader, otherwise valid public images whose DNS resolves to addresses such as 192.0.1.x are rejected as private targets. Narrow this check to the actual non-public prefixes rather than filtering the full /16.
            bytes[0] == 0 ||
                bytes[0] == 100 && bytes[1] in 64..127 ||
                bytes[0] == 192 && bytes[1] == 0 ||
                bytes[0] == 198 && bytes[1] in 18..19 ||

app/src/main/java/one/mixin/android/ui/media/pager/MediaPagerActivity.kt:470

  • use closes the Coil snapshot before the returned cache file is consumed. Once closed, the entry is eligible for update or LRU eviction; save reads it only after this function returns, and sharing hands the URI to another app that may open it later. A concurrent cache write/trim can therefore delete the file and make save/share fail. Copy the data to a stable temporary file while the snapshot is open (and manage that file's cleanup) instead of returning the cache-owned path.
                imageLoader.diskCache?.openSnapshot(coverUrl)?.use { snapshot ->
                    snapshot.data.toFile()

app/src/main/java/one/mixin/android/util/image/ImageNetworkPolicy.kt:39

  • The interceptor validates only the connected address, so an app-card cover that passes the initial HTTPS check can still follow OkHttp's default HTTPS-to-HTTP redirect and fetch over cleartext. Redirect follow-ups pass through here without a scheme check, while this app explicitly permits cleartext traffic. Preserve the app-wide HTTP policy for unrelated images, but ensure app-card requests reject cross-scheme redirects (for example via a dedicated/tagged request policy).
        val address = chain.connection()?.route()?.socketAddress?.address
        if (address == null || address.isBlockedImageAddress()) {
            throw IOException("Blocked non-public image target")
        }
        return chain.proceed(chain.request())

Comment on lines +65 to +68
bytes[0] == 100 && bytes[1] in 64..127 ||
bytes[0] == 192 && bytes[1] == 0 ||
bytes[0] == 198 && bytes[1] in 18..19 ||
bytes[0] >= 224
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

testing Now testing, but you can review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants