Raised by CodeRabbit while reviewing #363 (docs-only change there; behavior is pre-existing).
verifyTransactionWithServerApi in packages/kit/convex/purchases/ios.ts decodes the device JWS without signature verification, takes its transactionId, and calls getTransactionInfo. Apple's returned signed transaction IS verified against Apple's root CA, and the decoded bundleId is checked against the project config — but the response's transactionId/bundleId/environment are not compared back to the requesting payload's fields.
Evaluate whether a caller who submits a tampered payload selecting a different (valid) transactionId of the same app gains anything beyond querying that transaction's state, given entitlement binding happens via bind-user. If bind-user accepts any valid transaction, cross-user binding is the real risk. Either verify the device JWS with SignedDataVerifier and use only verified fields, or enforce response↔request equality on all three fields, and add tests for the mismatch paths.
Raised by CodeRabbit while reviewing #363 (docs-only change there; behavior is pre-existing).
verifyTransactionWithServerApiinpackages/kit/convex/purchases/ios.tsdecodes the device JWS without signature verification, takes itstransactionId, and callsgetTransactionInfo. Apple's returned signed transaction IS verified against Apple's root CA, and the decodedbundleIdis checked against the project config — but the response'stransactionId/bundleId/environmentare not compared back to the requesting payload's fields.Evaluate whether a caller who submits a tampered payload selecting a different (valid) transactionId of the same app gains anything beyond querying that transaction's state, given entitlement binding happens via
bind-user. If bind-user accepts any valid transaction, cross-user binding is the real risk. Either verify the device JWS withSignedDataVerifierand use only verified fields, or enforce response↔request equality on all three fields, and add tests for the mismatch paths.