diff --git a/MOBILE_CLIENT/COMMON/_LOW/UNREDACTED_SERVER_ERROR_MESSAGE/description.md b/MOBILE_CLIENT/COMMON/_LOW/UNREDACTED_SERVER_ERROR_MESSAGE/description.md new file mode 100644 index 00000000..a1f4de80 --- /dev/null +++ b/MOBILE_CLIENT/COMMON/_LOW/UNREDACTED_SERVER_ERROR_MESSAGE/description.md @@ -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) diff --git a/MOBILE_CLIENT/COMMON/_LOW/UNREDACTED_SERVER_ERROR_MESSAGE/meta.json b/MOBILE_CLIENT/COMMON/_LOW/UNREDACTED_SERVER_ERROR_MESSAGE/meta.json new file mode 100644 index 00000000..f471e7fe --- /dev/null +++ b/MOBILE_CLIENT/COMMON/_LOW/UNREDACTED_SERVER_ERROR_MESSAGE/meta.json @@ -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" + ] + } +} diff --git a/MOBILE_CLIENT/COMMON/_LOW/UNREDACTED_SERVER_ERROR_MESSAGE/recommendation.md b/MOBILE_CLIENT/COMMON/_LOW/UNREDACTED_SERVER_ERROR_MESSAGE/recommendation.md new file mode 100644 index 00000000..1615fdae --- /dev/null +++ b/MOBILE_CLIENT/COMMON/_LOW/UNREDACTED_SERVER_ERROR_MESSAGE/recommendation.md @@ -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.