Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
💎
💎 RubyGems
Not in CISA KEV
HIGH severity

CVE-2026-40070 — bsv-sdk

HIGHFix: sgbett/bsv-ruby-sdk@4992e8a

CVE-2026-40070 is a high-severity (CVSS 8.1) CWE-347 vulnerability in bsv-sdk. A fix is available for bsv-sdk — see the affected versions and patch details below.

bsv-sdk and bsv-wallet persist unverified certifier signatures in acquire_certificate (direct and issuance paths)

Also known asGHSA-hc36-c89j-5f4j
Published
Apr 9, 2026
Updated
Aug 12, 2026
Affected
2 pkgs
Patched
2 / 2
Exploits
None indexed
Exploitation data as of Sep 21, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-40070.

EPSS Exploitation Probability

via FIRST.org ↗
0.1%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs3th percentile — riskier than 3% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

CVE-2026-40070 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 378,156 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

2 pkgs affected
💎bsv-sdk💎bsv-wallet

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects RubyGems packages — download data is not available via public APIs for these ecosystems.

Description

Unverified certifier signatures persisted by acquire_certificate

Affected packages

Both bsv-sdk and bsv-wallet are published from the sgbett/bsv-ruby-sdk repository. The vulnerable code lives in lib/bsv/wallet_interface/wallet_client.rb, which is physically shipped inside both gems (the bsv-wallet.gemspec files list bundles the entire lib/bsv/wallet_interface/ tree). Consumers of either gem are independently vulnerable; the two packages are versioned separately, so each has its own affected range.

PackageAffectedPatched
bsv-sdk>= 0.3.1, < 0.8.20.8.2
bsv-wallet>= 0.1.2, < 0.3.40.3.4

Summary

BSV::Wallet::WalletClient#acquire_certificate persists certificate records to storage without verifying the certifier's signature over the certificate contents. Both acquisition paths are affected:

  • acquisition_protocol: 'direct' — the caller supplies all certificate fields (including signature:) and the record is written to storage verbatim.
  • acquisition_protocol: 'issuance' — the client POSTs to a certifier URL and writes whatever signature the response body contains, also without verification.

An attacker who can reach either API (or who controls a certifier endpoint targeted by the issuance path) can forge identity certificates that subsequently appear authentic to list_certificates and prove_certificate.

Details

BRC-52 requires a certificate's signature field to be verified against the claimed certifier's public key over a canonical hashing of (type, subject, serialNumber, revocationOutpoint, fields) before the certificate is trusted. The reference TypeScript SDK enforces this in Certificate.verify().

Direct path

The Ruby implementation's acquire_via_direct path (lib/bsv/wallet_interface/wallet_client.rb) constructs the certificate record directly from caller-supplied fields:

def acquire_via_direct(args)
  {
    type: args[:type],
    subject: @key_deriver.identity_key,
    serial_number: args[:serial_number],
    certifier: args[:certifier],
    revocation_outpoint: args[:revocation_outpoint],
    signature: args[:signature],
    fields: args[:fields],
    keyring: args[:keyring_for_subject]
  }
end

The returned record is then written to the storage adapter by acquire_certificate. No verification of args[:signature] against args[:certifier]'s public key occurs at any point in this path.

Issuance path

acquire_via_issuance POSTs to a certifier-supplied URL and parses the response body into a certificate record, which is then written to storage without verifying the returned signature. A hostile or compromised certifier endpoint — or anyone able to redirect/MITM the plain HTTP request — can therefore return an arbitrary signature value for any subject and have it stored as authentic. This is the same class of bypass as the direct path; it was tracked separately as finding F8.16 in the compliance review and is closed by the same fix.

Downstream impact

Downstream reads via list_certificates and selective-disclosure via prove_certificate treat stored records as valid without re-verifying, so any forgery that slips past acquire_certificate is trusted permanently.

Impact

Any caller that can invoke acquire_certificate — via either acquisition protocol — can forge a certificate attributed to an arbitrary certifier identity key, containing arbitrary fields, and have it persisted as authentic. Applications and downstream gems that rely on the wallet's certificate store as a source of truth for identity attributes (e.g. KYC assertions, role claims, attestations) are subject to credential forgery.

This is a credential-forgery primitive, not merely a spec divergence from BRC-52.

CVSS rationale

AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N → 8.1 (High)

  • AV:N — network-reachable in any wallet context that exposes acquire_certificate to callers.
  • AC:L — low attack complexity: pass arbitrary bytes as signature:.
  • PR:L — low privileges: any caller authorised to invoke acquire_certificate.
  • UI:N — no user interaction required.
  • C:H — forged credentials via prove_certificate can assert attributes about the subject.
  • I:H — the wallet's credential store is polluted with attacker-controlled data.
  • A:N — availability unaffected.

Proof of concept

client = BSV::Wallet::WalletClient.new(key, storage: BSV::Wallet::MemoryStore.new)

