Skip to main content

Security and compliance

The iOS Secure Fields SDK is designed so that raw card data never passes through your application code. This page describes what Purse guarantees, what your application must do, and what is prohibited.

QSA review

Share this page with your qualified security assessor (QSA). Formal PCI DSS scope determination requires an independent assessment.

What Purse guarantees

Card data never reaches your application code

Card field classes (SecurePANField, SecureCVVField, SecureExpDateField, SecureHolderNameField) are internal to the SDK module. Their text and attributedText getters are overridden to return nil:

override var text: String? {
get { nil } // external reads always return nil
set { super.text = newValue }
}

Any attempt to cast a card field to UITextField and read .text returns nil. Display is unaffected — UIKit renders from internal backing storage, bypassing the getter. The only outputs your application receives are:

OutputWhat it contains
Field state queriesisFieldValid, isFieldFocused, hasFieldContent (all Bool), panDigitCount (Int)
secureFieldsBrandsDetected(_:)Detected brand(s) — e.g. [.visa]
TokenizationResultvaultFormToken, bin (first 8 digits), lastFourDigits, detectedBrands

SDK-level hardening

ProtectionDetail
Field value getter overridetext and attributedText return nil — no cast can extract card data
Accessibility blockedaccessibilityValue returns nil — VoiceOver and third-party accessibility services cannot read field content
Privacy overlayUIBlurEffect placed over all card fields on willResignActiveNotification — card data is not visible in app-switcher thumbnails
Screen recording detectionUIScreen.capturedDidChangeNotification triggers the privacy overlay automatically when screen recording starts
Screenshot notificationUIApplication.userDidTakeScreenshotNotification fires secureFieldsScreenshotDetected() so your app can clear fields and warn the user
Keyboard learning reducedautocorrectionType = .no on all fields, plus spellCheckingType = .no on the PAN and cardholder-name fields — typed card data is not retained in the keyboard dictionary
Filled values remain unreadableThe PAN field uses textContentType = .creditCardNumber so iOS can offer to fill a saved card; any filled value is still never readable by your app, because the text/attributedText getters return nil (see above)
CVV maskedisSecureTextEntry = true on the CVV field — input is masked and excluded from screenshots
Memory cleared on submitclearSensitiveData() zeroes internal field content immediately after the tokenization request is built
Memory cleared on clearFields()All field buffers are zeroed; pending BIN lookups are cancelled
Ephemeral URLSessionNo URL cache, no cookie storage, reloadIgnoringLocalCacheData policy — no card data persists in the HTTP layer
HTTPS-only endpointsVault URLs are resolved internally from VaultEnvironment and are always https:// — the host app cannot point the SDK at a plaintext endpoint
Card data not loggedDebug prints are guarded by #if DEBUG — no output in production builds

Merchant obligations

Enable certificate pinning

App Transport Security (ATS) enforces HTTPS and TLS 1.2+ by default, but a rogue root CA installed via an MDM profile or malware can still perform a MITM attack. Enable SPKI pinning for production deployments:

SecureFieldsConfig(
tenantId: "${TENANT_ID}",
environment: .production,
pinnedPublicKeyHashes: [
"YOUR_PRIMARY_SPKI_HASH",
"YOUR_BACKUP_SPKI_HASH", // rotation backup — prevents downtime on cert renewal
]
)

Provide at least two hashes (primary + backup) so a certificate renewal does not break existing app versions in the field. Extract a hash from a live server:

openssl s_client -connect api.vault.purse-secure.com:443 2>/dev/null </dev/null \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| base64

Handle screenshot detection

The SDK fires secureFieldsScreenshotDetected() after the system saves a screenshot. iOS does not allow apps to prevent screenshots, but you can limit the exposure window:

func secureFieldsScreenshotDetected() {
secureFields.clearFields()
showAlert("Screenshot detected. For your security, please re-enter your card details.")
}

The SDK is distributed as a signed XCFramework. You can verify the signature independently:

codesign -dv --verbose=4 PurseSecureFields.xcframework/ios-arm64/PurseSecureFields.framework

Swift Package Manager also verifies the SHA-256 checksum embedded in Package.swift before using the downloaded artifact — any tampering with the release archive produces a checksum mismatch and Xcode refuses to resolve the package.

Jailbreak detection (optional, high-security environments)

On jailbroken devices, an attacker with physical access can attach a debugger or memory scanner regardless of SDK protections. If your risk model requires it, check before rendering the payment form:

if deviceIsJailbroken() {
showAlert("Payments are not available on this device.")
return
}

Prohibited practices

The following actions undermine the card data isolation model and are prohibited:

PracticeWhy it is prohibited
Screen recording or screen mirroring while the form is visibleThe SDK's privacy overlay mitigates this automatically, but deliberately working around it (e.g. disabling obscuresOnBackground without an equivalent replacement) defeats the isolation model
Subclassing or extending the internal card field classesSecurePANField, SecureCVVField, SecureExpDateField, and SecureHolderNameField are internal to the SDK module; attempting to access them via runtime introspection or method swizzling violates the isolation boundary
Using method swizzling or KVC/KVO to bypass the text getter overrideThe nil-returning getter is the SDK's core isolation mechanism; circumventing it via Objective-C runtime tricks defeats the SDK's entire security model
Building an accessibility service or automation tool to read field contentaccessibilityValue is overridden to return nil specifically to block this
Logging delegate payloads alongside card-identifying information you hold elsewhereSDK events never include card data, but do not correlate them with a stored PAN in your own logs in a way that could reconstruct card data
Reading memory from the SDK processAttaching a debugger or memory scanner to extract card data from the SDK's heap is prohibited and constitutes a PCI DSS violation

SAQ A-EP scope

The SDK architecture is designed to support SAQ A-EP eligibility:

  • Card data is captured by an embedded component (the SDK) provided by Purse
  • Your application code never directly processes, stores, or transmits raw card data
  • Tokenization is handled entirely within the SDK and the Purse gateway

For SAQ A-EP to apply, you must:

  1. Use the production environment (.production, which resolves to https://api.vault.purse-secure.com) in your release build
  2. Enable certificate pinning via pinnedPublicKeyHashes
  3. Implement secureFieldsScreenshotDetected() to clear fields and warn the user
  4. Leave obscuresOnBackground enabled (the default) unless you provide an equivalent mitigation
  5. Not implement any of the prohibited practices listed above
  6. Obtain a vaultFormToken from the SDK and pass it to your backend — never handle raw card data server-side either

Your QSA will verify these controls during assessment. Purse can provide an attestation letter describing the SDK's PCI scope on request.