# BitChat Emergency Wipe Implementation: How the Panic Wipe Feature Destroys Data Securely

> Explore BitChat's emergency wipe implementation. Learn how this feature securely destroys all local data, including keys and messages, ensuring privacy with guaranteed recovery markers.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-04

---

**BitChat's emergency wipe is a coordinated, multi-layered transaction that atomically erases all locally stored data—including messages, media, cryptographic keys, and even app-switcher snapshots—while using durable recovery markers to guarantee complete destruction even if the app crashes mid-operation.**

The panic wipe in BitChat (permissionlesstech/bitchat) serves as a last-resort privacy mechanism for users facing device compromise or coercion. This feature goes far beyond simple file deletion, implementing defense-in-depth strategies that thwart forensic recovery attempts and ensure no residual identity data survives.

## Architecture of the Panic Wipe System

The emergency wipe operates through eleven coordinated layers, each targeting a specific attack surface for data persistence.

| Layer | Responsibility | Key Implementation |
|-------|----------------|--------------------|
| **View-Model entry point** | Orchestrates the entire wipe operation | `ChatViewModel.panicClearAllData()` |
| **Service shutdown** | Prevents network callbacks from resurrecting cleared state | `panicNetworkLifecycle.stop()`, `meshService.emergencyDisconnectAll()` |
| **Durable intent creation** | Survives crashes via redundant markers in UserDefaults and filesystem | `PanicRecoveryOperations.live()` |
| **Media wipe** | Destroys voice notes, images, and files with directory recreation | `BLEIncomingFileStore.panicWipe()` |
| **Keychain wipe** | Eliminates all cryptographic secrets atomically | `KeychainManager.deleteAllKeychainData()` |
| **UserDefaults & identity** | Clears identity keys and randomizes nickname | `userDefaults.removeObject()`, `nickname = "anon…"` |
| **Location & presence** | Removes geohash state and bookmarks that reveal physical location | `LocationStateManager.shared.panicWipe()` |
| **Store clearing** | Purges conversation, courier, board, groups, and pre-key stores | `conversations.clearAll()`, `CourierStore.shared.wipe()` |
| **Nostr & relay state** | Resets relay identity and pending decrypts | `NostrRelayManager.shared.resetForPanicWipe()` |
| **BLE identity reset** | Suspends radio, wipes BLE identity, restarts under new identity | `panicTransport.resetIdentityForPanic()` |
| **App-switcher snapshot purge** | Deletes iOS UI screenshots in `~/Library/Caches/Snapshots/` | `clearAppSwitcherSnapshots()` |

The atomicity guarantee stems from **generation counter validation**: any background task holding pre-wipe keys encounters a mismatch after the wipe and aborts immediately, preventing data resurrection.

## Trigger Mechanisms

BitChat provides two activation paths for the emergency wipe feature:

- **Settings button**: A destructive-role button labeled via localized strings in [`AppInfoView.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppInfoView.swift)
- **Hidden gesture**: Triple-tap on the BitChat logo for rapid activation under duress

Both paths converge on `ChatViewModel.panicClearAllData()` at lines **1553-1755** of [`ChatViewModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModel.swift).

## Step-by-Step Execution Flow

The panic wipe proceeds through ten deterministic phases:

1. **User initiation** → `ChatViewModel.panicClearAllData()` receives control
2. **Network isolation** → `panicNetworkLifecycle.stop()` halts all connectivity; BLE transport suspends
3. **Crash recovery preparation** → `PanicRecoveryOperations.live().begin()` writes to UserDefaults and creates `.panic-recovery-pending` marker file
4. **Media destruction** → `BLEIncomingFileStore.panicWipe()` deletes and recreates media directories
5. **Cryptographic purge** → `KeychainManager.deleteAllKeychainData()` enumerates and wipes all keychain entries
6. **Store eradication** → Each persistent store executes `wipe()` for on-disk files and associated secrets
7. **Identity reset** → Noise identity keys cleared; random anonymous nickname assigned
8. **Transport reset** → Nostr relay manager and BLE transport reset identities and queues
9. **Snapshot elimination** → `clearAppSwitcherSnapshots()` removes iOS app switcher thumbnails
10. **Recovery commitment** → `panicRecoveryOperations.complete()` removes durable markers; services restart on success

Failure at any step preserves the recovery intent, triggering automatic retry on next app launch.

## Core Implementation: `panicClearAllData()`

