Skip to content

Commit 98c4838

Browse files
committed
GHSA/SYNC: 3 brand new advisories
1 parent b1e3c15 commit 98c4838

File tree

3 files changed

+621
-0
lines changed

3 files changed

+621
-0
lines changed

gems/bsv-sdk/CVE-2026-40069.yml

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

gems/bsv-sdk/CVE-2026-40070.yml

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
---
2+
gem: bsv-sdk
3+
cve: 2026-40070
4+
ghsa: hc36-c89j-5f4j
5+
url: https://github.com/sgbett/bsv-ruby-sdk/security/advisories/GHSA-hc36-c89j-5f4j
6+
title: bsv-sdk and bsv-wallet persist unverified certifier signatures
7+
in acquire_certificate (direct and issuance paths)
8+
date: 2026-04-09
9+
description: |
10+
# Unverified certifier signatures persisted by `acquire_certificate`
11+
12+
## Affected packages
13+
14+
Both `bsv-sdk` and `bsv-wallet` are published from the
15+
[sgbett/bsv-ruby-sdk](https://github.com/sgbett/bsv-ruby-sdk)
16+
repository. The vulnerable code lives in
17+
`lib/bsv/wallet_interface/wallet_client.rb`, which is **physically
18+
shipped inside both gems** (the `bsv-wallet.gemspec` `files` list
19+
bundles the entire `lib/bsv/wallet_interface/` tree). Consumers of
20+
either gem are independently vulnerable; the two packages are
21+
versioned separately, so each has its own affected range.
22+
23+
| Package | Affected | Patched |
24+
| --- | --- | --- |
25+
| `bsv-sdk` | `>= 0.3.1, < 0.8.2` | `0.8.2` |
26+
| `bsv-wallet` | `>= 0.1.2, < 0.3.4` | `0.3.4` |
27+
28+
## Summary
29+
30+
`BSV::Wallet::WalletClient#acquire_certificate` persists certificate
31+
records to storage **without verifying the certifier's signature**
32+
over the certificate contents. Both acquisition paths are affected:
33+
34+
- `acquisition_protocol: 'direct'` — the caller supplies all certificate
35+
fields (including `signature:`) and the record is written to storage verbatim.
36+
- `acquisition_protocol: 'issuance'` — the client POSTs to a certifier
37+
URL and writes whatever signature the response body contains, also
38+
without verification.
39+
40+
An attacker who can reach either API (or who controls a certifier
41+
endpoint targeted by the issuance path) can forge identity certificates
42+
that subsequently appear authentic to `list_certificates` and `prove_certificate`.
43+
44+
## Details
45+
46+
BRC-52 requires a certificate's `signature` field to be verified against
47+
the claimed certifier's public key over a canonical hashing of
48+
`(type, subject, serialNumber, revocationOutpoint, fields)` before
49+
the certificate is trusted. The reference TypeScript SDK enforces
50+
this in `Certificate.verify()`.
51+
52+
### Direct path
53+
54+
The Ruby implementation's `acquire_via_direct` path
55+
(`lib/bsv/wallet_interface/wallet_client.rb`) constructs the certificate
56+
record directly from caller-supplied fields:
57+
58+
```ruby
59+
def acquire_via_direct(args)
60+
{
61+
type: args[:type],
62+
subject: @key_deriver.identity_key,
63+
serial_number: args[:serial_number],
64+
certifier: args[:certifier],
65+
revocation_outpoint: args[:revocation_outpoint],
66+
signature: args[:signature],
67+
fields: args[:fields],
68+
keyring: args[:keyring_for_subject]
69+
}
70+
end
71+
```
72+
73+
The returned record is then written to the storage adapter by
74+
`acquire_certificate`. No verification of `args[:signature]` against
75+
`args[:certifier]`'s public key occurs at any point in this path.
76+
77+
### Issuance path
78+
79+
`acquire_via_issuance` POSTs to a certifier-supplied URL and parses
80+
the response body into a certificate record, which is then written
81+
to storage without verifying the returned signature. A hostile or
82+
compromised certifier endpoint — or anyone able to redirect/MITM the
83+
plain HTTP request — can therefore return an arbitrary `signature`
84+
value for any subject and have it stored as authentic. This is the
85+
same class of bypass as the direct path; it was tracked separately
86+
as finding **F8.16** in the compliance review and is closed by the same fix.
87+
88+
### Downstream impact
89+
90+
Downstream reads via `list_certificates` and selective-disclosure via
91+
`prove_certificate` treat stored records as valid without re-verifying,
92+
so any forgery that slips past `acquire_certificate` is trusted permanently.
93+
94+
## Impact
95+
96+
Any caller that can invoke `acquire_certificate` — via either
97+
acquisition protocol — can forge a certificate attributed to an
98+
arbitrary certifier identity key, containing arbitrary fields, and
99+
have it persisted as authentic. Applications and downstream gems
100+
that rely on the wallet's certificate store as a source of truth
101+
for identity attributes (e.g. KYC assertions, role claims,
102+
attestations) are subject to credential forgery.
103+
104+
This is a credential-forgery primitive, not merely a spec
105+
divergence from BRC-52.
106+
107+
## CVSS rationale
108+
109+
`AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N` → **8.1 (High)**
110+
111+
- **AV:N** — network-reachable in any wallet context that
112+
exposes `acquire_certificate` to callers.
113+
- **AC:L** — low attack complexity: pass arbitrary bytes as `signature:`.
114+
- **PR:L** — low privileges: any caller authorised to invoke
115+
`acquire_certificate`.
116+
- **UI:N** — no user interaction required.
117+
- **C:H** — forged credentials via `prove_certificate` can assert
118+
attributes about the subject.
119+
- **I:H** — the wallet's credential store is polluted with
120+
attacker-controlled data.
121+
- **A:N** — availability unaffected.
122+
123+
## Proof of concept
124+
125+
```ruby
126+
client = BSV::Wallet::WalletClient.new(key,
127+
storage: BSV::Wallet::MemoryStore.new)
128+
129+
client.acquire_certificate(
130+
type: 'age-over-18',
131+
acquisition_protocol: 'direct',
132+
certifier: claimed_trusted_pubkey_hex,
133+
serial_number: 'any-serial',
134+
revocation_outpoint: ('00' * 32) + '.0',
135+
signature: 'deadbeef' * 16, # arbitrary bytes — never verified
136+
fields: { 'verified' => 'true' },
137+
keyring_for_subject: {}
138+
)
139+
140+
client.list_certificates(
141+
certifiers: [claimed_trusted_pubkey_hex],
142+
types: ['age-over-18']
143+
)
144+
# => returns the forged record as if it were a real
145+
# certificate from that certifier
146+
```
147+
148+
## Affected versions
149+
150+
The vulnerable direct-path code was introduced in commit `d14dd19`
151+
("feat(wallet): implement BRC-100 identity certificate methods
152+
(Phase 5)") on 2026-03-27 20:35 UTC. The vulnerable issuance-path
153+
code was added one day later in `6a4d898` ("feat(wallet): implement
154+
certificate issuance protocol", 2026-03-28 04:38 UTC), which removed
155+
an earlier `raise UnsupportedActionError` and replaced it with an
156+
unverified HTTP POST.
157+
158+
**`bsv-sdk`:** the v0.3.1 chore bump (`89de3a2`) was committed
159+
28 minutes after `d14dd19`, so the direct-path bypass shipped in
160+
the **v0.3.1** tag. The v0.3.1 release raised `UnsupportedActionError`
161+
for the issuance path, so the issuance-path bypass first shipped in
162+
**v0.3.2** (`5a335de`). Every subsequent release up to and including
163+
**v0.8.1** is affected by at least one path, and every release
164+
from v0.3.2 onwards is affected by both.
165+
Combined affected range: `>= 0.3.1, < 0.8.2`.
166+
167+
**`bsv-wallet`:** at the time both commits landed, the wallet gem
168+
was at version 0.1.1. The first wallet release containing any of
169+
the vulnerable code was **v0.1.2** (`5a335de`, 2026-03-30), which
170+
shipped both paths simultaneously. Every subsequent release up to
171+
and including **v0.3.3** is affected on both paths.
172+
Affected range: `>= 0.1.2, < 0.3.4`.
173+
174+
## Patches
175+
176+
Upgrade to `bsv-sdk >= 0.8.2` **and/or** `bsv-wallet >= 0.3.4`.
177+
Both releases ship the same fix: a new module
178+
`BSV::Wallet::CertificateSignature`
179+
(`lib/bsv/wallet_interface/certificate_signature.rb`), which builds
180+
the BRC-52 canonical preimage (`type`, `serial_number`, `subject`,
181+
`certifier`, `revocation_outpoint`, lexicographically-sorted `fields`)
182+
and verifies the certifier's signature against it via
183+
`ProtoWallet#verify_signature` with protocol ID `[2, 'certificate signature']`
184+
and counterparty = the claimed certifier's public key. Both
185+
`acquire_via_direct` and `acquire_via_issuance` now call
186+
`CertificateSignature.verify!` before returning the certificate to
187+
`acquire_certificate`, so invalid certificates raise
188+
`BSV::Wallet::CertificateSignature::InvalidError` (a subclass of
189+
`InvalidSignatureError`) and are never written to storage.
190+
191+
Consumers should upgrade whichever gem they depend on directly; they
192+
do not need both. `bsv-wallet 0.3.4` additionally tightens its
193+
dependency on `bsv-sdk` from the stale `~> 0.4` to `>= 0.8.2, < 1.0`,
194+
which forces the known-good pairing and pulls in the sibling
195+
advisory fixes (F1.3, F5.13) tracked separately.
196+
197+
The issuance-path fix also partially closes finding **F8.16**
198+
from the same compliance review. F8.16's second aspect — switching
199+
the issuance transport from ad-hoc JSON POST to BRC-104 AuthFetch —
200+
is not addressed here and remains deferred to a future release.
201+
202+
Fixed in sgbett/bsv-ruby-sdk#306.
203+
204+
## Workarounds
205+
206+
If upgrading is not immediately possible:
207+
208+
- Do not expose `acquire_certificate` (either acquisition
209+
protocol) to untrusted callers.
210+
- Do not invoke `acquire_certificate` with `acquisition_protocol:
211+
'issuance'` against a certifier URL you do not fully trust, and
212+
require TLS for any such request.
213+
- Treat any record returned by `list_certificates` / `prove_certificate`
214+
as unverified and perform an out-of-band BRC-52 verification against
215+
the certifier's public key before acting on it.
216+
217+
## Credit
218+
219+
Identified during the 2026-04-08 cross-SDK compliance review, tracked
220+
as findings F8.15 (direct path) and F8.16 (issuance path, partial).
221+
222+
cvss_v3: 8.1
223+
unaffected_versions:
224+
- "< 0.3.1"
225+
patched_versions:
226+
- ">= 0.8.2"
227+
related:
228+
url:
229+
- https://nvd.nist.gov/vuln/detail/CVE-2026-40070
230+
- https://github.com/sgbett/bsv-ruby-sdk/security/advisories/GHSA-hc36-c89j-5f4j
231+
- https://github.com/sgbett/bsv-ruby-sdk/pull/306
232+
- https://github.com/sgbett/bsv-ruby-sdk/commit/4992e8a265fd914a7eeb0405c69d1ff0122a84cc
233+
- https://github.com/sgbett/bsv-ruby-sdk/issues/305
234+
- https://bsv.brc.dev/peer-to-peer/0052
235+
- https://advisories.gitlab.com/pkg/gem/bsv-sdk/CVE-2026-40070/
236+
- https://github.com/advisories/GHSA-hc36-c89j-5f4j

0 commit comments

Comments
 (0)