Skip to content

KM-16629: storekit2 - #355

Merged
kp-mario-nachbaur merged 20 commits into
masterfrom
KM-16629-storekit2
Jul 24, 2026
Merged

KM-16629: storekit2#355
kp-mario-nachbaur merged 20 commits into
masterfrom
KM-16629-storekit2

Conversation

@kp-mario-nachbaur

Copy link
Copy Markdown
Contributor
  • Migrate to StoreKit 2
  • Type-safe receipt type JWS (just a wrapper over string).
    • New PIABase package for base types.

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@kpkb-1f8e9813897fe9831983e89f7143

kpkb-1f8e9813897fe9831983e89f7143 Bot commented Jul 7, 2026

Copy link
Copy Markdown

KB review — 7 findings on this PR

🟠 1 HIGH · 🟡 1 MEDIUM · ⚪ 4 LOW · ℹ️ 1 INFO

Severity Status Kind Finding
🟠 HIGH added BUG_HUNT OpenVPN and WireGuard connect() calls startTunnel() immediately after stopVPNTunnel() without waiting for disconnect
🟡 MEDIUM changed SECURITY_REVIEW StoreKit 2 unverified purchase forwarded to backend without client-side rejection
⚪ LOW added SECURITY_REVIEW StoreKit 2 JWS receipt exported in plaintext via debug menu share sheet
⚪ LOW added SECURITY_REVIEW Debug menu exports full StoreKit 2 JWS credential accessible in TestFlight builds
⚪ LOW added SECURITY_REVIEW Debug menu exports raw StoreKit 2 JWS receipt via iOS share sheet
⚪ LOW added SECURITY_REVIEW StoreKit 2 JWS receipt exposed in TestFlight debug export
ℹ️ INFO changed UNKNOWN

kb · head d7fbf4f · full PR view

@claude

This comment was marked as outdated.

Comment thread LocalPackages/PIADebugMenu/Sources/PIADebugMenu/DebugMenuView.swift
@claude

This comment was marked as outdated.

Comment thread LocalPackages/PIADebugMenu/Sources/PIADebugMenu/DebugMenuView.swift
Comment thread LocalPackages/PIADebugMenu/Sources/PIADebugMenu/DebugMenuView.swift
@claude

This comment was marked as outdated.

@kp-diego-trevisan kp-diego-trevisan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code-wise it looks fine to me.
Added a couple suggestions, maybe it's worth checking the latest Claude concerns, but I see the most relevant ones were already tackled 👍

Comment thread LocalPackages/PIABase/.gitignore Outdated
Comment thread LocalPackages/PIABase/Package.swift
@claude

This comment was marked as outdated.

@claude

This comment was marked as outdated.

@claude

This comment was marked as outdated.

@claude

This comment was marked as outdated.

Comment thread LocalPackages/PIADebugMenu/Sources/PIADebugMenu/DebugMenuView.swift
Comment thread LocalPackages/PIADebugMenu/Sources/PIADebugMenu/DebugMenuView.swift
@claude

This comment was marked as outdated.

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown

Claude finished @kp-mario-nachbaur's task in 1m 16s —— View job


PR Re-review: StoreKit 2 Migration — branch sync (a9ec5bbbd7fbf4ff)

  • Gather diff since last review
  • Confirm status of prior open items
  • Post feedback

This synchronize brought in one new commit — a merge from master:

  • d7fbf4ff — "Merge branch 'master' into KM-16629-storekit2" (pulling in ae9450aa KM-17456: Mac Catalyst reconnect keychain race fix, plus other already-merged master work)

The only file changes introduced by the merge relative to my last review are from master, not from the StoreKit 2 work itself:

File Source
PIAKPI/Internal/KPIEventUtils.swift master (KM-17456)
PIALibrary/Util/Keychain.swift master (KM-17456)

No new StoreKit-related code has been added since my last pass, so there is nothing new to review here — the merge is clean and the diff is limited to unrelated master fixes. ✅


Still open (carry over — unchanged)

All four items from my previous review remain in the same state:

  • 📝 Comment fix half doneAppStoreProvider.swift:77-78 still reads "With our current architecture is difficult to handle them." — missing subject, should be "…architecture it is difficult…". Purely cosmetic. Fix this →

  • ⚠️ Dead pending-recovery gating — unchanged. DashboardViewController.swift:436 and RootCoordinator.swift:113 still hardcode preset.shouldRecoverPendingSignup = false (with the commented-out //!TransientState.didRetryPendingSignup), while the if !TransientState.didRetryPendingSignup { … } blocks below still write the flag. Because the value is always false, tryRecoverSignupProcess() stays unreachable. Either wire it to !TransientState.didRetryPendingSignup or delete the dead recovery code. Fix this →

  • 🔒 JWS debug export (LOW — KB SEC-L-4 and related) — unchanged. DebugMenuView.swift:188 still exports raw transactionJWS.value via a per-section ShareLink, and DebugMenuView+Values.swift still appends the full JWS to the "Export All" bundle. Neither path is gated. Since the debug menu is shake-reachable in TestFlight, consider gating both behind #if DEVELOPMENT || STAGING. Acceptable if intentional.

  • 🔐 Unverified transaction → backend (MEDIUM — KB SEC-M-3) — the author confirmed the backend validates the full JWS cert chain to Apple's Root CA and verifies the signature, so forwarding .unverified is safe by design. Resolved by discussion. AppStoreProvider.swift:172-173 still has no explanatory code comment — worth a one-liner documenting why .unverified is intentionally forwarded so the next reader doesn't re-flag it.

  • 🧪 Test coverage — unchanged. Still no new unit tests for the new provider branches (currentEntitlementJWS newest/verified selection, restore-with-cached-entitlement, purchasePending/userCancelled mapping, badReceipt fallback, retry-with-sync), and SignupProviderTests remains removed.


Summary

This sync is a clean merge from master with no new StoreKit 2 changes — nothing new to flag. The carry-over items are all still minor/optional except the dead pending-recovery gating, which continues to read as unfinished WIP and is the one item worth resolving before merge. PR remains in good shape and is already approved by @kp-diego-trevisan.
· branch KM-16629-storekit2

func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
guard (request == productsRequest) else {
return
log.debug("Purchasing product with identifier: \(product.id)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM · SECURITY_REVIEW — StoreKit 2 unverified purchase forwarded to backend without client-side rejection

In AppStoreProvider.purchase(), the VerificationResult.unverified case — which StoreKit 2 returns when the cryptographic signature on the transaction JWS cannot be verified on-device — is treated identically to the VerificationResult.verified case: the function returns .success(AppStoreTransaction(...)) and the caller proceeds to authenticate with the PIA backend using the unverified JWS: ```swift case .unverified(let transaction, let error): log.debug("(#function) success unverifi…

View finding in KB

kb · SEC-M-3


var receiptBase64: String? {
Client.store.paymentReceipt?.base64EncodedString()
var transactionJWS: JWS? {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW · SECURITY_REVIEW — StoreKit 2 JWS receipt exported in plaintext via debug menu share sheet

The new DebugMenuView and its DebugMenuView+Values extension expose the device's current active subscription entitlement as a raw JWS string through two iOS share sheet entry points: 1. Dedicated Transaction section (receiptSection): renders a 300-character preview and offers a ShareLink that exports the full JWS value to any iOS share target (AirDrop, Messages, Files, email, etc.). 2. "Export All" toolbar button (buildExportContent()): appends the full transactionJWS?.value

View finding in KB

kb · F-2XB2EX6VW9J6SR88

@State private var refundTransactionId: UInt64 = 0
@State private var isRefundSheetPresented = false
@State private var availableTransactions: [StoreKit.Transaction] = []
@State private var isTransactionPickerPresented = false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW · SECURITY_REVIEW — Debug menu exports full StoreKit 2 JWS credential accessible in TestFlight builds

The PIADebugMenu package's DebugMenuView fetches the active entitlement JWS on view appear and renders it with a share-sheet export button: swift .task { entitlementJWS = await Client.store.currentEntitlementJWS() } The "Export" share link in the receiptSection writes the full JWS string to a DebugExportFile (transaction_<timestamp>.txt), and the same value is appended to the "Export All" log bundle under === Transaction (JWS) ===. The debug menu is gated by: ```swift…

View finding in KB

kb · F-A8GCEDDANS1C70XW

preview: SharePreview("Receipt")
preview: SharePreview("Transaction JWS")
) {
Label("Export", systemImage: "square.and.arrow.up")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW · SECURITY_REVIEW — Debug menu exports raw StoreKit 2 JWS receipt via iOS share sheet

DebugMenuView (introduced in this PR as part of the StoreKit 2 migration) displays the current entitlement's JWS representation and exposes it through two iOS share sheet entry points: 1. "Transaction JWS" section – a dedicated ShareLink that exports the raw transactionJWS.value string to a timestamped .txt file: ```swift ShareLink( item: DebugExportFile( content: transactionJWS.value, filename: "transaction_(Int(Date().timeIntervalSince1970)).txt…

View finding in KB

kb · F-XGFXR4MEDSCV754S


var receiptBase64: String? {
Client.store.paymentReceipt?.base64EncodedString()
var transactionJWS: JWS? {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW · SECURITY_REVIEW — StoreKit 2 JWS receipt exposed in TestFlight debug export

The debug menu's buildExportContent() now includes the full StoreKit 2 JWS value in its "Export All" share sheet output (section "=== Transaction (JWS) ==="). A dedicated per-section "Export" ShareLink in receiptSection also exports the raw JWS as a .txt file. The JWS is loaded in DebugMenuView.onAppear via Client.store.currentEntitlementJWS() — the identical value passed to loginWithReceipt(receipt:) for authentication. The debug menu is activated by shake gesture in TestFlight b…

View finding in KB

kb · SEC-L-4

@kp-diego-trevisan
kp-diego-trevisan self-requested a review July 24, 2026 13:45
@kp-mario-nachbaur
kp-mario-nachbaur merged commit f03f80e into master Jul 24, 2026
3 checks passed
@kp-mario-nachbaur
kp-mario-nachbaur deleted the KM-16629-storekit2 branch July 24, 2026 13:46
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.

2 participants