View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012122 | Taler | wallet (iOS App) | public | 2026-11-11 21:04 | 2026-11-11 21:04 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | have not tried |
| Status | new | Resolution | open | ||
| Summary | 0012122: Add payment service: text that is not an address ("GLS Bank") is answered with "Internal core error" and raw JSON -- the alert validates nothing and Add is always enabled | ||||
| Description | Settings -> Payment Services -> "+" opens an alert titled "Add payment service" with one text field ("Enter the URL"). Whatever is typed there is handed to wallet-core unchanged, and anything wallet-core cannot read as an address comes back to the user as the generic error sheet titled "Internal core error", with wallet-core's JSON as the body (screenshot attached). Two ordinary mistakes, two different codes, the same sheet: * typing a name, "GLS Bank": { "code" : 7048, "detail" : "URL contains characters that are not allowed in a URL" } * the fuzzer's own input, an e-mail address run together with a host name ("...@tripwire.taler.netBytripwire.taler.nettaler.net"): { "code" : 7035, "hint" : "expected a taler://add-exchange/ URI", ... } Nothing internal went wrong; the user mistyped. The alert's Add button carries no .disabled() at all, so by the code it is enabled for an empty field too (not tried). Steps to reproduce (iOS simulator, iPhone 17, fresh install, developer mode on, demo cash withdrawn; no fault injection): 1. Settings -> Payment Services -> "+" 2. type "GLS Bank" 3. Add Result: "Internal core error" + JSON + "Copy" + "OK". Expected: the Add button stays disabled until the text can be an address (or the error says the address is not valid, in words). Cause ----- TalerWallet1/Views/Settings/Exchange/ExchangeListView.swift: Button(addButtonStr) { addExchange(newExchange, model: model) ... and the file-level addExchange() calls model.addExchange(uri:) with viewHandles left false, so every WalletBackendError goes to the global error sheet. There is no check between the text field and the request. (BankListView.swift:28 has a second addExchange() with the same shape.) Proposed patch -------------- Below. It adds exchangeAddress(_:) -- trims, rejects whitespace, accepts taler:// URIs, a bare host or an http(s) URL without user part -- and disables Add until it returns something. NOT compiled or run on a device yet. diff --git a/TalerWallet1/Views/Settings/Exchange/ExchangeListView.swift b/TalerWallet1/Views/Settings/Exchange/ExchangeListView.swift index 2017f245..fe42ecc6 100644 --- a/TalerWallet1/Views/Settings/Exchange/ExchangeListView.swift +++ b/TalerWallet1/Views/Settings/Exchange/ExchangeListView.swift @@ -9,6 +9,24 @@ import SwiftUI import taler_swift import SymLog +/// What the user typed into the "Add payment service" alert, if it can be an +/// address at all. wallet-core answers anything else with 7048 ("URL contains +/// characters that are not allowed") or 7035 ("expected a taler://add-exchange/ +/// URI"), which reached the user as "Internal core error" with the raw JSON. +fileprivate +func exchangeAddress(_ typed: String) -> String? { + let text = typed.trimmingCharacters(in: .whitespacesAndNewlines) + if text.isEmpty || text.contains(where: { $0.isWhitespace }) { return nil } + if text.lowercased().hasPrefix("taler") && text.contains("://") { return text } + let withScheme = text.contains("://") ? text : "https://" + text + guard let components = URLComponents(string: withScheme), + let scheme = components.scheme?.lowercased(), scheme == "https" || scheme == "http", + let host = components.host, !host.isEmpty, + components.user == nil + else { return nil } + return text +} + @MainActor fileprivate func addExchange(_ exchange: String, model: WalletModel) -> Void { @@ -88,11 +106,14 @@ struct ExchangeListView: View { .accessibilityLabel(enterURL) // .textFieldStyle(.roundedBorder) Yikes: when adding style the alert will stop showing the textfield! Don't do this. Button(addButtonStr) { - addExchange(newExchange, model: model) - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - checkExchanges() + if let address = exchangeAddress(newExchange) { + addExchange(address, model: model) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + checkExchanges() + } } } + .disabled(exchangeAddress(newExchange) == nil) Button("Cancel", role: .cancel) { } } message: { Text(enterURL) @@ -104,7 +125,9 @@ struct ExchangeListView: View { title: addTitleStr, doneText: addButtonStr, text: $newExchange) { text in - addExchange(text, model: model) + if let address = exchangeAddress(text) { + addExchange(address, model: model) + } } } } How it was found ---------------- UI fuzzing campaign c2h-23, episode 14 (the walk typed a contact alias into the field). Reduced to the deterministic harness scenario scenarios/candidate/exchange-add-not-a-url.json, which is green (UI assertions plus the "addExchange with error ... not allowed in a URL" line in the app's os_log) on wallet build df63756f. Same family as 12064 (Add bank account, X-TALER-BANK). | ||||
| Tags | No tags attached. | ||||
| Attached Files | |||||