Skip to content

galahador/DeviceSecurityKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

214 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ›‘οΈ DeviceSecurityKit

DeviceSecurityKit

Lightweight iOS Security Detection Framework

Detect jailbreaks, debuggers, emulators, Frida, runtime hooks, SSL pinning bypasses, VPN/proxy usage and more.

Swift iOS SPM License


πŸš€ Features

Category Detection
πŸ”“ Jailbreak Files, sandbox escape, fork capability, URL schemes, symlinks, environment variables, TrollStore markers, and anti-detection tweak signatures (Shadow, Liberty Lite, A-Bypass)
🐞 Debugger sysctl, ptrace, parent process, timing analysis, breakpoint instructions
πŸ“± Emulator Hardware mismatch, simulator artifacts, DeviceCheck validation
🧬 Reverse Engineering Frida, Substrate, libhooker, runtime tampering
πŸ”’ App Integrity Code signature validation, Team ID verification, CodeResources hash validation
πŸͺ Hook Detection Runtime function hook detection via ARM64 prologue inspection
πŸ”„ Swizzling Detection Objective-C IMP redirection validation, including biometric (LAContext) method hooking
πŸ‘Ύ Frida Detection Libraries, symbols, process checks, multi-port scanning, Frida Gadget dylib signatures, frida-server filesystem artifacts
πŸ“Ί Screen Recording Active recording and mirroring detection
πŸ“Έ Screenshot Detection Real-time screenshot notifications
🌐 Pinning Bypass Detection Detects bypass tools (SSLKillSwitch, ssl-proxy, etc.) and proxy configurations β€” not a substitute for implementing certificate/public-key pinning in your networking stack
πŸ”Œ VPN / Proxy Detection VPN interfaces and proxy configuration detection
πŸ” App Attest Apple App Attest validation
πŸ“¦ Anti-Repackaging Signing certificate verification
πŸ›‘οΈ DSK Integrity Runtime validation of DSK internals
⏱️ Monitoring Continuous background security monitoring, BGTaskScheduler integration
πŸ”„ Signature Updates Ed25519-verified remote updates to detection lists
🏒 MDM Detection Flags devices running under an enterprise Managed App Configuration
πŸ“‹ Clipboard Monitoring Detects unexpected pasteboard changes after the app copies sensitive data
πŸ–₯️ External Display Detection Flags AirPlay screen mirroring or external/wired monitor connections
⌨️ Keyboard Detection Flags third-party keyboard extensions active on sensitive input fields
🌍 Localization All user-facing strings (threat/status/severity descriptions, reports) ship via a String Catalog and adapt to the device's locale
πŸ› οΈ CI Scanning dsk-scan CLI runs static integrity/repackaging checks against a built .ipa/.app for CI pre-submission gates

⚑ Quick Start

import DeviceSecurityKit

DSK.shared
    .configure(.production)
    .onThreatDetected { threat in
        print("Threat: \(threat.description)")
    }
    .start()

πŸ“¦ Installation

Swift Package Manager

Xcode

  1. File β†’ Add Package Dependencies
  2. Enter:
https://github.com/galahador/DeviceSecurityKit.git
  1. Select:
from: "0.40.0"
  1. Add Package

Package.swift

dependencies: [
    .package(
        url: "https://github.com/galahador/DeviceSecurityKit.git",
        from: "0.40.0"
    )
]

🎯 Usage

Configure

DSK.shared
    .configure(.production)
    .start()

Threat Monitoring

DSK.shared
    .onThreatDetected { threat in
        print(threat.description)
    }
    .start()

Security Status Monitoring

DSK.shared
    .onStatusChange { status in
        print(status)
    }
    .start()

One-Shot Security Check

let result = DSK.shared.performCheck()

if result.isSecure {
    print("Secure")
} else {
    print(result.threats)
}

performCheck() runs all enabled detectors synchronously and may take several seconds. Call it off the main thread, or use the async variant below.

Async API

let result = await DSK.shared.performCheckAsync()
let secure = await DSK.shared.isSecureAsync()

App Attest

let attestation = try await DSK.shared.attest(challengeHash: serverChallenge)
// Send `attestation` to your backend for verification

