Skip to content

feat: gov default backend none 4.11 [WPB-26206] [WPB-26586] [WPB-26624]#5022

Open
Garzas wants to merge 4 commits into
release/cycle-4.11from
feat/gov-default-backend-none-4.11
Open

feat: gov default backend none 4.11 [WPB-26206] [WPB-26586] [WPB-26624]#5022
Garzas wants to merge 4 commits into
release/cycle-4.11from
feat/gov-default-backend-none-4.11

Conversation

@Garzas

@Garzas Garzas commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
StoryWPB-26206 [Android] Landing screen for Gov edition


PR Submission Checklist for internal contributors

  • The PR Title

    • conforms to the style of semantic commits messages¹ supported in Wire's Github Workflow²
    • contains a reference JIRA issue number like SQPIT-764
    • answers the question: If merged, this PR will: ... ³
  • The PR Description

    • is free of optional paragraphs and you have filled the relevant parts to the best of your ability

What's new in this PR?

Adds support for builds where no backend is configured by default:

  • add default_backend_enabled config flag
  • show backend setup landing page when no backend is configured
  • allow entering a configuration link manually
  • add QR camera shortcut for scanning configuration QR codes externally
  • show success page after backend configuration is applied
  • fallback empty support links to backend websiteURL/support
  • support optional supportEmail from deeplink.json without DB migration
  • add feedback_menu_item_enabled flag to hide the Give Feedback menu item

Emulator flow: deeplink -> backend dialog -> success page -> login screen

@Garzas Garzas self-assigned this Jul 3, 2026
@Suppress("TooGenericExceptionCaught")
private suspend fun fetchSupportEmail(configUrl: String): String? = withContext(Dispatchers.IO) {
try {
val connection = URL(configUrl).openConnection() as HttpURLConnection

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Semgrep identified a blocking 🔴 issue in your code:

Unvalidated configUrl passed directly to openConnection() allows Server-Side Request Forgery attacks targeting internal services.

More details about this

The configUrl parameter is passed directly to URL(configUrl).openConnection() without any validation of its destination. An attacker who can control the configUrl value can trick the application into making HTTP requests to arbitrary internal services or external targets.

Exploit scenario:

  1. An attacker provides a malicious configUrl value like http://localhost:8080/admin or http://169.254.169.254/latest/meta-data (AWS metadata endpoint)
  2. The fetchSupportEmail() function receives this URL and immediately constructs a URL object with it
  3. The openConnection() call establishes an HTTP connection to wherever the attacker specified
  4. The application's server—which typically has network access to internal services—executes the request
  5. The attacker receives the response data (or discovers internal service availability), compromising internal infrastructure or exposing sensitive metadata

This works because there's no whitelist validation, URL scheme checking, or private IP range blocking before the connection is made.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
val connection = URL(configUrl).openConnection() as HttpURLConnection
@Suppress("TooGenericExceptionCaught")
private suspend fun fetchSupportEmail(configUrl: String): String? = withContext(Dispatchers.IO) {
try {
val uri = try {
URI(configUrl)
} catch (exception: Exception) {
appLogger.w("Invalid backend support config URL", exception)
return@withContext null
}
val allowedHosts = setOf(
"api.example.com",
"config.example.com"
)
val scheme = uri.scheme?.lowercase()
val host = uri.host?.lowercase()
if (scheme != "https" ||
host.isNullOrBlank() ||
host !in allowedHosts ||
uri.userInfo != null ||
(uri.port != -1 && uri.port != 443)
) {
appLogger.w("Rejected untrusted backend support config URL: $configUrl")
return@withContext null
}
val connection = uri.toURL().openConnection() as HttpURLConnection
connection.instanceFollowRedirects = false
connection.connectTimeout = CONNECT_TIMEOUT_MILLIS
connection.readTimeout = READ_TIMEOUT_MILLIS
try {
connection.inputStream.bufferedReader().use { reader ->
json.decodeFromString(SupportConfig.serializer(), reader.readText()).supportEmail
?.trim()
?.takeIf { it.isNotBlank() }
}
} finally {
connection.disconnect()
}
} catch (exception: Exception) {
appLogger.w("Failed to read backend support email from config", exception)
null
}
}
View step-by-step instructions
  1. Validate configUrl before creating the URL so the app only connects to trusted destinations.
    Add a small validator that parses the value with URI(configUrl) and rejects anything that is not an expected https URL.

  2. Restrict the host to a fixed allowlist of backend domains that your app is supposed to use.
    For example, only allow hosts such as api.example.com or config.example.com, and reject IP addresses, localhost, and private network names.

  3. Normalize and compare the parsed fields instead of doing string checks on the full URL.
    Check scheme, host, and optionally port, for example: val uri = URI(configUrl); require(uri.scheme == "https"); require(uri.host in allowedHosts).

  4. Reject redirects or handle them with the same allowlist validation.
    Set connection.instanceFollowRedirects = false, or if redirects are required, read the Location header and validate the redirect target before following it.

  5. Create the connection only after validation succeeds.
    Move URL(configUrl).openConnection() behind the validation step so untrusted input never reaches the network call.

  6. Fail safely when the URL is invalid.
    Return null or throw a controlled exception when validation fails, for example: if (!isTrustedConfigUrl(configUrl)) return@withContext null.

  7. Prefer passing a trusted config endpoint from application configuration instead of accepting an arbitrary string.
    Alternatively, if configUrl should always point to one backend, replace the parameter with a constant or a value loaded from trusted app config such as URL(BuildConfig.SUPPORT_CONFIG_URL).

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by URLCONNECTION_SSRF_FD-1.

You can view more details about this finding in the Semgrep AppSec Platform.

@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant