88382 w a insight wallet mnemonic stored with default ios keychain accessibility ksecattraccessiblewhenunlocked seed is backup extractable and not device bound
Submitted on Aug 12th 2026 at 23:26:07 UTC by @promael for Audit Comp | Quantus
Report ID: #88382
Report Type: Websites & Apps
Report severity: Insight
Target: https://github.com/immunefi-team/audit-comp-quantus-apps/tree/audit-comp-ready/quantus_sdk
Impacts:
Description
Brief/Intro
The Quantus SDK persists the wallet's BIP39 mnemonic — the single seed from which every account key (including Dilithium/ML-DSA signing keys and wormhole secrets) is derived — using FlutterSecureStorage with no platform accessibility options set. On iOS this falls back to the library default kSecAttrAccessibleWhenUnlocked, which stores the item without the ThisDeviceOnly protection class. Such Keychain items are included in encrypted device backups and are migratable to a different device on restore. As a result, an attacker who obtains a victim's encrypted iTunes/Finder backup (and its backup password) can extract the plaintext seed phrase from the backup and restore full wallet control onto their own device — a path that a …ThisDeviceOnly protection class would close entirely. This is a defense-in-depth gap for the most sensitive secret in the system, not a remotely triggerable vulnerability.
Vulnerability Details
The only FlutterSecureStorage instance in the in-scope code is constructed with no IOSOptions/AndroidOptions:
// quantus_sdk/lib/src/services/settings_service.dart:16
final _secureStorage = const FlutterSecureStorage(
mOptions: MacOsOptions(usesDataProtectionKeychain: false),
);The wallet mnemonic is written to and read from this store in plaintext:
flutter_secure_storage (pinned ^10.1.0) defaults, when no IOSOptions(accessibility: …) is provided, to KeychainAccessibility.unlocked → the iOS Keychain attribute kSecAttrAccessibleWhenUnlocked. Two consequences follow from that protection class:
Included in encrypted backups. Items with
kSecAttrAccessibleWhenUnlockedare copied into an encrypted iTunes/Finder backup and, on restore, are re-installed onto the target device. Only the…ThisDeviceOnlyvariants (kSecAttrAccessibleWhenUnlockedThisDeviceOnly/…AfterFirstUnlockThisDeviceOnly) are excluded from every backup and bound to the originating device's hardware.Not device-bound. Because the item is not
ThisDeviceOnly, the seed can be materialized on hardware other than the one that generated it.
A grep across the two in-scope Dart trees (quantus_sdk/lib, mobile-app/lib) confirms zero occurrences of IOSOptions, AndroidOptions, KeychainAccessibility, or accessibility — i.e. the accessibility class is never overridden anywhere, and this single store holds the mnemonic for every wallet index.
Scope note (honest): this is an iOS/macOS issue. On Android, flutter_secure_storage encrypts values with a key held in the Android Keystore, whose master key is non-exportable; even if the encrypted blob is captured by Android auto-backup, it cannot be decrypted off-device, so the Android default is materially safer. The additional usesDataProtectionKeychain: false on macOS selects the weaker legacy file-based keychain over the modern data-protection keychain — a secondary hardening gap on macOS.
Impact Details
The mnemonic is the root secret controlling all funds (it deterministically derives the Dilithium account keys and the m/44'/189189189'/… wormhole secrets). With the default protection class, the attack path is:
The ThisDeviceOnly protection class would make steps 1–2 impossible (the item is never present in any backup and never leaves the device). Because exploitation requires possession of an encrypted backup plus its password — not a remote or on-chain trigger — this is honestly an Insight / Low (best-practice hardening for key material), not a High/Critical. It is nonetheless a real, end-to-end-traceable weakness in how the most sensitive secret is stored, and it is trivially fixable.
Recommended fix
Pin the strongest protection class for the seed store (and use the modern data-protection keychain on macOS):
first_unlock_this_device (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) keeps the seed usable for background operations while excluding it from all backups and binding it to the device. If background access is not required, unlocked_this_device is even stricter.
References
quantus_sdk/lib/src/services/settings_service.dart:16(storage construction, no accessibility options)quantus_sdk/lib/src/services/settings_service.dart:352-358(setMnemonic/getMnemonic— plaintext seed write/read)quantus_sdk/pubspec.yaml:36(flutter_secure_storage: ^10.1.0)Apple, Keychain Item Accessibility Constants —
kSecAttrAccessibleWhenUnlockedvs…ThisDeviceOnly(backup inclusion / device binding semantics)
Proof of Concept
Proof of Concept (step-by-step; no runnable exploit required for an Insight)
Conditions under which the weakness is valuable / demonstrable:
Contrast: after applying the recommended first_unlock_this_device fix and repeating steps 1–3, the mnemonic item no longer appears in the encrypted backup — closing the extraction path.
Was this helpful?