Skip to content

fix: secure shop payments and locked links - #1158

Open
ben-kaufman wants to merge 12 commits into
masterfrom
cursor/fix-shop-quickpay-auth-86ae
Open

fix: secure shop payments and locked links#1158
ben-kaufman wants to merge 12 commits into
masterfrom
cursor/fix-shop-quickpay-auth-86ae

Conversation

@ben-kaufman

@ben-kaufman ben-kaufman commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Description

This PR:

  1. Restricts native shop payment messages to the exact https://embed.bitrefill.com origin through an origin-scoped WebMessage listener.
  2. Disables native shop payments when origin-aware WebView messaging is unavailable, rejects non-string messages, and keeps broader Bitrefill navigation separate from the privileged payment origin.
  3. Defers payment links and scans until authentication completes while preserving the intentional QuickPay opt-in behavior.
  4. Keeps only the latest deferred external scan while incoming Paykit requests remain repository-backed until authentication.
  5. Serializes scan execution and preserves each scan's contact context so concurrent requests cannot overlap or be attributed to the wrong contact.

Addresses VulnHunter 01b finding 02 and the unauthenticated deeplink handling from finding 03. The explicit QuickPay bypass of per-payment PIN confirmation remains accepted product behavior.

Companion iOS PR: synonymdev/bitkit-ios#668

Preview

N/A — security boundary and payment-flow state handling only.

QA Notes

Manual Tests

  • 1. Shop → Gift Cards → Bitrefill checkout → submit a payment: Send Confirm or QuickPay opens with the expected invoice.
  • 2a. regression: Shop → navigate between legitimate Bitrefill pages: navigation remains available.
    • 2b. Shop → attempt off-Bitrefill main-frame navigation: navigation is blocked.
  • 3. Settings → PIN on + PIN for payments on + QuickPay on → authenticate, then scan a small LN invoice: QuickPay opens without another PIN prompt, as designed.
  • 4. Settings → PIN on + PIN for payments off + QuickPay on → scan a small LN invoice: QuickPay still opens.
  • 5. Lock app → open a QuickPay-eligible lightning: link → authenticate: no payment flow opens before authentication, then QuickPay opens once.
  • 6. Lock app → open two different payment links → authenticate: only the latest link opens and the earlier link is not replayed.
  • 7. Lock app with an incoming Paykit request → open a payment link → authenticate → dismiss the external send flow: the external payment opens first, then the Paykit request opens once with its contact context.
  • 8. Lock app with a payment link deferred → open Scanner → authenticate → scan another invoice: the scanner result opens first and the deferred link opens after it is dismissed.

Automated Checks

  • ShopOriginTest.kt: covers the exact payment origin, trusted port/user-info boundary, broader navigation allow-list, and bridge-page guard.
  • ShopWebViewInterfaceTest.kt: covers feature detection, fail-closed fallback, exact listener origin, sibling-origin rejection, non-string messages, and payment parsing.
  • ShopWebViewClientTest.kt: covers supported/unsupported bridge injection, sibling-page non-injection, and main-frame navigation boundaries.
  • AppViewModelSendFlowTest.kt: covers authentication/QuickPay deferral, latest-wins replacement, context isolation, cancellation serialization, scanner ordering, sheet dismissal, and Paykit handoff races.
  • Test coverage removed: deleted FIFO/capacity/stale-queue cases because deferred external scans now use one latest-wins slot and automatic Paykit requests remain repository-backed.
  • Local verification: compileDevDebugKotlin, testDevDebugUnitTest, detekt --rerun-tasks, and git diff --check.

cursoragent and others added 2 commits August 13, 2026 18:58
Co-authored-by: benk10 <ben.kaufman10@gmail.com>
Co-authored-by: benk10 <ben.kaufman10@gmail.com>
Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Fixed
Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Fixed
cursoragent and others added 2 commits August 13, 2026 19:00
Co-authored-by: benk10 <ben.kaufman10@gmail.com>
Co-authored-by: benk10 <ben.kaufman10@gmail.com>
@ben-kaufman
ben-kaufman marked this pull request as ready for review August 13, 2026 19:00
@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR restricts the shop WebView to trusted Bitrefill HTTPS origins, makes QuickPay respect payment PIN settings, and defers payment inputs until authentication. It also adds focused tests and a security changelog entry.

  • Adds shared Bitrefill host/origin validation for navigation and WebView messages.
  • Prevents QuickPay from bypassing payment PIN confirmation.
  • Defers locked scans and deeplinks, although multiple pending inputs currently overwrite one another.
  • Adds unit coverage for origin filtering, navigation blocking, PIN-aware QuickPay, and single pending-input replay.

