fix(mobile): allow self-hosted plain-HTTP servers on Android release builds - #15559
Conversation
…builds PR toeverything#15118 tightened release builds to block cleartext traffic and mixed content, which broke connecting to self-hosted instances served over plain HTTP (toeverything#15438): the WebView fetch to <server>/graphql fails and the app shows a generic "Network error", while browsers load the same server fine. Restore the behavior established by toeverything#13279 and toeverything#13435: - manifest: always allow cleartext traffic - WebView: always allow mixed content; the only page loaded is the bundled local app, and every other request targets the user-configured server, which may be plain HTTP for self-hosted instances Fixes toeverything#15438
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Team
Run ID: f9f1aa16-eab0-4306-b4d8-a6681ab0f3ac
📒 Files selected for processing (3)
packages/frontend/apps/android/App/app/build.gradlepackages/frontend/apps/android/App/app/src/main/AndroidManifest.xmlpackages/frontend/apps/android/App/app/src/main/java/app/affine/pro/MainActivity.kt
💤 Files with no reviewable changes (1)
- packages/frontend/apps/android/App/app/build.gradle
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
| android:hardwareAccelerated="true" | ||
| android:supportsRtl="true" | ||
| android:usesCleartextTraffic="${usesCleartextTraffic}" | ||
| android:usesCleartextTraffic="true" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
app_root='packages/frontend/apps/android/App'
rg -n -C 5 \
'usesCleartextTraffic|networkSecurityConfig|targetSdk|http://|https://|serverUrl\(|eventSource\(|Request\.Builder|Authorization|Cookie|token|session' \
"$app_root"Repository: toeverything/AFFiNE
Length of output: 50376
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo '== repository conventions and learnings =='
find /tmp/coderabbit-repo-knowledge/toeverything-affine-a85df9b5 \
-maxdepth 2 -type f -name '*.md' -print
echo '== manifest and Android configuration =='
cat -n packages/frontend/apps/android/App/app/src/main/AndroidManifest.xml
sed -n '1,60p' packages/frontend/apps/android/App/app/build.gradle
sed -n '1,20p' packages/frontend/apps/android/App/gradle/libs.versions.toml
echo '== direct service definitions =='
rg -l --glob '*.kt' 'class[[:space:]]+(GraphQLService|SSEService)|interface[[:space:]]+(GraphQLService|SSEService)' \
packages/frontend/apps/android/App/app/src/main/javaRepository: toeverything/AFFiNE
Length of output: 8119
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo '== applicable repository guidance =='
cat /tmp/coderabbit-repo-knowledge/toeverything-affine-a85df9b5/architecture/android-client.md
cat /tmp/coderabbit-repo-knowledge/toeverything-affine-a85df9b5/architecture/repo-guidelines.md
cat /tmp/coderabbit-repo-knowledge/toeverything-affine-a85df9b5/architecture/service-communication.md
echo '== service structure =='
ast-grep outline packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/service/GraphQLService.kt
ast-grep outline packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/service/SSEService.kt
echo '== service implementations =='
cat -n packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/service/GraphQLService.kt
cat -n packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/service/SSEService.ktRepository: toeverything/AFFiNE
Length of output: 15337
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo '== shared HTTP client and server URL source =='
rg -l --glob '*.kt' \
'object[[:space:]]+OkHttp|class[[:space:]]+OkHttp|getCurrentServerBaseUrl|fun[[:space:]]+getCurrentServerBaseUrl' \
packages/frontend/apps/android/App/app/src/main/java
rg -n -C 8 --glob '*.kt' \
'object[[:space:]]+OkHttp|class[[:space:]]+OkHttp|getCurrentServerBaseUrl|Authorization|Cookie|Bearer|accessToken|refreshToken' \
packages/frontend/apps/android/App/app/src/main/java/app/affine/proRepository: toeverything/AFFiNE
Length of output: 50375
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo '== shared client and cookie policy =='
sed -n '21,120p' packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/service/OkHttp.kt
echo '== authentication requests using the configured endpoint =='
sed -n '320,375p' packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/plugin/AuthPlugin.kt
echo '== endpoint configuration bridge =='
rg -n -C 6 --glob '*.ts' --glob '*.tsx' --glob '*.kt' \
'getCurrentServerBaseUrl|serverBaseUrl|self-hosted|selfHosted|serverUrl' \
packages/frontend/apps/android/App packages/frontend/coreRepository: toeverything/AFFiNE
Length of output: 50375
Security Misconfiguration (CWE-319): Cleartext Transmission of Sensitive Information
Reachability: External · Exploitability: Moderate
Do not enable cleartext traffic application-wide.
usesCleartextTraffic="true" permits cleartext connections from every app component. GraphQLService and SSEService use the configured server with the shared OkHttp.client, which loads stored authentication cookies by host. Keep HTTPS as the default, and enforce a host-scoped HTTP opt-in with a warning before sending credentials or session cookies over HTTP.
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
app_root='packages/frontend/apps/android/App'
rg -n -C 6 \
'MIXED_CONTENT_|loadUrl\(|loadData|shouldInterceptRequest|shouldOverrideUrlLoading|http://|<script|<iframe|fetch\(|XMLHttpRequest|addJavascriptInterface|evaluateJavascript' \
"$app_root"Repository: toeverything/AFFiNE
Length of output: 41735
🏁 Script executed:
set -euo pipefail
knowledge_root=/tmp/coderabbit-repo-knowledge/toeverything-affine-a85df9b5
printf '%s\n' '--- knowledge files ---'
find "$knowledge_root" -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- MainActivity outline ---'
ast-grep outline packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/MainActivity.kt
printf '%s\n' '--- MainActivity relevant source ---'
sed -n '1,190p' packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/MainActivity.kt
printf '%s\n' '--- WebView and client definitions ---'
sed -n '1,180p' packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/AffineWebViewClient.kt
sed -n '1,180p' packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/AffineEditorWebView.kt
printf '%s\n' '--- bounded WebView/server references ---'
rg -n -C 5 'serverBaseUrl|currentServer|loadUrl|setWebViewClient|AffineWebViewClient|trustedOrigin|shouldInterceptRequest|MIXED_CONTENT|eventSource|serverUrl' \
packages/frontend/apps/android/App/app/src/main/java/app/affine/pro \
packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/serviceRepository: toeverything/AFFiNE
Length of output: 32383
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- Android architecture and repository guidance ---'
sed -n '1,220p' /tmp/coderabbit-repo-knowledge/toeverything-affine-a85df9b5/architecture/android-client.md
sed -n '1,180p' /tmp/coderabbit-repo-knowledge/toeverything-affine-a85df9b5/architecture/service-communication.md
sed -n '1,160p' /tmp/coderabbit-repo-knowledge/toeverything-affine-a85df9b5/architecture/repo-guidelines.md
printf '%s\n' '--- direct server URL callers and helpers ---'
rg -n -C 8 'getCurrentServerBaseUrl|class WebService|fun update\(|serverBaseUrl|currentServerBaseUrl|localUrl' \
packages/frontend/apps/android/App/app/src/main/java
printf '%s\n' '--- Android bridge/plugin URL and WebView operations ---'
rg -n -C 8 'loadUrl|loadData|evaluateJavascript|addJavascriptInterface|removeJavascriptInterface|WebViewClient|shouldInterceptRequest|shouldOverrideUrlLoading|urlOverride|server' \
packages/frontend/apps/android/App/app/src/main/java/app/affine/pro
printf '%s\n' '--- Android app assets and frontend configuration references ---'
rg -n -C 5 'serverUrl|server_url|serverBaseUrl|self-hosted|api/copilot|/graphql|EventSource|fetch\\(|XMLHttpRequest|http://' \
packages/frontend/apps/android/App/app/src/main/assets packages/frontend/apps/android/App/app/src/main/java 2>/dev/null || trueRepository: toeverything/AFFiNE
Length of output: 48566
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- tracked Android app files relevant to WebView loading ---'
git ls-files packages/frontend/apps/android/App | rg '(^|/)(capacitor|assets|public|www|WebExt|AuthInitializer|MainActivity|AffineWebViewClient|AffineEditorWebView|build\.gradle|AndroidManifest)'
printf '%s\n' '--- all definitions and callers of getCurrentServerBaseUrl ---'
rg -n -C 6 'getCurrentServerBaseUrl' packages/frontend --glob '!**/node_modules/**' --glob '!**/dist/**'
printf '%s\n' '--- Capacitor configuration ---'
fd -i 'capacitor*' packages/frontend --type f --exclude node_modules --exclude dist --exec sh -c 'echo "--- $1"; sed -n "1,220p" "$1"' sh {}
printf '%s\n' '--- mobile server request patterns ---'
rg -n -C 4 'getCurrentServerBaseUrl|currentServerBaseUrl|serverBaseUrl|EventSource|fetch\\(|XMLHttpRequest|/graphql' \
packages/frontend/core packages/frontend/apps packages/frontend/mobile \
--glob '!**/node_modules/**' --glob '!**/dist/**' 2>/dev/null | head -n 500Repository: toeverything/AFFiNE
Length of output: 26996
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- Android frontend native bridge URL function ---'
sed -n '210,275p' packages/frontend/apps/android/src/app.tsx
printf '%s\n' '--- Android runtime configuration and generated web bundle inputs ---'
rg -n -C 8 'CAP_SERVER_URL|server:|cleartext|webDir|baseUrl|endpoint|DefaultServerService|ServersService' \
packages/frontend/apps/android packages/frontend/core \
--glob '!**/node_modules/**' --glob '!**/dist/**' | head -n 600
printf '%s\n' '--- frontend HTTP client and endpoint construction ---'
rg -n -C 6 'fetch\\(|axios|XMLHttpRequest|graphql|baseUrl|endpoint.*url|url.*endpoint|server.*baseUrl' \
packages/frontend/core packages/frontend/apps/android/src \
--glob '!**/node_modules/**' --glob '!**/dist/**' | head -n 600Repository: toeverything/AFFiNE
Length of output: 50375
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- cloud service definitions ---'
rg -l 'class (DefaultServerService|ServersService|ServerService)|DefaultServerService|ServersService' packages/frontend/core/src/modules/cloud packages/frontend/core/src | head -n 80
printf '%s\n' '--- cloud service outlines ---'
for f in $(rg -l 'class (DefaultServerService|ServersService|ServerService)' packages/frontend/core/src/modules/cloud packages/frontend/core/src | head -n 20); do
echo "--- $f"
ast-grep outline "$f"
done
printf '%s\n' '--- endpoint request implementations ---'
rg -l 'fetch\\(|graphql-request|ApolloClient|createClient|WebSocket|EventSource' packages/frontend/core/src/modules/cloud packages/frontend/core/src | head -n 80
printf '%s\n' '--- Android build and manifest network settings ---'
sed -n '1,220p' packages/frontend/apps/android/App/app/build.gradle
sed -n '1,180p' packages/frontend/apps/android/App/app/src/main/AndroidManifest.xmlRepository: toeverything/AFFiNE
Length of output: 6547
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- server selection and base URL definitions ---'
sed -n '1,220p' packages/frontend/core/src/modules/cloud/services/server.ts
sed -n '1,240p' packages/frontend/core/src/modules/cloud/services/default-server.ts
sed -n '1,280p' packages/frontend/core/src/modules/cloud/services/servers.ts
printf '%s\n' '--- cloud request clients ---'
rg -n -C 5 'fetch\\(|ApolloClient|graphql-request|EventSource|WebSocket|baseUrl' \
packages/frontend/core/src/modules/cloud \
packages/frontend/core/src/modules \
--glob '!**/*.spec.*' | head -n 500
printf '%s\n' '--- Android frontend entry and build output configuration ---'
sed -n '1,120p' packages/frontend/apps/android/src/app.tsx
sed -n '1,100p' packages/frontend/apps/android/package.jsonRepository: toeverything/AFFiNE
Length of output: 4720
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- cloud module files and URL-bearing code ---'
git ls-files packages/frontend/core/src/modules/cloud | sort
rg -n -C 5 'baseUrl|fetch\\(|XMLHttpRequest|graphql|subscribe|WebSocket|EventSource|socket' \
packages/frontend/core/src/modules/cloud \
--glob '!**/*.spec.*'
printf '%s\n' '--- server entity and scope ---'
rg -l 'class Server|interface ServerScope|serverScope' packages/frontend/core/src/modules/cloud
for f in $(rg -l 'class Server|interface ServerScope|serverScope' packages/frontend/core/src/modules/cloud | head -n 12); do
echo "--- $f"
sed -n '1,260p' "$f"
done
printf '%s\n' '--- frontend request primitives used by Android ---'
rg -n -C 4 'fetch\\(|graphql|WebSocket|EventSource' \
packages/frontend/apps/android/src packages/frontend/core/src \
--glob '!**/*.spec.*' --glob '!**/generated/**' \
| head -n 500Repository: toeverything/AFFiNE
Length of output: 5318
🏁 Script executed:
set -euo pipefail
for f in \
packages/frontend/core/src/modules/cloud/entities/server.ts \
packages/frontend/core/src/modules/cloud/scopes/server.ts \
packages/frontend/core/src/modules/cloud/services/fetch.ts \
packages/frontend/core/src/modules/cloud/services/graphql.ts \
packages/frontend/core/src/modules/cloud/services/eventsource.ts \
packages/frontend/core/src/modules/cloud/services/realtime.ts \
packages/frontend/core/src/modules/cloud/realtime/live-query.ts; do
echo "--- $f"
sed -n '1,260p' "$f"
done
printf '%s\n' '--- direct request call sites ---'
rg -n -C 4 'FetchService|GraphQLService|EventSourceService|fetch\\(|new EventSource|WebSocket|baseUrl' \
packages/frontend/core/src/modules/cloud \
packages/frontend/apps/android/src \
--glob '!**/*.spec.*' | head -n 700Repository: toeverything/AFFiNE
Length of output: 16933
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- WebView-side HTTP and SSE call sites ---'
rg -n -C 5 'eventSource\\(|\.fetch\(|fetch\\(' \
packages/frontend/core/src packages/frontend/apps/android/src \
--glob '!**/*.spec.*' --glob '!**/generated/**' \
| head -n 900
printf '%s\n' '--- response consumers that could create executable content ---'
rg -n -C 6 'text\\(\\)|blob\\(\\)|arrayBuffer\\(\\)|json\\(\\)|innerHTML|outerHTML|dangerouslySetInnerHTML|srcDoc|<iframe|<script|URL\\.createObjectURL' \
packages/frontend/core/src packages/frontend/apps/android/src \
--glob '!**/*.spec.*' --glob '!**/generated/**' \
| head -n 900Repository: toeverything/AFFiNE
Length of output: 311
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- WebView-side HTTP and SSE call sites ---'
rg -n -C 5 -F \
-e 'eventSource(' \
-e '.fetch(' \
-e 'fetch(' \
packages/frontend/core/src packages/frontend/apps/android/src \
--glob '!**/*.spec.*' --glob '!**/generated/**' \
| head -n 900
printf '%s\n' '--- response consumers that could create executable content ---'
rg -n -C 6 -F \
-e '.text()' \
-e '.blob()' \
-e '.arrayBuffer()' \
-e '.json()' \
-e 'innerHTML' \
-e 'outerHTML' \
-e 'dangerouslySetInnerHTML' \
-e 'srcDoc' \
-e '<iframe' \
-e '<script' \
-e 'URL.createObjectURL' \
packages/frontend/core/src packages/frontend/apps/android/src \
--glob '!**/*.spec.*' --glob '!**/generated/**' \
| head -n 900Repository: toeverything/AFFiNE
Length of output: 50376
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- HTML preview implementation ---'
sed -n '1,230p' packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/html-preview.ts
printf '%s\n' '--- HTML preview content and isolation controls ---'
rg -n -C 8 'HtmlPreview|html-preview|sandbox|srcdoc|contentWindow|document\\.write|innerHTML|setAttribute\\(' \
packages/frontend/core/src/blocksuite/view-extensions/code-block-preview \
packages/frontend/core/src \
--glob '!**/*.spec.*' | head -n 700Repository: toeverything/AFFiNE
Length of output: 4564
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- iframe linking implementation ---'
sed -n '1,260p' packages/frontend/core/src/blocksuite/view-extensions/code-block-preview/iframe-container.ts
printf '%s\n' '--- iframe-container references ---'
rg -n -C 8 -F \
-e 'linkIframe(' \
-e 'sandbox' \
-e 'srcdoc' \
-e 'contentWindow' \
-e 'document.write' \
packages/frontend/core/src/blocksuite/view-extensions/code-block-previewRepository: toeverything/AFFiNE
Length of output: 4725
Security Misconfiguration (CWE-16)
Reachability: External · Exploitability: Moderate
Keep mixed content blocked for the WebView.
MIXED_CONTENT_ALWAYS_ALLOW also permits HTTP subresources, including scripts referenced by HTML rendered in the sandboxed preview iframe. If HTTP self-hosted servers remain supported, use native clients for API and SSE traffic, or enforce an origin and resource-type allowlist that prevents HTTP responses from becoming executable content.

PR #15118 tightened release builds to block cleartext traffic and mixed content, which broke connecting to self-hosted instances served over plain HTTP (#15438): the WebView fetch to /graphql fails and the app shows a generic "Network error", while browsers load the same server fine.
Restore the behavior established by #13279 and #13435:
Fixes #15438
Summary by CodeRabbit