Skip to content

feat: default backend none [WPB-26206] [WPB-26586] [WPB-26624]#5023

Open
Garzas wants to merge 3 commits into
developfrom
feat/default-backend-none
Open

feat: default backend none [WPB-26206] [WPB-26586] [WPB-26624]#5023
Garzas wants to merge 3 commits into
developfrom
feat/default-backend-none

Conversation

@Garzas

@Garzas Garzas commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

https://wearezeta.atlassian.net/browse/WPB-26206


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 app builds that do not ship with a preconfigured default backend. When no backend is configured, the app shows a setup screen and waits for backend configuration from supported configuration sources.

Changes

  • Add default_backend_enabled config flag.
  • Support empty default backend configuration.
  • Show setup landing page when no backend is configured.
  • Support backend setup through:
    • MDM / managed configuration
    • configuration deeplink
    • manually entered configuration link
    • QR code scanned externally by the system camera app
  • Show success state after backend configuration is applied.
  • Continue to the regular login flow after setup.
  • Add support link fallback for empty support URLs using backend websiteURL/support.
  • Support optional supportEmail from the configuration payload without DB migration.
  • Add feedback_menu_item_enabled flag to hide the generic feedback menu item.
  • Update setup/login copy and DE translations.

Validation

  • Setup flow tested with configuration deeplink.
  • Success page verified before login form.
  • Support URL fallback covered by unit test.
  • Static analysis/lint passed before final copy updates.

@Garzas Garzas self-assigned this Jul 3, 2026
@pull-request-size

Copy link
Copy Markdown

Ups 🫰🟨

This PR is too big. Please try to break it up into smaller PRs.

@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