fix: resolve 61 security audit findings, mitigate 7, document 5 upstream - #2669
Conversation
ee0bbca to
bfd3e38
Compare
The shared Flutter setup action used fvm.app's installer, which still hardcodes the legacy leoafarias/fvm repository. On arm64 that redirect returned HTTP 503 repeatedly and prevented integration jobs from reaching any build or test step. Download the pinned 4.1.2 archive directly from conceptadev/fvm, select x64 or arm64 from uname, retry transient release-asset failures, and validate the installed binary. This matches the direct installer already used by the App Store workflow. Refs #2669
|
@claude audit this PR |
|
Claude finished @ethicnology's task in 4m 49s —— View job Audit of PR #2669I reviewed all 16 commits against the fork point ( The overall direction of the PR is good — parent-tx verification, the swap/wallet binding in 🔴 Critical1. [verified] 🟠 High — fund safety & broken flows2. [verified] Payjoin is silently disabled for every software-wallet send — 3. [verified] The #2627 fix inverts itself — 4. [verified] Coin control is silently defeated by the #2631 prune — 5. [verified] #2601 (rebroadcast-same-tx on retry) does not cover Liquid — 6. [verified] Signature invalidation clears the wrong field (#2593 incomplete) — 7. [verified] Fee fetching is broken (or Tor-bypassing) under Tor — 8. [verified] UR animated scans from external wallets wedge (#2637 regression) — 9. [plausible — verify on device] PushTx NFC checksum length — 10. [plausible — verify against Boltz] MRH amount equality gate may block legitimate invoices — 🟡 Medium11. [verified] BIP329 reserved-label forgery is still possible (#2606) — 12. [verified] The new swap-binding guards unbind legitimate swap legs — 13. [verified] Exchange-order labels are now almost never written (#2624) — the buy path labels only in 14. [verified] #2602's outage handling is dead code — 15. [verified] CI supply chain ( 16. [verified] Log scrubbing over-redacts to the point of gutting the logs — 🟢 Minor (condensed)
Test suiteThe headline claim — "every finding reproduced by a dedicated test… flipped into a regression test" — doesn't hold for the areas above. Most of the What checked out cleanParent-tx verification in Recommendation: fix #1 (compile), #2–#6 (fund-safety regressions in the send flow), #8 (scan wedge), #11 and #12 before merge; #9/#10 need device/Boltz verification, which the PR already lists as pending. Given several fixes regressed adjacent behavior while their grep-tests stayed green, I'd also convert at least the send-cubit tests to behavioral ones in this PR rather than a follow-up. |
Addresses the NATIVE iOS findings from the security audit: - #2599: apply NSURLIsExcludedFromBackupKey to the app Documents directory at startup so wallet databases, payjoin state and TSV logs no longer ship to iCloud/iTunes backups - #2612: stop registering the four disabled BGTask identifiers so a startup failure cannot reactivate background tasks the user turned off; the Dart-side cancellation stays as defense in depth Regression tests: test/security_audit/issue_2599_test.dart, test/security_audit/issue_2612_test.dart
Addresses the NATIVE Android finding from the security audit: - #2640: the manifest advertised an exported, browsable intent filter for bitcoin: URIs but no consumer routes them, so any website could launch the app into an unhandled state. Remove the filter rather than implement a deep-link consumer. Regression test: test/security_audit/issue_2640_test.dart
Addresses the QR/UR findings from the security audit: - #2604: decode ur:crypto-hdkey CBOR maps with integer keys so Passport and Keystone device imports no longer fail silently - #2607: reject BBQR totals below one and out-of-range part indexes, and reset decoder state when stream parameters change so one invalid frame cannot wedge the scan session - #2608: enforce conservative UR sequence-count and message-size limits before frames reach the pinned bc-ur-dart fountain decoder - #2637: reset the UR reader after decode errors and successful completion so a failed stream cannot permanently wedge the scanner Regression tests: test/security_audit/issue_2604_test.dart, issue_2607_test.dart, issue_2608_test.dart, issue_2637_test.dart
Addresses the BIP85 findings from the security audit: - #2613: re-derive stored paths only after checking the stored xprvFingerprint against the current default seed, so a default-wallet change cannot silently display entropy from another seed - #2614: apply the privacy-screen mixin to the BIP85 home page so derived secrets are protected from screen capture - #2644: overwrite the clipboard with empty text on a timer after a copy (remaining lifetime work is tracked in the audit PR) - #2645: select the default wallet with a mainnet/Bitcoin filter so testnet selection no longer breaks derivation - #2646: serialize derivation actions in the cubit so concurrent taps cannot race on the next index (datasource-level atomicity is tracked as a remaining partial in the audit PR) Regression tests: test/security_audit/issue_2613_test.dart, issue_2614_test.dart, issue_2644_test.dart, issue_2645_test.dart, issue_2646_test.dart
Addresses the Dart-side BITBOX findings from the security audit: - #2650: classify bridge errors from one explicit pattern table that maps device-side cancellation and pairing rejection to a dedicated failure instead of an unexpected one (typed Rust variants remain an upstream fix) - #2652: restart the BLE settle window on every newly discovered advertiser instead of anchoring it to the first device seen - #2653: request the extended-key version matching the account's script type (BIP44/49/84, mainnet and testnet) instead of always xpub/tpub The remaining BITBOX findings are upstream in the bull_sdk bridge and stayed open: #2615 (attestation), #2647 (pairing error propagation), #2648 (USB permission), #2649 (P2WSH panic), #2651 (device lock). Regression tests: test/security_audit/issue_2650_test.dart, issue_2652_test.dart, issue_2653_test.dart
… bypass Addresses the FEES findings from the security audit: - #2618: reject or clamp out-of-range fee-oracle values, enforce monotonic tiers and cap the relay floor so a malicious server cannot overprice or break fee selection - #2619: configure bounded connect/send/receive timeouts on the fee HTTP client - #2657: disable redirects on fee requests so an HTTPS endpoint cannot silently downgrade to plaintext or delegate to another oracle - #2658: route fee requests through the Tor-aware SOCKS5 transport when Tor is enabled instead of a direct client Regression tests: test/security_audit/issue_2618_test.dart, issue_2619_test.dart, issue_2657_test.dart, issue_2658_test.dart
Addresses the MEMPOOL findings from the security audit: - #2620: a plaintext (non-TLS) custom server can no longer be marked fee-capable by default - #2621: editing a custom server atomically replaces the existing row instead of leaving a duplicate that breaks single-row reads - #2622: validation fetches and compares the genesis block hash so a valid server on the wrong Bitcoin network is rejected with a typed network-mismatch error (Liquid networks remain unverified, tracked in the audit PR) - #2623: parse URLs with Uri and reject userinfo, query/fragment, unexpected paths and invalid ports; normalize hostnames - #2659: route onion validation through the configured Tor HTTP client instead of a direct one - #2660: propagate custom-server deletion failures with a typed, retryable error instead of reporting success - #2661: compare canonical parsed hosts (trailing dots stripped, userinfo rejected) for the default-server guard - #2662: keep custom server URLs out of fee exceptions and validator logs so they cannot reach logs or telemetry Regression tests: test/security_audit/issue_2620_test.dart through issue_2662_test.dart (MEMPOOL series)
Addresses the LOG findings from the security audit: - #2598: redact long hex/Base64, mnemonic-like and API-key-like values at the logger boundary, and map secret-processing exceptions to constant non-sensitive messages at the recoverbull, mnemonic and API-key call sites, so shareable TSV logs never carry key material - #2609: scrub Sentry breadcrumbs before stack-frame vars and guard each step so an unmodifiable frame map cannot abort scrubbing and leak a partially minimized event - #2610: strip navigation breadcrumb messages and data (route arguments can carry transaction and wallet identifiers); only route names remain - #2611: gate native crash, watchdog and app-hang reporting on the user's error-reporting consent - #2643: rewrite the consent copy to describe exactly what minimized reports still contain instead of promising 'anonymized' reports Regression tests: test/security_audit/issue_2598_test.dart, issue_2609_test.dart, issue_2610_test.dart, issue_2611_test.dart, issue_2643_test.dart
Addresses the IMPORT findings from the security audit: - #2596: reject private extended-key version bytes at the watch-only import boundary so an xprv can never be imported as a watch-only wallet and later treated as public material - #2633: detect duplicate watch-only imports against ALL wallets and return a typed duplicate error instead of silently overwriting metadata - #2634: remove the orphaned seed when wallet creation fails after the seed was persisted, so a retry of the same mnemonic is not rejected as a duplicate - #2635: surface watch-only network mismatches against the active environment instead of silently importing on the wrong network - #2636: release the scanner latch on every non-success exit so an aborted derivation choice no longer disables further scans Regression tests: test/security_audit/issue_2596_test.dart, issue_2633_test.dart, issue_2634_test.dart, issue_2635_test.dart, issue_2636_test.dart
Addresses the LABELS findings from the security audit: - #2597: validate extended public key references against known public SLIP-132 versions so an xprv can never be stored or re-exported as an xpub label - #2605: make freeze imports opt-in and apply them only to outpoints the attributed wallet currently owns; unattributed records are dropped instead of entering the global frozen set - #2606: reject imported labels whose text matches a privileged system label so a crafted file cannot forge reserved labels - #2641: sanitize label content at the entity boundary (control characters stripped, length bounded) so imports can no longer bypass the note validator - #2642: bound label imports to 1 MiB and store records in one awaited atomic batch instead of unbounded sequential writes Regression tests: test/security_audit/issue_2597_test.dart, issue_2605_test.dart, issue_2606_test.dart, issue_2641_test.dart, issue_2642_test.dart
Addresses the swap/payment-parsing findings from the security audit: - #2603: bind pending-swap lookup to the wallet and swap type so a pending swap can never be reused across wallets - #2629: anchor the Lightning-address and LNURL regexes so a malformed Bitcoin URI can no longer fall through and be paid as a Lightning address - #2630: sanitize payment-request labels, messages and invoice descriptions (control characters stripped) before they reach stored transaction labels Regression tests: test/security_audit/issue_2603_test.dart, issue_2629_test.dart, issue_2630_test.dart
Addresses the SEND findings from the security audit: - #2593: clear the signed transaction whenever address, amount, fee or coin selection changes after signing, so a modified send can never broadcast a stale signature - #2594: reset sendMax when a new payment request is accepted so the drain path cannot fire on a stale request - #2595: surface the actual on-chain destination and amount in review instead of silently overriding the invoice (full magic-routing signature verification is upstream in boltz-dart; the issue stays open) - #2601: rebroadcast the same signed transaction on retry instead of rebuilding a new one that could double-pay - #2602: classify swap-limit load failures and keep Lightning sends recoverable during a provider outage - #2626: treat a non-positive exchange rate as unavailable so fiat conversion can never divide by zero during rendering - #2627: guard post-close emissions and complete durable bookkeeping right after broadcast so closing the screen mid-broadcast cannot lose the transaction record - #2628: show the net recipient amount (balance minus fee) on MAX confirmations instead of the full spendable balance - #2631: prune stale coin-control selections on UTXO refresh so spent or frozen coins cannot block transaction creation - #2632: map swap failures to typed errors instead of storing raw exception strings that leak payment metadata Regression tests: test/security_audit/issue_2593_test.dart through issue_2632_test.dart (SEND series)
Addresses the SEED/BROADCAST findings from the security audit: - #2600: combine original and signed PSBTs and finalize before extraction so SeedSigner trimmed PSBTs can actually be broadcast - #2616: never map an NFC PushTx URL launch to a successful broadcast; show a neutral opened-externally state and require HTTPS - #2617: fail signed-transaction review fetches when Tor is required instead of opening a direct socket - #2638: support origin-prefixed xpubs in the watch-only parser so SeedSigner static and Specter imports parse (user-facing scan diagnostics are tracked in the audit PR) - #2639: verify the Coldcard PushTx truncated SHA-256 checksum before review and reject conflicting network markers - #2654: fall back to the bitcoin_base finalization path when BDK extraction fails on finalized PSBTs stripped of UTXO metadata - #2655: reject parent transactions whose computed id differs from the request and treat impossible fees as unknown instead of displaying attacker-controlled values - #2656: bound Electrum reads with a read timeout mapped to a transient server failure so review cannot hang indefinitely Regression tests: test/security_audit/issue_2600_test.dart through issue_2656_test.dart (SEED/BROADCAST series)
Addresses the TXUI findings from the security audit: - #2624: exchange labels are written only from an explicit order-completion event through the new TransactionsFacade, wired to the buy and sell completion paths — never from history reads; only completed buy/sell orders are labeled - #2625: neutralize CSV cells starting with =, +, -, @, tab or CR so server-provided swap strings cannot inject spreadsheet formulas - #2663: validate exchange-order binding against on-chain address, amount and direction instead of the server-provided tx id alone - #2664: validate swap binding against the wallet and amount, and deduplicate by verified on-chain identity instead of server ids - #2665: map aggregation failures to a sealed TransactionFailure with a generic localized message instead of rendering raw exception text Regression tests: test/security_audit/issue_2624_test.dart, issue_2625_test.dart, issue_2663_test.dart, issue_2664_test.dart, issue_2665_test.dart
Adds the audit reproducers for the BITBOX findings that cannot be fixed in this repository — they live in the bull_sdk Rust/Kotlin bridge and the issues stay open until fixed upstream: - #2615: connected devices are not cryptographically attested - #2647: pairing failures are swallowed and misreported - #2648: Android USB permission returns before the user responds - #2649: unsupported P2WSH signing panics via todo!() - #2651: abandoned operations hold the device lock up to 60s
bfd3e38 to
8e128da
Compare
The shared Flutter setup action used fvm.app's installer, which still hardcodes the legacy leoafarias/fvm repository. On arm64 that redirect returned HTTP 503 repeatedly and prevented integration jobs from reaching any build or test step. Download the pinned 4.1.2 archive directly from conceptadev/fvm, select x64 or arm64 from uname, retry transient release-asset failures, and validate the installed binary. This matches the direct installer already used by the App Store workflow. Refs #2669
The shared Flutter setup action used fvm.app's installer, which still hardcodes the legacy leoafarias/fvm repository. On arm64 that redirect returned HTTP 503 repeatedly and prevented integration jobs from reaching any build or test step. Download the pinned 4.1.2 archive directly from conceptadev/fvm, select x64 or arm64 from uname, retry transient release-asset failures, and validate the installed binary. This matches the direct installer already used by the App Store workflow. Refs #2669
8e128da to
8b9f2c2
Compare
Addresses the 73 security findings filed in issues #2593–#2665
Every finding was first reproduced by a dedicated test (
test/security_audit/issue_<N>_test.dart) asserting the vulnerable behavior — zero false positives, zero already-fixed. Each fix ships with its test flipped into a regression test asserting the secure behavior.Closesreferences belowbull_sdkBitBox bridge / boltz-dart → kept open, documented intest(bitbox): document the upstream bridge findingsCommits (one per area, each with its regression tests)
fix(ios)fix(android)fix(qr)fix(bip85)fix(bitbox)fix(fees)fix(mempool)fix(log)fix(import)fix(labels)fix(swaps)fix(send)fix(broadcast)fix(transactions)test(bitbox)Highlights
TransactionsFacade, wired to the buy and sell completion paths (Security (Medium, TXUI F-1): exchange history writes unverified privileged labels #2624) —FEATURES.mdgraph updated accordingly.Partial fixes (issues kept open)
scan_watch_only_screen.dart.Upstream (issues kept open, fixes belong to bull_sdk / boltz-dart)
#2615 (device attestation), #2647 (pairing error propagation), #2648 (Android USB permission), #2649 (P2WSH
todo!()panic), #2651 (device lock on abandoned operations).Test plan
make analyze(--fatal-warnings --fatal-infos, CI-equivalent): No issues foundmake unit-test: full suite green (including the 73 regression tests intest/security_audit/)make translationsrun; one new l10n key (mempoolErrorNetworkMismatch, en/fr/es)Closes
Closes #2593, closes #2594, closes #2596, closes #2597, closes #2598, closes #2599, closes #2600, closes #2601, closes #2602, closes #2603, closes #2605, closes #2606, closes #2607, closes #2608, closes #2609, closes #2610, closes #2611, closes #2612, closes #2613, closes #2614, closes #2616, closes #2617, closes #2618, closes #2619, closes #2621, closes #2623, closes #2624, closes #2625, closes #2626, closes #2627, closes #2628, closes #2629, closes #2630, closes #2631, closes #2632, closes #2633, closes #2634, closes #2635, closes #2636, closes #2637, closes #2638, closes #2639, closes #2640, closes #2641, closes #2642, closes #2643, closes #2645, closes #2650, closes #2652, closes #2653, closes #2654, closes #2655, closes #2656, closes #2657, closes #2658, closes #2659, closes #2660, closes #2662, closes #2663, closes #2664, closes #2665
Refs #2595, refs #2604, refs #2620, refs #2622, refs #2644, refs #2646, refs #2661, refs #2615, refs #2647, refs #2648, refs #2649, refs #2651