Confidence Score: 4/5

The PR should not merge until locked payment inputs can no longer silently overwrite earlier pending requests.

The new authentication gate retains only one pending scan or deeplink, so consecutive Android payment intents received before unlock cause earlier requests to be discarded.

Files Needing Attention: app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt

Important Files Changed

Filename Overview
app/src/main/java/to/bitkit/ui/screens/shop/shopWebView/ShopOrigin.kt Introduces HTTPS Bitrefill host validation and an origin-filtering JavaScript message bridge.
app/src/main/java/to/bitkit/ui/screens/shop/shopWebView/ShopWebViewClient.kt Injects the restricted bridge and blocks untrusted main-frame navigation.
app/src/main/java/to/bitkit/ui/screens/shop/shopWebView/ShopWebViewInterface.kt Moves bridge handling onto the main thread and validates the current page origin before forwarding payment intents.
app/src/main/java/to/bitkit/ui/screens/shop/shopWebView/ShopWebViewScreen.kt Supplies the current WebView URL to the interface for origin validation.
app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Adds PIN-aware QuickPay suppression and locked-input deferral, but the single pending slot silently drops all but the latest input.
app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt Covers replay of one locked input and PIN-aware QuickPay but does not exercise multiple inputs received before unlock.

Sequence Diagram

sequenceDiagram
    participant OS as Android intents
    participant VM as AppViewModel
    participant Lock as Authentication UI
    participant Send as Send flow
    OS->>VM: Payment input A
    VM->>VM: "pendingLockedScan = A"
    OS->>VM: Payment input B
    VM->>VM: "pendingLockedScan = B"
    Note over VM: Input A is overwritten
    Lock->>VM: setIsAuthenticated(true)
    VM->>VM: flushPendingLockedScan()
    VM->>Send: Launch input B only
Loading

Reviews (1): Last reviewed commit: "fix: handle null shop origin scheme" | Re-trigger Greptile

Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Outdated
cursoragent and others added 2 commits August 13, 2026 19:05
Co-authored-by: benk10 <ben.kaufman10@gmail.com>
Co-authored-by: benk10 <ben.kaufman10@gmail.com>
Comment thread app/src/main/java/to/bitkit/ui/screens/shop/shopWebView/ShopWebViewInterface.kt Outdated

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review + manual verification on a regtest dev build (Pixel_9 emulator).

Verified working on device: QuickPay is skipped when PIN-for-payments is on (Skipping QuickPay because PIN is required for payments → Confirm sheet → PIN prompt on swipe); QuickPay still runs with PIN-for-payments off; payment links fired while locked are queued and opened exactly once after auth; a second link replaces the first (Replacing deferred scan) and the earlier one is never replayed; off-Bitrefill main-frame navigation is blocked (Blocked shop navigation to untrusted origin 'https://policies.google.com/privacy') while in-shop navigation is unaffected.

Not covered on device: Bitrefill checkout (dev flavor talks to the real embed.bitrefill.com, so a checkout emits a mainnet invoice a regtest wallet can't act on), the Paykit handoff race, and the scanner-ordering case.

5 inline findings below.

Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Outdated
Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
Comment thread app/src/main/java/to/bitkit/ui/screens/shop/shopWebView/ShopOrigin.kt Outdated
@pwltr

pwltr commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Note that QuickPay skipping PIN checks is assumed risk per design.

image

@ben-kaufman ben-kaufman changed the title fix: gate shop origin and pin pay fix: secure shop payments and locked links Aug 14, 2026
@ben-kaufman

Copy link
Copy Markdown
Contributor Author

@pwltr Thanks for clarifying. Agreed—QuickPay is the explicit opt-in exception to per-payment PIN confirmation. I removed the new PIN-for-payments gate in 32b59a5, kept the app-authentication deferral for locked links, and updated the regression test, changelog, title, and PR description to state the accepted behavior accurately. The focused test, full unit suite, compile, and detekt checks are green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants