RSSAmplifier

Sentinel Den · Engineering blog · Apr 6, 2026

iOS clipboard threat model: why UIPasteboard fails crypto wallets

0
Sign in to vote or save

Muhammad Khan · Sentinel Den

UIPasteboard.general is the iOS clipboard. It is a process that runs system-wide, persists data across apps and across launches, syncs to other Apple devices via Universal Clipboard, and exposes its contents to any app the user opens for the next several minutes (with a banner notification, on iOS 16+). For copying a URL between Safari and Mail, this is the right design. For copying a 24-word BIP-39 mnemonic between a wallet’s reveal view and its backup view, this is the entire problem.

Most iOS wallets use UIPasteboard.general because it is the easiest API and because their threat model has not been deliberately considered. The result is that the secret the wallet is trying to protect leaves the wallet’s process the moment the user taps “Copy”. This post is the threat model on UIPasteboard, why the standard mitigations are insufficient, and the engineering pattern for an in-process secure clipboard that closes the gaps, the clipboard leak paths formalized in the InputGuard threat model.

What UIPasteboard actually does

When code calls UIPasteboard.general.string = "...", the string goes to a system process called pasteboardd. From that process:

  • Any other app on the device can read the value via UIPasteboard.general.string. On iOS 16+, the OS shows a banner (“App pasted from Wallet”) the first time the new app reads, after a small delay. This is a notification, not a permission gate, the read has already happened.
  • If the user has Universal Clipboard enabled (default on, in Settings → General → AirDrop & Handoff), the value is broadcast over Bluetooth + iCloud to every Apple device signed into the same iCloud account within 10–60 seconds.
  • The Universal Clipboard transmission is end-to-end encrypted to the user’s devices, but the value sits in the receiving devices’ clipboards for the same minutes-to-hours window.
  • The value persists in pasteboardd until overwritten or the device reboots. Even after your app quits, the value is still there.

For a seed phrase, this is the whole leak path: wallet → UIPasteboard → other app on phone, or other Apple device, or, in the case of Mac handoff, the clipboard buffer that the user’s open Mac terminal accidentally pastes the seed into the next time they hit Cmd-V.

The “auto-clear after N seconds” half-measure

The standard mitigation is to overwrite UIPasteboard with an empty string a few seconds after the user copies. Apple’s UIPasteboard.setItems(_:options:) even has an expirationDate option that does this automatically. Most apps that have considered the problem at all ship this.

It is not enough, for three reasons:

  1. The 30-second window is enough. Universal Clipboard transmits the value within 10 seconds. Even if the wallet auto-clears at 30 seconds, the Mac and iPad have already received the value and stored it in their own clipboards, where the wallet has no reach.
  2. Other apps already read. A malicious app installed alongside the wallet can poll UIPasteboard.general.string every 100ms and capture the value within milliseconds of the copy. The auto-clear is too slow.
  3. The expiration date is advisory. On older iOS versions, expirationDate is honored on a best-effort basis; on devices with low memory pressure, the auto-clear sometimes doesn’t fire. The value persists past the configured window in production-observed cases.

The auto-clear is necessary as a backstop. It is not a defense.

The right shape: in-process clipboard

The correct primitive is a clipboard that lives entirely inside your app’s process. Three properties define it:

  • In-process: never crosses to UIPasteboard, never to any system process, never to Universal Clipboard. Lives in your app’s address space and dies when your app terminates.
  • Encrypted at rest: even within your own process, the buffer is encrypted with an AES-256-GCM key generated per app launch. A debugger attached to a non-debug build cannot dump the value as plaintext.
  • Time- and use-bounded: configurable expiration after N seconds, or after the buffer has been read once (write-once-read-once mode). Cleared with memset_s or equivalent to zero the bytes, not just deallocate them.

InputGuard’s SecureClipboard ships exactly this. The API is intentionally parallel to UIPasteboard.general so wallet code that previously called UIPasteboard.general.string = phrase becomes try inputGuard.clipboard.copy(phrase). The threat model changes; the API surface barely does.

import InputGuardSDK

// Activate the license once at launch, BEFORE configure or any gated accessor.
// start(...) returns the instance you hold onto; there is no shared singleton.
let inputGuard = try await InputGuard.start(apiKey: apiKey, environment: .production)

// Configure once at app launch, auto-clear after 30s, sanitize UIPasteboard
// on every write to the secure clipboard (defense in depth in case the
// integrator accidentally copies to the system clipboard elsewhere).
try inputGuard.configure(
    InputGuardConfiguration(
        clipboardAutoClearInterval: 30,
        sanitizeSystemPasteboard: true,
        // ...
    )
)

