Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Unredacted Server Error Messages Rendered Verbatim to End Users

When a mobile application receives an error response from a backend service (REST or GraphQL), it must decide how to surface that failure to the user. Rendering the server-authored error text verbatim to the end user is an **information disclosure** weakness: the server message is not designed as user-facing copy and may embed internal implementation details that aid reconnaissance.

## Description

The application exposes the raw server error text to the user on one or more error code paths. Typically the data flow is:

1. A network layer throws an exception whose `message` getter returns the server-authored text unchanged. For REST this is the parsed `ApiError.message` field (and accompanying `errors[]` array of internal exception class names); for GraphQL it is `response.errors.first().message` per the GraphQL specification.
2. A view model constructs an error UI state from `error.message` (or the exception object itself) instead of mapping the server error to a localized client string.
3. A state holder stores that raw string as the message to render, with no allow-list mapping server error codes/names to localized strings and no redaction of unmapped text.
4. The UI renders the string verbatim in a visible control (for example a `Text` composable, a `Snackbar`, or a `Toast`) and frequently also announces it to assistive technologies via a `contentDescription`.

There is no sanitization hop between the thrown exception and the rendered text, so any future server change that emits a more descriptive message is auto-leaked.

## Impact

The disclosure is shown only to the application's own (authenticated) user, so the direct impact is limited. The realistic exposure is **reconnaissance-aiding information disclosure** rather than direct compromise. Per the GraphQL specification, `Error.message` is server-authored text and may include:

* Internal field names and enum values.
* Validation or database-derived messages (for example constraint names, column names, or SQL error fragments).
* Internal exception class names (e.g. `LegacyAuthMismatchException`, `PasswordResetRequiredException`) that reveal account-state or authentication-flow internals.

This information helps an attacker understand the backend schema, account states, and validation logic, which can support secondary attacks such as user enumeration or targeted parameter manipulation.

> Note: This weakness concerns **verbatim server-authored error text**, not JVM or server stack traces. A `message` getter that returns `response.errors.first().message` exposes server text, not `Throwable.stackTrace`. The "stack traces rendered to the user" phrasing is therefore not supported by this class of issue; only verbatim message rendering applies.

## Risk Rating

**Low.** The information is disclosed to the application's own user and is reconnaissance-aiding rather than directly destructive. Higher-impact exploitation (such as account enumeration) is tracked separately where applicable.

## References

* [CWE-209: Information Exposure Through an Error Message](https://cwe.mitre.org/data/definitions/209.html)
* [CWE-215: Information Exposure Through Debug Information](https://cwe.mitre.org/data/definitions/215.html)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"risk_rating": "low",
"short_description": "The application renders server-authored error messages verbatim to the user, which may disclose internal field names, enum values, exception class names, or validation/database-derived details that aid reconnaissance.",
"references": {
"CWE-209: Information Exposure Through an Error Message": "https://cwe.mitre.org/data/definitions/209.html",
"CWE-215: Information Exposure Through Debug Information": "https://cwe.mitre.org/data/definitions/215.html"
},
"title": "Unredacted Server Error Messages Rendered Verbatim to End Users",
"cvss_v3_vector": "CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:N",
"privacy_issue": false,
"security_issue": true,
"categories": {
"OWASP_MASVS_L1": [
"MSTG_CODE_6"
],
"OWASP_MASVS_L2": [
"MSTG_CODE_6"
],
"OWASP_MASVS_v2_1": [
"MASVS_RESILIENCE_4"
],
"PCI_STANDARDS": [
"REQ_6_2"
],
"OWASP_MOBILE_TOP_10": [
"M4_2024"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Recommendation

Never render server-authored error text verbatim to end users. Map known server error codes/names to localized client strings and fall back to a generic localized message for any unmapped server text.

### Immediate Mitigation

Remove the verbatim-rendering code paths in the view models. Replace `customMessageString = error.message`, `UiState.Error(error = error)`, and `UiState.Error(e)` (where the source is an `ApiException`/`RestApiException`) with localized resource strings keyed off the server error type:

```kotlin
// LoginUsernameViewModel.kt — replace the verbatim branches
error.containsError("PasswordResetRequiredException") ->
UiState.Error(R.string.error_password_reset_required, showTime = null)
else -> UiState.Error(R.string.error_signing_in, showTime = null)
```

```kotlin
// StateOfResSelectionViewModel.kt — replace the verbatim branch
.onApiError { uiState.value = UiState.Error(R.string.error_saving) }
```

As a defense-in-depth guard, make the state holder's message resolver ignore any raw `customMessageString` originating from `ApiException`/`RestApiException` sources, so a future verbatim path cannot reach the UI.

### Permanent Fix

* Introduce an allow-list that maps known server error codes/names (for example `LegacyAuthMismatchException`, `PasswordResetRequiredException`, GraphQL `extensions.code` values) to localized client strings. Never construct an error UI state from raw `error.message`.
* Coordinate with the backend to return stable, machine-readable error codes rather than free-text messages.
* Add redaction so that any unmapped server message falls back to a generic localized string rather than the raw text.
* Add regression tests asserting that a server payload carrying an internal message produces a localized UI state (e.g. `UiState.Error(R.string.*)`), not the raw message. Add a dedicated test modeling a GraphQL `Error.message` payload and asserting the rendered UI string is the localized fallback, not the server text.

### Verification

1. Grep the repository for `customMessageString = error.message`, `UiState.Error(error = error)`, and `UiState.Error(e)` with an `ApiException`/`RestApiException` source; confirm zero remaining matches after the fix.
2. Confirm that assistive technology (e.g. TalkBack) announces the localized string rather than the raw server message.
Loading