Skip to content

[audit] KYC routing dead-ends & raw-error UX #613

@joshuakrueger-dfx

Description

@joshuakrueger-dfx

Context

Read-only source-level audit of develop (2026-06-01). develop is dfx01-green; these are not caught by the gates. flutter not run in the audit env; items are code-evidenced (file:line) with confidence. Bundled: KYC routing dead-ends + raw-error UX. Split into PRs as appropriate.

Related in-flight: #604 (countryAndTINs + phone validator — excluded here), #605 (registration confirm-and-sign page — reworks kyc_page_manager.dart/kyc_cubit.dart; see notes below). Suggested label: bug.

Findings

K1 — KycStep.dfxApproval routes to a blank white Scaffold (silent dead-end) — HIGH

  • Fix
  • lib/screens/kyc/kyc_page_manager.dart:61-76: the KycSuccess step switch has cases for email/legalDisclaimer/registration/linkWallet/nationality/twoFa/ident/financialData but no KycStep.dfxApproval → falls through to (_) => const Scaffold() (line 75). _continueKyc can emit KycSuccess(currentStep: KycStep.dfxApproval) (kyc_cubit.dart:247-258, _mapStepName maps dfxApproval).
  • Impact: user lands on a blank screen with no back/refresh/error — the "silent misroute" class the code's own 2026-05-21 incident comments warn about (the PendingReview path is guarded with KycUnsupportedStepFailure; this path is not).
  • Confidence: HIGH. Note: PR feat(kyc): replace registration form with confirm-and-sign page #605 edits this switch but does not add the dfxApproval case — still reproducible. Test-gap: no test drives KycSuccess(dfxApproval); the (_) => Scaffold() fallback hides the gap from exhaustiveness checks.

K2 — Financial-data submit failure is a dead-end and discards all answers — HIGH

  • Fix
  • lib/screens/kyc/steps/financial_data/cubits/kyc_financial_data_cubit.dart:79-92 replaces KycFinancialDataLoadedSuccess (which holds the user's responses) with KycFinancialDataFailure on submit error; subpages/kyc_financial_data_failure_page.dart has only an AppBar — no retry button, no recovery — and the answers are gone from state.
  • Impact: a transient 5xx on "Complete" after a long questionnaire → permanent dead screen + full data loss; user must restart from scratch.
  • Confidence: HIGH. Test asserts the failure state but not recoverability/answer-retention.

K3 — swissTaxResidence hardcoded true for all registrations — HIGH

K4 — Raw e.toString() exception text shown to users across KYC steps — MEDIUM

  • Fix
  • Localized wrapper but raw interpolated message at: nationality cubit:26 (setNationalityFailed), link_wallet cubit:30,32 (registrationFailed), ident cubit:69 + kyc_ident_page.dart:61 (raw-appended), financial_data cubit:36,90, kyc_cubit.dart:54,220 (KycFailurePage). Users see e.g. Exception: ... Status: 500 {body} / type 'Null' is not a subtype.... i18n gap + internal detail leak.

K5 — KycPending/KycUnsupportedStepFailure show raw enum/wire identifiers — LOW

  • Fix
  • kyc_pending_page.dart:46,54 uses Dart enum .name ("DFXAPPROVAL", "FINANCIALDATA"); kyc_page_manager.dart:54-56 injects the raw API step value. Not localized/human-readable.

Checked & clean: ident uses the Sumsub native SDK with an access token (not a loaded URL/JS; token not logged); nationality/country force-unwraps are validator-guarded; link-wallet missing-userData has a dedicated retry page.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions