118 lines
5.9 KiB
Markdown
118 lines
5.9 KiB
Markdown
# 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. |
|