client.acquire_certificate(
  type: 'age-over-18',
  acquisition_protocol: 'direct',
  certifier: claimed_trusted_pubkey_hex,
  serial_number: 'any-serial',
  revocation_outpoint: ('00' * 32) + '.0',
  signature: 'deadbeef' * 16,       # arbitrary bytes — never verified
  fields: { 'verified' => 'true' },
  keyring_for_subject: {}
)

client.list_certificates(
  certifiers: [claimed_trusted_pubkey_hex],
  types: ['age-over-18']
)
# => returns the forged record as if it were a real certificate from that certifier

Affected versions

The vulnerable direct-path code was introduced in commit d14dd19 ("feat(wallet): implement BRC-100 identity certificate methods (Phase 5)") on 2026-03-27 20:35 UTC. The vulnerable issuance-path code was added one day later in 6a4d898 ("feat(wallet): implement certificate issuance protocol", 2026-03-28 04:38 UTC), which removed an earlier raise UnsupportedActionError and replaced it with an unverified HTTP POST.

bsv-sdk: the v0.3.1 chore bump (89de3a2) was committed 28 minutes after d14dd19, so the direct-path bypass shipped in the v0.3.1 tag. The v0.3.1 release raised UnsupportedActionError for the issuance path, so the issuance-path bypass first shipped in v0.3.2 (5a335de). Every subsequent release up to and including v0.8.1 is affected by at least one path, and every release from v0.3.2 onwards is affected by both. Combined affected range: >= 0.3.1, < 0.8.2.

bsv-wallet: at the time both commits landed, the wallet gem was at version 0.1.1. The first wallet release containing any of the vulnerable code was v0.1.2 (5a335de, 2026-03-30), which shipped both paths simultaneously. Every subsequent release up to and including v0.3.3 is affected on both paths. Affected range: >= 0.1.2, < 0.3.4.

Patches

Upgrade to bsv-sdk >= 0.8.2 and/or bsv-wallet >= 0.3.4. Both releases ship the same fix: a new module BSV::Wallet::CertificateSignature (lib/bsv/wallet_interface/certificate_signature.rb), which builds the BRC-52 canonical preimage (type, serial_number, subject, certifier, revocation_outpoint, lexicographically-sorted fields) and verifies the certifier's signature against it via ProtoWallet#verify_signature with protocol ID [2, 'certificate signature'] and counterparty = the claimed certifier's public key. Both acquire_via_direct and acquire_via_issuance now call CertificateSignature.verify! before returning the certificate to acquire_certificate, so invalid certificates raise BSV::Wallet::CertificateSignature::InvalidError (a subclass of InvalidSignatureError) and are never written to storage.

Consumers should upgrade whichever gem they depend on directly; they do not need both. bsv-wallet 0.3.4 additionally tightens its dependency on bsv-sdk from the stale ~> 0.4 to >= 0.8.2, < 1.0, which forces the known-good pairing and pulls in the sibling advisory fixes (F1.3, F5.13) tracked separately.

The issuance-path fix also partially closes finding F8.16 from the same compliance review. F8.16's second aspect — switching the issuance transport from ad-hoc JSON POST to BRC-104 AuthFetch — is not addressed here and remains deferred to a future release.

Fixed in sgbett/bsv-ruby-sdk#306.

Workarounds

If upgrading is not immediately possible:

  • Do not expose acquire_certificate (either acquisition protocol) to untrusted callers.
  • Do not invoke acquire_certificate with acquisition_protocol: 'issuance' against a certifier URL you do not fully trust, and require TLS for any such request.
  • Treat any record returned by list_certificates / prove_certificate as unverified and perform an out-of-band BRC-52 verification against the certifier's public key before acting on it.

Credit

Identified during the 2026-04-08 cross-SDK compliance review, tracked as findings F8.15 (direct path) and F8.16 (issuance path, partial).

References

Affected Packages

2 total 2 fixed
EcosystemPackageVulnerable rangeFix
💎RubyGemsbsv-sdk≥ 0.3.1&&< 0.8.20.8.2bundle update bsv-sdk --conservative
💎RubyGemsbsv-wallet≥ 0.1.2&&< 0.3.40.3.4bundle update bsv-wallet --conservative

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for bsv-sdk, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update bsv-sdk to 0.8.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-40070 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-40070 can be triaged on real exposure rather than presence alone.

Tailored to CVE-2026-40070. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

# Unverified certifier signatures persisted by `acquire_certificate` ## Affected packages Both `bsv-sdk` and `bsv-wallet` are published from the [sgbett/bsv-ruby-sdk](https://github.com/sgbett/bsv-ruby-sdk) repository. The vulnerable code lives in `lib/bsv/wallet_interface/wallet_client.rb`, which is **physically shipped inside both gems** (the `bsv-wallet.gemspec` `files` list bundles the entire `lib/bsv/wallet_interface/` tree). Consumers of either gem are independently vulnerable; the two packages are versioned separately, so each has its own affected range. | Package | Affected | Patche
O3 Security · Impact-Aware SCA

Is CVE-2026-40070 in your dependencies?

O3 Security finds CVE-2026-40070 across RubyGems dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

CVE-2026-40070: bsv-sdk (High 8.1) | O3 Security