AsyncStream

Subscribe to a live stream of threat events:

Task {
    for await event in DSK.shared.threatEvents {
        print("\(event.threat) at \(event.detectedAt)")
        print("Evidence: \(event.evidence)")
    }
}

Multiple consumers can subscribe independently. The stream ends when stop() is called or the consuming Task is cancelled.

Threat History

DSK keeps a ring buffer of recent ThreatEvents so you can inspect detections after the fact:

let history = DSK.shared.threatHistory

for event in history {
    print("\(event.threat) β€” \(event.detectedAt)")
}

Configure the buffer size (default: 100) and clear it:

DSK.shared
    .threatHistoryMaxSize(200)
    .start()

// Later:
DSK.shared.clearThreatHistory()

Threat History Persistence

Opt in to persist threatHistory to the Keychain so a tampering event survives an app relaunch or kill β€” useful for forensics:

let config = DeviceSecurityConfiguration.default
    .withThreatHistoryPersistence(true)

DSK.shared
    .configure(config)
    .start()

History is rehydrated from the Keychain on init when enabled, and clearThreatHistory() also wipes the persisted copy. Disabled by default.

SwiftUI

DSKObservable wraps DSK as an ObservableObject, publishing status and threatHistory for use directly in SwiftUI views:

struct ContentView: View {
    @StateObject private var dsk = DSKObservable()

    var body: some View {
        VStack {
            Text("Status: \(dsk.status.rawValue)")

            List(dsk.threatHistory) { event in
                Text("\(event.threat) β€” \(event.detectedAt)")
            }
        }
    }
}

🚨 Responding To Threats

DSK.shared
    .onThreatDetected { threat in

        switch threat.severity {

        case .critical:

            AuthManager.shared.clearTokens()
            KeychainManager.shared.wipe()

            Analytics.log(
                "security_threat",
                ["type": threat.rawValue]
            )

            exit(0)

        case .high:

            showSecurityAlert()

        default:
            break
        }
    }
    .start()

βš™οΈ Configuration

Presets

.configure(.default)
.configure(.production)
.configure(.jailbreakOnly)
.configure(.disabled)

Custom Configuration

let config = DeviceSecurityConfiguration.default
    .withJailbreakCheck(true)
    .withDebuggerCheck(true)
    .withEmulatorCheck(false)
    .withReverseEngineeringCheck(true)
    .withScreenRecordingCheck(true)
    .withScreenshotDetection(true)
    .withHookDetection(true)
    .withPinningBypassDetection(true)
    .withSwizzlingDetection(true)
    .withFridaDetection(true)
    .withAttestationCheck(true)
    .withVPNProxyDetection(
        true,
        allowedBundleIDs: [
            "com.example.corporate-vpn"
        ]
    )
    .withAppIntegrityCheck(
        true,
        expectedTeamID: "ABCDE12345"
    )
    .withAntiRepackagingCheck(
        true,
        expectedCertificateHash: "a1b2c3..."
    )
    .withMDMDetection(true)
    .withClipboardMonitoring(true)
    .withExternalDisplayDetection(true)
    .withKeyboardExtensionDetection(true)

DSK.shared
    .configure(config)
    .start()

Clipboard Monitoring

iOS does not let an app detect when another process reads the pasteboard. As a practical proxy, call ClipboardMonitor.markSensitiveCopy() right after copying sensitive data (passwords, OTPs, tokens) to the clipboard. This baselines UIPasteboard.general.changeCount; if the pasteboard changes again β€” by this app or another process β€” without another markSensitiveCopy() call, DSK reports SecurityThreat.clipboardExfiltration.

UIPasteboard.general.string = oneTimePasscode
ClipboardMonitor.markSensitiveCopy()

External Display Detection

When withExternalDisplayDetection(true) is enabled, DSK checks UIScreen.screens.count > 1 to detect AirPlay screen mirroring or a connected external/wired monitor. If a second screen is present, DSK reports SecurityThreat.externalDisplayConnected β€” useful for hiding sensitive content (e.g. with secureScreen(dsk:)) while the device's screen is being mirrored.

Keyboard Extension Detection