// Copy a seed phrase with the typed kind
try inputGuard.clipboard.copy(
    Data(mnemonic.joined(separator: " ").utf8),
    kind: .seedPhrase
)

// Paste it back inside your own app, at the destination view
let phrase = try inputGuard.clipboard.pasteString()

// Or read structured data with the typed kind
let entry = try inputGuard.clipboard.paste()
// entry.kind == .seedPhrase, entry.writtenAt == ..., entry.asString() == "abandon ability ..."

// Manually clear when the user dismisses the backup view
inputGuard.clipboard.clear()

The sanitizeSystemPasteboard: true option is the defense in depth: if any code path accidentally writes a secret to UIPasteboard.general, the secure clipboard automatically clears UIPasteboard.general on every write to its own buffer. The integrator may also explicitly bridge a value to UIPasteboard for the legitimate cross-app case (sharing a deposit address with Cash App), via the static ClipboardSanitizer.copyToSystemPasteboard(_:ttl:) call (which sets a system-clipboard expiration and localOnly: true), the design makes the unsafe path explicit rather than default.

The cross-app-paste edge case

A real wallet needs cross-app paste sometimes. The user wants to send Bitcoin to a deposit address they’ve copied from a friend’s text message. The address lives on UIPasteboard. The wallet has to read it.

The threat model here is reversed: the wallet is the receiver, not the source. The value on UIPasteboard came from another app, the wallet only needs to read it once and immediately. The pattern:

// Detect that a UIPasteboard value matches a known sensitive pattern
// (Bitcoin address, Ethereum address, etc.) and ask the user explicitly
// before reading. PasteboardSentinel is the primitive.
let monitor = PasteboardSentinel(
    matchers: [BitcoinAddressMatcher(), EthereumAddressMatcher()],
    clearOnDetect: false,
    scanPolicy: .onUserAction
)
monitor.onDetect = { matched in
    // User taps "Yes, paste this address"
    showConfirmation { confirmed in
        if confirmed, let value = UIPasteboard.general.string {
            // Adopt into the encrypted buffer, then wipe the system clipboard.
            try? inputGuard.clipboard.copy(Data(value.utf8), kind: .address)
            UIPasteboard.general.string = ""
        }
    }
}
monitor.start()

Adopting the value into the secure clipboard and immediately overwriting UIPasteboard with an empty string clears the system clipboard before the next iOS banner fires; other apps polling UIPasteboard miss the value (they were always going to miss it eventually, but here the window is sub-second instead of multi-minute).

What about Apple’s own anti-leak measures?

iOS 16 added the paste banner notification (“Wallet pasted from Notes”). This is helpful for noticing leaks after the fact. It is not protection, the read has already happened. The banner is a UX nudge for the user, not a permission gate.

iOS 17 added UIPasteControl, a button view the system renders that, when tapped, hands the pasteboard value to your app without triggering the banner. This is useful for legitimate cross-app paste UX but does not change the underlying threat model, the value is still on pasteboardd, still synced to other devices, still readable by other apps.

iOS 18 (rumored) is adding per-pasteboard access controls similar to clipboard permissions on Android. This will help but is forward-looking, apps on iOS 17 and below have no equivalent protection.

The in-process clipboard is the only pattern that does not depend on the iOS clipboard subsystem. It is also the only pattern that survives Universal Clipboard cleanly, the value never crosses the wireless transmission boundary because it never goes to pasteboardd in the first place.

What this looks like end-to-end

For a wallet’s seed-phrase backup flow:

  1. User taps “Show recovery phrase”. Wallet shows the phrase in a protected view (see iOS keyboard leak surfaces) without ever putting the phrase on any clipboard.
  2. User taps “Copy”. Wallet writes to inputGuard.clipboard.copy(phrase). The phrase does not touch UIPasteboard.
  3. User taps “Verify backup”. Wallet reads from inputGuard.clipboard.pasteString() inside its own backup-verification flow.
  4. The clipboard auto-clears after 30 seconds. If the user has not completed verification by then, they have to copy again, friction that maps to the actual risk window.

The user’s cross-app paste experience is unchanged for non-sensitive values; the wallet’s threat surface for the sensitive values is reduced from “every app, every Apple device, persisted for minutes” to “this process, encrypted at rest, gone in 30 seconds”. That is the entire point of the in-process clipboard primitive.

See /sdk/inputguard for the marketing summary, /docs/inputguard for the clipboard API reference, and the companion posts on iOS keyboard leak surfaces and BIP-39 verification challenges for the rest of the wallet-input threat model.

Read the original on sentinelden.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.