|
| 1 | +--- |
| 2 | +layout: advisory |
| 3 | +title: 'CVE-2026-40069 (bsv-sdk): bsv-sdk ARC broadcaster treats INVALID/MALFORMED/ORPHAN |
| 4 | + responses as successful broadcasts' |
| 5 | +comments: false |
| 6 | +categories: |
| 7 | +- bsv-sdk |
| 8 | +advisory: |
| 9 | + gem: bsv-sdk |
| 10 | + cve: 2026-40069 |
| 11 | + ghsa: 9hfr-gw99-8rhx |
| 12 | + url: https://github.com/sgbett/bsv-ruby-sdk/security/advisories/GHSA-9hfr-gw99-8rhx |
| 13 | + title: bsv-sdk ARC broadcaster treats INVALID/MALFORMED/ORPHAN responses as successful |
| 14 | + broadcasts |
| 15 | + date: 2026-04-09 |
| 16 | + description: |- |
| 17 | + # ARC broadcaster treats failure statuses as successful broadcasts |
| 18 | +
|
| 19 | + ## Summary |
| 20 | +
|
| 21 | + `BSV::Network::ARC`'s failure detection only recognises `REJECTED` |
| 22 | + and `DOUBLE_SPEND_ATTEMPTED`. ARC responses with `txStatus` values |
| 23 | + of `INVALID`, `MALFORMED`, `MINED_IN_STALE_BLOCK`, or any |
| 24 | + `ORPHAN`-containing `extraInfo` / `txStatus` are silently treated |
| 25 | + as successful broadcasts. Applications that gate actions on broadcaster |
| 26 | + success are tricked into trusting transactions that were never |
| 27 | + accepted by the network. |
| 28 | +
|
| 29 | + ## Details |
| 30 | +
|
| 31 | + `lib/bsv/network/arc.rb` (lines ~74-100 in the affected code) uses a |
| 32 | + narrow failure predicate compared to the TypeScript reference SDK. |
| 33 | + The TS broadcaster additionally recognises: |
| 34 | +
|
| 35 | + - `INVALID` |
| 36 | + - `MALFORMED` |
| 37 | + - `MINED_IN_STALE_BLOCK` |
| 38 | + - Any response containing `ORPHAN` in `extraInfo` or `txStatus` |
| 39 | +
|
| 40 | + The Ruby implementation omits all of these, so ARC responses |
| 41 | + carrying any of these statuses are returned to the caller as |
| 42 | + successful broadcasts. |
| 43 | +
|
| 44 | + Additional divergences in the same module compound the risk: |
| 45 | +
|
| 46 | + - `Content-Type` is sent as `application/octet-stream`; the TS |
| 47 | + reference sends `application/json` with a `{ rawTx: <hex> }` |
| 48 | + body (EF form where source transactions are available). |
| 49 | + - The headers `XDeployment-ID`, `X-CallbackUrl`, and `X-CallbackToken` |
| 50 | + are not sent. |
| 51 | +
|
| 52 | + The immediate security-relevant defect is the missing failure |
| 53 | + statuses; the other divergences are fixed in the same patch for |
| 54 | + protocol compliance. |
| 55 | +
|
| 56 | + ## Impact |
| 57 | +
|
| 58 | + Integrity: callers receive a success response for broadcasts that |
| 59 | + were actually rejected by the ARC endpoint. Applications and |
| 60 | + downstream gems that gate actions on broadcaster success — releasing |
| 61 | + goods, marking invoices paid, treating a token as minted, progressing |
| 62 | + a workflow — are tricked into trusting transactions that were never broadcast. |
| 63 | +
|
| 64 | + This is an integrity bug with security consequences. It does not |
| 65 | + disclose information (confidentiality unaffected) and does not |
| 66 | + affect availability. |
| 67 | +
|
| 68 | + ## CVSS rationale |
| 69 | +
|
| 70 | + `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N` → **7.5 (High)** |
| 71 | +
|
| 72 | + - **AV:N** — network-reachable. |
| 73 | + - **AC:L** — no specialised access conditions are required. Triggering |
| 74 | + any of the unhandled failure statuses is not meaningfully harder |
| 75 | + than broadcasting a transaction at all: a malformed or invalid |
| 76 | + transaction, an orphan condition from a transient fork, or a |
| 77 | + hostile/misbehaving ARC endpoint returning one of these statuses |
| 78 | + is sufficient. The attacker does not need to defeat any mitigation |
| 79 | + or race a specific window — the bug is that the code path doesn't |
| 80 | + exist at all. |
| 81 | + - **PR:N** — no privileges required. |
| 82 | + - **UI:N** — no user interaction. |
| 83 | + - **C:N** — no confidentiality impact. |
| 84 | + - **I:H** — downstream integrity decisions are taken on |
| 85 | + non-broadcast transactions. |
| 86 | + - **A:N** — no availability impact. |
| 87 | +
|
| 88 | + ## Affected versions |
| 89 | +
|
| 90 | + The ARC broadcaster was introduced in commit `a1f2e62` ("feat(network): |
| 91 | + add ARC broadcaster with injectable HTTP client") on 2026-02-08 and |
| 92 | + first released in **v0.1.0**. The narrow failure predicate has been |
| 93 | + present since introduction. Every release up to and including **v0.8.1** |
| 94 | + is affected. |
| 95 | +
|
| 96 | + Affected range: `>= 0.1.0, < 0.8.2`. |
| 97 | +
|
| 98 | + ## Patches |
| 99 | +
|
| 100 | + Upgrade to `bsv-sdk >= 0.8.2`. The fix: |
| 101 | +
|
| 102 | + - Expands the failure predicate (`REJECTED_STATUSES` + `ORPHAN` |
| 103 | + substring check on both `txStatus` and `extraInfo`) to include |
| 104 | + `INVALID`, `MALFORMED`, `MINED_IN_STALE_BLOCK`, and any |
| 105 | + orphan-containing response, matching the TypeScript reference. |
| 106 | + - Switches `Content-Type` to `application/json` with a `{ rawTx: <hex> }` |
| 107 | + body, preferring Extended Format (BRC-30) hex when every input has |
| 108 | + `source_satoshis` and `source_locking_script` populated and falling |
| 109 | + back to plain raw-tx hex otherwise. |
| 110 | + - Adds support for the `XDeployment-ID` (default: random |
| 111 | + `bsv-ruby-sdk-<hex>`), `X-CallbackUrl`, and `X-CallbackToken` |
| 112 | + headers via new constructor keyword arguments. |
| 113 | +
|
| 114 | + Fixed in sgbett/bsv-ruby-sdk#306. |
| 115 | +
|
| 116 | + ### Note for `bsv-wallet` consumers |
| 117 | +
|
| 118 | + The sibling gem `bsv-wallet` (published from the same repository) is |
| 119 | + not independently vulnerable — `lib/bsv/network/arc.rb` is not bundled |
| 120 | + into the wallet gem's `files` list. However, `bsv-wallet` runtime-depends |
| 121 | + on `bsv-sdk`, so a consumer of `bsv-wallet` that also invokes the |
| 122 | + ARC broadcaster is transitively exposed whenever `Gemfile.lock` |
| 123 | + resolves to a vulnerable `bsv-sdk` version. `bsv-wallet >= 0.3.4` |
| 124 | + tightens its `bsv-sdk` constraint to `>= 0.8.2, < 1.0`, so upgrading |
| 125 | + either gem is sufficient to pull in the fix. |
| 126 | +
|
| 127 | + ## Workarounds |
| 128 | +
|
| 129 | + If upgrading is not immediately possible: |
| 130 | +
|
| 131 | + - Verify broadcast results out-of-band (e.g. query a block explorer |
| 132 | + or WhatsOnChain) before treating a transaction as broadcast. |
| 133 | + - Do not gate integrity-critical actions solely on the ARC |
| 134 | + broadcaster's success response. |
| 135 | +
|
| 136 | + ## Credit |
| 137 | +
|
| 138 | + Identified during the 2026-04-08 cross-SDK compliance review, |
| 139 | + tracked as finding F5.13. |
| 140 | + cvss_v3: 7.5 |
| 141 | + unaffected_versions: |
| 142 | + - "< 0.1.0" |
| 143 | + patched_versions: |
| 144 | + - ">= 0.8.2" |
| 145 | + related: |
| 146 | + url: |
| 147 | + - https://nvd.nist.gov/vuln/detail/CVE-2026-40069 |
| 148 | + - https://github.com/sgbett/bsv-ruby-sdk/releases/tag/v0.8.2 |
| 149 | + - https://github.com/sgbett/bsv-ruby-sdk/pull/306 |
| 150 | + - https://github.com/sgbett/bsv-ruby-sdk/commit/4992e8a265fd914a7eeb0405c69d1ff0122a84cc |
| 151 | + - https://github.com/sgbett/bsv-ruby-sdk/issues/305 |
| 152 | + - https://github.com/sgbett/bsv-ruby-sdk/security/advisories/GHSA-9hfr-gw99-8rhx |
| 153 | + - https://advisories.gitlab.com/pkg/gem/bsv-sdk/CVE-2026-40069 |
| 154 | + - https://github.com/advisories/GHSA-9hfr-gw99-8rhx |
| 155 | +--- |
0 commit comments