When withKeyboardExtensionDetection(true) is enabled, mark sensitive fields (password, OTP, payment, etc.) as they become active so DSK can flag a third-party keyboard extension being used to type into them:

func textFieldDidBeginEditing(_ textField: UITextField) {
    KeyboardExtensionMonitor.markSensitiveFieldActive(textField)
}

func textFieldDidEndEditing(_ textField: UITextField) {
    KeyboardExtensionMonitor.markSensitiveFieldInactive()
}

If the active input mode isn't an Apple system keyboard while a sensitive field is marked active, DSK reports SecurityThreat.thirdPartyKeyboardActive within a configurable detection window (KeyboardExtensionMonitor.detectionWindowSeconds, default 10s).


πŸ”’ Anti-Repackaging

Obtain Your Certificate Hash

#if DEBUG
print(
    RepackagingDetector.currentCertificateHash()
)
#endif

Configure

.withAntiRepackagingCheck(
    true,
    expectedCertificateHash:
    "your-hash-here"
)

πŸ”„ Signature Updates

SignatureUpdateManager extends DSK's built-in detection lists (jailbreak paths, debugger process names, reverse-engineering libraries, etc.) with additional entries from a remotely-distributed, Ed25519-signed manifest. Detectors append these entries to their static lists, so you can react to newly-discovered jailbreak tools or hooking frameworks without shipping an app update.

SignatureUpdateManager.shared
    .configure(publicKey: yourEd25519PublicKey)

let manifest = try await SignatureUpdateManager.shared.update(
    from: manifestURL
)

print(manifest.version)
print(SignatureUpdateManager.shared.entries(for: .jailbreakPaths))

The manifest is a signed envelope (payload + signature); update(from:) verifies the signature against the configured public key before applying or caching it. An invalid signature throws SignatureUpdateError.invalidSignature, and calling update(from:) before configure(publicKey:) throws .notConfigured. The most recently verified manifest is cached on disk and reloaded automatically the next time configure(publicKey:) is called.


πŸ› οΈ CI Pre-Submission Scanning (dsk-scan)

dsk-scan is a standalone CLI executable shipped alongside the package. It runs the same static checks as AppIntegrityDetector/RepackagingDetector β€” but offline, against a built .ipa or .app β€” so you can gate CI before submitting to the App Store.

swift run dsk-scan Build/MyApp.ipa \
    --expected-certificate-hash a1b2c3... \
    --expected-team-id ABCDE12345

🌐 VPN Allowlist

.withVPNProxyDetection(
    true,
    allowedBundleIDs: [
        "com.cisco.anyconnect",
        "com.microsoft.intune.tunnel"
    ]
)

⏱️ Monitoring Interval

DSK.shared
    .monitoringInterval(30)
    .start()

Default: 60 seconds

Adaptive Monitoring

DSK uses exponential backoff to balance responsiveness with efficiency:

  • Threat detected β€” interval snaps to minMonitoringInterval for rapid re-checking.
  • Consecutive clean cycles β€” interval doubles each cycle: base Γ— 2^cleanCycles, clamped to [min, max].
DSK.shared
    .monitoringInterval(60)        // base interval
    .minMonitoringInterval(10)     // fastest re-check
    .maxMonitoringInterval(600)    // slowest backoff
    .start()

Query the current adaptive interval at any time:

let current = DSK.shared.currentMonitoringInterval

Background Monitoring (BGTaskScheduler)

DSK can run a security check while the app is suspended, via BGAppRefreshTask:

let identifier = "com.example.app.dsk-refresh"

DSK.shared
    .registerBackgroundTask(identifier: identifier)

DSK.shared.scheduleBackgroundCheck(identifier: identifier)

registerBackgroundTask(identifier:) should be called during app launch (before applicationDidFinishLaunching returns). Each run automatically reschedules the next check and calls performCheckAsync().

Add the identifier to your Info.plist:

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.example.app.dsk-refresh</string>
</array>

🎯 Countermeasures

Countermeasures are automatic actions that fire when a threat is detected.

Any Threat

DSK.shared
    .countermeasure(throttled: false) { threat in
        Analytics.log("dsk_threat", ["type": threat.rawValue])
    }

Specific Threat

