# Payment Risk Register > Each row is one discrete payment feature or flow in nigig-pay. > Risk = Likelihood × Impact. Updated after Phase 0 fixes. --- ## 1. USSD Dispatch | Attribute | Value | |---|---| | **Feature** | `dispatch_ussd()` — types into Safaricom USSD dialog | | **Current Gate** | `#[cfg(feature = "demo")]` — disabled in production builds | | **Residual Risk** | Low (production), High (demo/development) | | **Remaining Issues** | No idempotency key. No server-side confirmation. | | **Next Action** | Phase 1: provider integration with idempotency | --- ## 2. Biometric Authentication | Attribute | Value | |---|---| | **Feature** | Fingerprint gate before USSD dispatch | | **Previous Risk** | P0 — fail-open on error dispatched without auth | | **Current Gate** | Fail-closed: error marks payment as `Failed` | | **Residual Risk** | Low | | **Remaining Issues** | `cfg!(feature = "demo")` wraps the biometric prompt itself; in non-demo builds, biometric is skipped entirely (but dispatch is also blocked). | | **Next Action** | Phase 1: biometric in all builds, dispatch gated by provider auth | --- ## 3. PIN Handling | Attribute | Value | |---|---| | **Feature** | M-Pesa PIN stored in SharedPreferences for AccessibilityService | | **Previous Risk** | P0 — PIN persisted indefinitely across sessions | | **Current Gate** | Phase 0 removes the Rust `KEY_PIN` write and the Android AccessibilityService now removes any stale key then fails closed at the PIN step. No normal or demo flow can hand a PIN to SharedPreferences. | | **Residual Risk** | No payment PIN persistence path remains. The legacy AccessibilityService must still be removed or kept disabled until an authorised provider integration exists. | | **Next Action** | Phase 5: replace the legacy USSD bridge with an authorised provider integration; do not restore PIN handoff. | --- ## 4. Auto-Retry | Attribute | Value | |---|---| | **Feature** | 5 retries with exponential backoff on retryable errors | | **Previous Risk** | High — no idempotency key, can duplicate payments | | **Current Gate** | Automatic replay was removed. A dispatch error is surfaced and requires explicit reconciliation/new user action. | | **Residual Risk** | No automatic retry path remains. Provider-side idempotency is still required before any future production gateway is enabled. | | **Next Action** | Phase 5: provider idempotency + reconciliation before enabling an authorised gateway. | --- ## 5. Bulk Payments | Attribute | Value | |---|---| | **Feature** | Sequential batch USSD from CSV | | **Previous Risk** | Critical — no provider, no reconciliation, no audit | | **Current Gate** | Legacy bulk dispatch is disabled because the PIN handoff is disabled and no provider gateway exists. | | **Residual Risk** | No functioning batch payment path remains. A future provider batch feature requires idempotency, reconciliation, limits, and audit export. | | **Remaining Issues** | No authorised provider rail. No post-batch audit export. | | **Next Action** | Phase 5: only after provider integration + reconciliation model. | --- ## 6. SMS Observation | Attribute | Value | |---|---| | **Feature** | Parse M-Pesa SMS from inbox to detect transaction outcomes | | **Previous Risk** | High — raw SMS persisted to disk, no sender validation | | **Current Gate** | `raw_message` no longer written to PSV. Renamed `Verified` → `ObservedEvidence`. | | **Residual Risk** | Medium — SMS remains untrusted evidence and sender labels can be spoofed. | | **Remaining Issues** | Sender provenance is not provider authentication. Evidence must remain non-settlement data. | | **Next Action** | Phase 5: provider confirmation; Phase 7: consent-safe adversarial SMS corpus. | --- ## 7. Transaction Storage (PSV) | Attribute | Value | |---|---| | **Feature** | Flat pipe-delimited file for transaction records | | **Residual Risk** | Medium — legacy tracker still uses `f64` money and remains a compatibility path. | | **Remaining Issues** | Domain/storage use exact money and integrity checks; tracker/UI consumers still need the Phase 6 money migration. | | **Next Action** | Phase 6: migrate tracker UI/parser consumers to exact minor-unit money. | --- ## 8. R-SEC-001: Cloudflare API token committed to the repository | Attribute | Value | |---|---| | **Discovered** | 2026-07-27, during Phase 1 completion | | **Severity** | High | | **Description** | A live Cloudflare API token (`cfut_Jviss2Pz…`) was committed in plaintext in `README.md`. | | **Exposure** | Present since the `Initial commit` (`cc05abd`) and pushed to a public remote. It is readable in git history by anyone who has ever cloned or can clone the repository. | | **Immediate action taken** | Removed from `README.md` in the working tree; the example now reads the token from `$CLOUDFLARE_API_TOKEN`. | | **Required action (owner)** | **Rotate the token in the Cloudflare dashboard.** Redaction from the tip commit does **not** revoke it and does **not** remove it from history. Treat it as compromised. | | **Optional follow-up** | Purge from history with `git filter-repo`, accepting that this rewrites every published commit hash and requires all clones to re-clone. Rotation is mandatory; purging is not a substitute for it. | | **Preventive control** | Add secret scanning to CI. Never place a credential in a tracked file. | --- ## Summary: Post-Phase 0 Status | Severity | Count | Notes | |---|---|---| | **Critical (production)** | 0 | Tracker-only production path has no working PIN/USSD dispatch route. | | **Critical (legacy demo)** | 0 | PIN handoff and automatic replay are disabled; legacy automation fails closed at the PIN step. | | **High** | 3 | No provider confirmation, legacy tracker `f64` money, Android key provisioning not implemented. | | **High (credential)** | 1 | R-SEC-001 Cloudflare token in git history — rotation required. | | **Medium** | 2 | SMS sender/provenance is untrusted; legacy tracker remains a compatibility path. | | **Low** | 1 | Hard-coded fee table. |