The central coordinator method in [`ChatViewModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModel.swift) implements defensive execution patterns:

```swift
@discardableResult
func panicClearAllData(restartServices: Bool = true) -> Bool {
    panicRecoveryBlocked = true
    isPanicResetting = true
    defer { isPanicResetting = false }

    // 1️⃣ Stop services
    panicNetworkLifecycle.stop()
    if let panicTransport = meshService as? PanicResettingTransport {
        panicTransport.suspendForPanicReset()
    } else {
        meshService.emergencyDisconnectAll()
    }

    // 2️⃣ Create durable intent
    let recoveryIntent = panicRecoveryOperations.begin()

    // 3️⃣ Media & store wipes
    mediaTransferCoordinator.resetForPanic()
    liveVoiceCoordinator.resetForPanic()
    conversations.clearAll()
    keychain.deleteAllKeychainData()
    LocationStateManager.shared.panicWipe()
    // … additional store wipes

    // 4️⃣ Finalise recovery
    do {
        try panicRecoveryOperations.wipeMedia(recoveryIntent)
        try panicRecoveryOperations.complete()
        panicRecoveryBlocked = false
        return true
    } catch {
        // Recovery will retry on next launch
        return false
    }
}

```

Key defensive patterns include:
- **`panicRecoveryBlocked` flag**: Prevents re-entrant wipe attempts
- **`defer` with `isPanicResetting`**: Ensures state cleanup regardless of exit path
- **Typed throws**: Distinguishes recoverable from fatal errors

## Media File Destruction: `BLEIncomingFileStore.panicWipe()`

Media files receive special handling due to their detached task exposure:

```swift
func panicWipe(hasDurablePendingMarker: Bool = false) throws {
    // Delete every sub-directory of managed media
    for sub in Self.mediaSubdirectories {
        let dir = baseDirectory?.appendingPathComponent(sub)
        try fileManager.removeItem(at: dir)
        try fileManager.createDirectory(at: dir, withIntermediateDirectories: true,
                                        attributes: Self.mediaProtectionAttributes)
    }
    // Optional: write marker file for crash recovery coordination
    if hasDurablePendingMarker {
        let markerURL = baseDirectory?.appendingPathComponent(Self.panicRecoveryPendingMarkerFileName)
        try panicMarkerWriter(Data(), markerURL!)
    }
}

```

The **directory recreation step** eliminates race conditions where detached tasks might recreate files in stale paths.

## Cryptographic Key Elimination

The keychain wipe in [`KeychainManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/KeychainManager.swift) (lines **668-752**) uses enumeration rather than hardcoded key lists:

```swift
func deleteAllKeychainData() -> Bool {
    SecureLogger.warning("Panic mode - deleting all keychain data", category: .security)
    let searchQuery: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecMatchLimit as String: kSecMatchLimitAll,
        kSecReturnAttributes as String: true
    ]
    var result: AnyObject?
    let status = SecItemCopyMatching(searchQuery as CFDictionary, &result)
    // Enumerate and delete each owned item …
}

```

This approach catches **legacy and dynamically created entries** that explicit key lists would miss.

## Crash Recovery Guarantees

The `PanicRecoveryOperations` system implements **two-phase commit semantics**:

- **`live()` mode**: Persists intent to both UserDefaults and filesystem
- **`begin()`**: Creates markers before any destructive operation
- **`complete()`**: Removes markers only after all wipes succeed

If the process terminates between `begin()` and `complete()`, the next launch detects pending markers via `live()` and re-executes the full wipe sequence.

## Source Files and Locations

| File | Function | Lines |
|------|----------|-------|
| [`ChatViewModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModel.swift) | `panicClearAllData()` | 1553-1755 |
| [`ChatViewModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/ChatViewModel.swift) | `clearAppSwitcherSnapshots()` | 81-87 |
| [`BLEIncomingFileStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEIncomingFileStore.swift) | `panicWipe()` | 89-100 |
| [`KeychainManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/KeychainManager.swift) | `deleteAllKeychainData()` | 668-752 |
| [`LocationStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationStateManager.swift) | `panicWipe()` | 628-640 |
| [`PanicRecoveryOperations.swift`](https://github.com/permissionlesstech/bitchat/blob/main/PanicRecoveryOperations.swift) | `live()`, `begin()`, `complete()` | Full file |
| [`AppInfoView.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppInfoView.swift) | UI button configuration | 122-127 |

## Summary

- **BitChat's panic wipe** is a transactionally consistent, crash-recoverable operation that destroys all local data across eleven independent layers
- **Durable intent markers** in both UserDefaults and filesystem guarantee completion even if the app terminates unexpectedly
- **Service shutdown precedes data destruction** to prevent network callbacks from re-establishing state
- **Generation counter validation** provides implicit atomicity by invalidating all pre-wipe cryptographic contexts
- **Directory recreation and snapshot purging** address forensic recovery vectors often ignored by simpler wipe implementations

## Frequently Asked Questions

### What triggers the BitChat emergency wipe feature?

The panic wipe activates through either a destructive-role button in Settings (defined in [`AppInfoView.swift`](https://github.com/permissionlesstech/bitchat/blob/main/AppInfoView.swift)) or a hidden triple-tap gesture on the BitChat logo. Both paths invoke `ChatViewModel.panicClearAllData()` after user confirmation.

### Does the BitChat panic wipe work if the app crashes mid-operation?

Yes. The `PanicRecoveryOperations.live()` system writes durable markers to both UserDefaults and a filesystem file (`.panic-recovery-pending`) before any destructive operations. If the app crashes, these markers persist and trigger automatic wipe completion on the next launch.

### What data does the BitChat emergency wipe actually destroy?

The wipe eliminates messages, conversation history, media files, cryptographic keys from the iOS Keychain, geohash location data, Nostr relay state, BLE identity, and iOS app-switcher screenshots. It also randomizes the user's nickname to sever social graph associations.

### How does BitChat prevent data recovery after a panic wipe?

Multiple mechanisms prevent recovery: keychain enumeration catches all secrets including legacy entries; media directories are deleted and immediately recreated to foil undeletion; app-switcher screenshots are purged from `~/Library/Caches/Snapshots/`; and cryptographic generation counters invalidate any cached keys held by background tasks.