5.9 KiB
5.9 KiB
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. |