DSK.shared
    .countermeasure(for: .jailbreak, throttled: true) { _ in
        AuthManager.shared.clearTokens()
    }

Severity-Based

DSK.shared
    .countermeasure(forMinimumSeverity: .critical, throttled: true) { threat in
        KeychainManager.shared.wipe()
        exit(0)
    }

Custom Countermeasure Object

let cm = Countermeasure(
    trigger: .threat(.fridaDetected),
    throttled: true
) { _ in
    exit(0)
}

DSK.shared.addCountermeasure(cm)

Remove Countermeasures

DSK.shared.removeCountermeasure(cm)
DSK.shared.removeAllCountermeasures()

Throttled countermeasures execute once every 300 seconds per threat type. Adjust with .threatCallbackThrottleInterval(_:).


πŸ“Š Threat Severity

Severity Meaning
🟒 Normal No threat detected
πŸ”΅ Low Informational
🟑 Medium Potential risk
🟠 High Dangerous environment
πŸ”΄ Critical Immediate action recommended

πŸ“š API Reference

SecurityMonitor

Method Description
performCheck() Run all configured checks
isSecure() Quick security status
startMonitoring() Begin monitoring
stopMonitoring() Stop monitoring
configure() Update configuration
onStatusChange() Status callback
onThreatDetected() Threat callback

🚩 Supported Threats

Threat Severity
Jailbreak πŸ”΄ Critical
Reverse Engineering πŸ”΄ Critical
App Integrity Failure πŸ”΄ Critical
Hook Detection πŸ”΄ Critical
Method Swizzling πŸ”΄ Critical
Pinning Bypass πŸ”΄ Critical
Frida πŸ”΄ Critical
Attestation Failure πŸ”΄ Critical
DSK Tampering πŸ”΄ Critical
Repackaging πŸ”΄ Critical
Debugger 🟠 High
Screen Recording 🟠 High
Emulator 🟑 Medium
VPN / Proxy 🟑 Medium
Screenshot 🟑 Medium
MDM / Enterprise Management 🟒 Low
Clipboard Exfiltration 🟑 Medium
External Display Connected 🟑 Medium
Third-Party Keyboard Active 🟑 Medium

🌍 Localization

All user-facing strings β€” SecurityThreat.description, ThreatSeverity.description, SecurityStatus.description, RiskLevel.description, and SecurityResult.generateReport(...) β€” are sourced from a String Catalog (Localizable.xcstrings) bundled with DSK. They automatically follow the host app's locale; no extra setup is required. Contribute additional translations by adding languages to the catalog.


πŸ“‹ Info.plist

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>cydia</string>
    <string>sileo</string>
    <string>zbra</string>
    <string>filza</string>
    <string>undecimus</string>
    <string>checkra1n</string>
    <string>taurine</string>
    <string>odyssey</string>
    <string>dopamine</string>
</array>

πŸ“± Requirements

Requirement Version
iOS 15.0+
Swift 5.9+
Xcode 15.0+

Limitations

DeviceSecurityKit is a client-side detection library. All checks run within the app process on the user's device, which means:

  • Bypassable by a determined attacker. Anyone with full control of the device (root access, custom kernel, instrumentation frameworks) can intercept, patch, or suppress any check. No client-side security library can prevent this.
  • Best used as a signal, not a gate. Treat detection results as one input into a broader risk-assessment pipeline. Combine them with server-side validation (App Attest, device posture APIs, backend anomaly detection) for defence in depth.
  • False positives are possible. Some legitimate developer tools, accessibility software, enterprise MDM profiles, or VPN configurations may trigger detections. Test thoroughly with your user base and use the configuration API to disable checks that don't apply.
  • Simulator environment. Several detectors are automatically disabled in the iOS Simulator (#if targetEnvironment(simulator)) because they would always trigger. Test security-critical flows on a real device.

🀝 Contributing

Issues and pull requests are welcome.

For major changes, please open an issue first.


πŸ“„ License

MIT License

Created by @galahador


πŸ›‘οΈ Security First β€’ Zero Dependencies β€’ Open Source Forever

About

A lightweight iOS security detection library for Swift that identifies compromised runtime conditions in real time.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages