Skip to content
Quantix Security Labs

Risk summary

State at remediation commit de6deb2f8980

Findings
7
Critical and high
2
Open or partial
0
Remediation
100%

Findings by severity

  • Critical0
  • High2
  • Medium2
  • Low2
  • Informational1

Remediation statusAnalysis

  • Resolved6
  • Partially resolved0
  • Acknowledged1
  • Open0

Larkspur Wallet SDK

Wallet Infrastructure

Document
QX-TSA-2026-004 · v1.1
Type
Penetration Test
Review
17 Aug 2026 – 28 Aug 2026
Published
9 Sept 2026
Ecosystems
Cosmos
Languages
TypeScript, Go
Risk Index
A81.4
Assessment contents · 12 sections

01

Assessment overview

Larkspur is a signing SDK that lets Cosmos dApps request signatures from wallets, with a Go relay that pairs a dApp session to a wallet and forwards encrypted messages between them. A wallet SDK sits on the path between what a user sees and what they sign, so the test concentrated on whether a malicious or compromised party can make those two diverge.

Seven findings were reported. The two high-severity issues were a human-readable transaction summary that was built from caller-supplied metadata instead of the bytes being signed, and short pairing identifiers that could be guessed and were not bound to the requesting origin. Together they could let a hostile page present one transaction and obtain a signature for another.

All high and medium findings were fixed and re-tested against the remediation commit. Two low findings were fixed; an informational note about development dependencies was acknowledged.

02

Technical analysis: protocol architecture

A dApp imports the SDK and calls requestSignature with a transaction. The SDK serialises the transaction into an Amino JSON or Protobuf SignDoc, produces a human-readable summary, and sends both to the wallet through the pairing relay.

Pairing starts when the dApp asks the relay for a pairing ID, shows it as a QR code or deep link, and the wallet joins with the same ID. After a key exchange, messages are end-to-end encrypted; the relay sees only ciphertext and routing metadata.

Chain metadata — chain IDs, fee denominations and RPC endpoints — is fetched by the connector from a registry service and cached in the browser.

Trusted· 3

Assumed to behave as specified; compromise is out of the threat model.

  • Signing SDKBuilds sign requests and display summaries; verifies signatures
  • Wallet connectorSession lifecycle and chain registry client
  • Wallet applicationDisplays the request and signs
trust boundary

Semi-trusted· 3

Trusted for liveness or within bounds; misbehaviour is partly modelled.

  • Pairing relayPairs sessions and forwards encrypted messages
  • Chain registryServes chain metadata to the connector
  • Staging cloudBuild pipeline and relay hosting
trust boundary

Untrusted· 1

May act adversarially; every input is validated.

  • Integrating dApp pageCalls the SDK; may be malicious or compromised
Components grouped by trust level. Calls that cross a trust boundary are where the review concentrated input validation and authorisation checks.

Trust assumptions

  • The relay is not trusted with message content; end-to-end encryption must hold even if the relay is compromised.
  • The wallet displays what the SDK sends; the SDK must therefore derive every displayed value from the signed bytes.
  • Integrating pages may be hostile, so origin must be bound into pairing and session state.

03

Scope

Signing SDK, wallet connector, pairing relay API and the staging environment that hosts it.

Repository
github.com/example-org/larkspur-wallet
Review effort
Two testers, ten working days (source-assisted)
Reviewed commit
ba74b61eb568ebb8ecd6232af9220a373971946e
Remediation commit
de6deb2f8980e501c9ac20f57c8369eb1240bdc9
Files and components in scope
PathDescriptionnSLOC
packages/sdk-coreSign-request construction (Amino and Direct), response verification, display summaries3,140
packages/connectWallet connector, session handling and chain registry client1,860
services/relay-apiGo pairing relay: session pairing, encrypted message forwarding, HTTP API2,420
staging.larkspur.exampleStaging deployment of the relay API and registry (black-box testing)–
Total4 items7,420

Out of scope

  • Third-party wallet applications that implement the protocol
  • Production infrastructure and cloud account configuration
  • Denial-of-service testing beyond basic rate-limit checks
  • Social engineering

04

Threat model

Each threat lists the actor, the vector and the mitigation, and links to the findings that realised it in the reviewed code.

Assets at risk

  • User signatures and the transactions they authorise
  • Session keys for paired dApp–wallet sessions
  • Integrity of chain metadata used to build transactions

Actors

  • Malicious dApp pageUntrustedCalls the SDK with arbitrary arguments and metadata
  • Network attackerUntrustedObserves or guesses pairing identifiers; sends cross-origin requests
  • Compromised relay operatorSemi-trustedReads routing metadata and drops or reorders messages
  • Registry tamperingUntrustedServes modified chain metadata
Threats, vectors, mitigations and related findings
ThreatActorVectorMitigationFindings
Blind signing: summary differs from signed bytesMalicious dApp pageMetadata passed alongside the SignDocDerive summary from decoded SignDocLW-01
Session hijack through guessed pairing IDNetwork attackerEnumerating short pairing IDs128-bit IDs bound to originLW-02
Cross-origin session API accessNetwork attackerReflected CORS with credentialsOrigin allowlistLW-03
Transactions built against a malicious chain configRegistry tamperingUnsigned registry responsesSigned registry manifestLW-04
Pairing enumeration at scaleNetwork attackerUnthrottled pairing endpointsRate limitingLW-05

05

Findings

Severity

7 findings

LW-01HighResolvedBusiness logic

Displayed transaction summary is not derived from the signed payload

packages/sdk-core/src/sign/request.ts:L64–L118

Likelihood
Medium
Impact
High
Weakness
CWE-451
Description

buildSignRequest accepts a transaction and an optional display object. When display is present, its recipient, amount and memo are used for the human-readable summary sent to the wallet; the SignDoc that will actually be signed is serialised separately from the transaction argument.

Nothing checks that the two agree. A page that controls both arguments can show the user "Send 1 ATOM to cosmos1…abc" while the SignDoc contains a MsgSend of the full balance to a different address, or an authz MsgGrant.

Wallets that trust the SDK summary, which is the intended integration pattern, display the false summary next to the approval button.

Preconditions
  • The user connects a wallet to a malicious or compromised page
  • The wallet displays the SDK-provided summary
Exploit scenario
  1. 1.A compromised dApp page calls buildSignRequest with a MsgGrant giving the attacker authz over the user’s funds.
  2. 2.It passes display = { recipient: <the expected address>, amount: "1 ATOM" }.
  3. 3.The wallet shows the small transfer; the user approves.
  4. 4.The signed MsgGrant lets the attacker execute transfers on the user’s behalf.
Impact

Users can be induced to sign transactions materially different from what they were shown, up to loss of all funds controlled by the key.

Recommendation

Remove the display override. Generate the summary by decoding the exact bytes of the SignDoc that will be signed, render unknown message types explicitly as unrecognised, and add differential tests that decode each request independently.

Fix
packages/sdk-core/src/sign/request.tsdiff
-export function buildSignRequest(tx: TxInput, display?: DisplayHints): SignRequest {-  const signDoc = serializeSignDoc(tx)-  const summary = display ? summaryFromHints(display) : summaryFromTx(tx)+export function buildSignRequest(tx: TxInput): SignRequest {+  const signDoc = serializeSignDoc(tx)+  // The summary is always decoded from the bytes that will be signed.+  const summary = summaryFromSignDoc(decodeSignDoc(signDoc.bytes))   return { signDoc, summary } }
Resolution

Display hints removed from the public API; the summary is decoded from SignDoc bytes. Unknown message types are rendered as "Unrecognised message" with the raw type URL. Differential tests (DT-01) added.

LW-02HighResolvedCryptography

Pairing identifiers are short and not bound to the requesting origin

services/relay-api/internal/pairing/id.go:L12

Likelihood
Medium
Impact
High
Weakness
CWE-330
Description

Pairing IDs are 32-bit values from math/rand encoded in base32. With many pairings open at once, an attacker who enumerates IDs has a realistic chance of joining another user’s pending pairing before the wallet does.

The relay also does not record the origin that requested the pairing, so a wallet joining a pairing cannot tell whether it was created by the page the user is looking at. math/rand is not a cryptographic generator.

Preconditions
  • Pending pairings exist on the relay
  • The attacker can send requests to the relay (it is public)
Exploit scenario
  1. 1.The attacker repeatedly calls the join endpoint with random 32-bit IDs.
  2. 2.A guess hits a pending pairing created by a victim’s dApp session.
  3. 3.The attacker completes the key exchange before the victim’s wallet and receives sign requests intended for the victim, or injects responses.
Impact

Session hijacking: the attacker can read sign requests and, combined with LW-01, present false summaries to a victim wallet.

Recommendation

Generate 128-bit pairing IDs with crypto/rand, bind the requesting origin into the pairing record and the key-exchange transcript, and expire unjoined pairings within minutes.

Fix
services/relay-api/internal/pairing/id.godiff
-import "math/rand"+import "crypto/rand" -func NewID() string {-    return base32.StdEncoding.EncodeToString(uint32Bytes(rand.Uint32()))+func NewID() (string, error) {+    b := make([]byte, 16)+    if _, err := rand.Read(b); err != nil {+        return "", err+    }+    return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b), nil }
Resolution

128-bit IDs from crypto/rand; origin stored in the pairing record and included in the key-exchange transcript; unjoined pairings expire after five minutes.

LW-03MediumResolvedConfiguration

Relay reflects any Origin with credentials allowed

services/relay-api/cmd/server/cors.go:L8

Likelihood
Medium
Impact
Medium
Weakness
CWE-942
Description

The CORS middleware copies the request Origin header into Access-Control-Allow-Origin and sets Access-Control-Allow-Credentials: true. Any website can therefore make credentialed requests to the session-management endpoints and read the responses.

Preconditions
  • A user with an active relay session visits an attacker-controlled site
Exploit scenario
  1. 1.The attacker site issues fetch() to the session list endpoint with credentials: "include".
  2. 2.The browser sends the session cookie and exposes the response to the attacker.
Impact

Disclosure of session metadata and the ability to close or modify sessions from any origin.

Recommendation

Allow only configured dApp origins, and do not combine credentials with dynamic origins.

Fix
services/relay-api/cmd/server/cors.godiff
-  w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))-  w.Header().Set("Access-Control-Allow-Credentials", "true")+  if origin := r.Header.Get("Origin"); cfg.AllowedOrigins.Contains(origin) {+      w.Header().Set("Access-Control-Allow-Origin", origin)+      w.Header().Set("Vary", "Origin")+  }
Resolution

Allowlist from configuration; credentials removed from cross-origin responses because the API moved to bearer tokens.

LW-04MediumResolvedSupply chain

Chain registry responses are not integrity-protected

packages/connect/src/registry.ts:L22–L47

Likelihood
Low
Impact
High
Weakness
CWE-494
Description

The connector fetches chain metadata — chain ID, fee denominations, RPC and REST endpoints — from the registry over HTTPS and caches it in localStorage without any signature or pinning. A compromised registry, or any script able to write to the page’s storage, can redirect RPC traffic or change the chain ID and fee denomination used to build transactions.

Preconditions
  • Registry compromise, or script execution on the integrating origin
Exploit scenario
  1. 1.A modified registry entry points the RPC endpoint at an attacker node.
  2. 2.The attacker node returns false account numbers and balances, shaping what the user signs.
Impact

Transactions can be built against attacker-chosen metadata, and balance displays can be falsified.

Recommendation

Sign the registry manifest with a key held offline and verify it in the connector before use; re-verify cached entries on load.

Fix
packages/connect/src/registry.tsdiff
-  const chains = await (await fetch(REGISTRY_URL)).json()+  const { manifest, signature } = await (await fetch(REGISTRY_URL)).json()+  if (!(await verifyManifest(manifest, signature, REGISTRY_PUBLIC_KEY))) {+    throw new RegistryIntegrityError()+  }+  const chains = manifest.chains
Resolution

Manifest signed with an Ed25519 key; the public key ships in the SDK. Cached entries are re-verified on load.

LW-05LowResolvedDenial of service

Pairing and join endpoints have no rate limiting

services/relay-api/internal/http/routes.go:L31

Likelihood
Medium
Impact
Low
Weakness
CWE-770
Description

The pairing creation and join endpoints accept unlimited requests per client. This made the LW-02 enumeration practical and allows resource exhaustion through mass pairing creation.

Preconditions
  • Network access to the relay
Exploit scenario
  1. 1.An attacker scripts pairing creation in a loop, filling the pairing table until legitimate pairings fail.
Impact

Degraded relay availability and support for enumeration attacks.

Recommendation

Apply per-IP and per-origin token-bucket limits to pairing creation and join, and cap pending pairings per origin.

Resolution

Token-bucket limits added at the HTTP layer; pending pairings capped per origin.

LW-06LowResolvedConfiguration

Error responses include stack traces

services/relay-api/internal/http/errors.go:L19

Likelihood
Medium
Impact
Low
Weakness
CWE-209
Description

Unhandled errors return the Go stack trace in the response body, disclosing internal package paths, dependency versions and file layout.

Preconditions
  • Any request that triggers an unhandled error
Exploit scenario
  1. 1.A malformed join request returns a stack trace that reveals the router and dependency versions.
Impact

Information disclosure that assists further attacks.

Recommendation

Return generic error bodies with a correlation ID and log details server-side only.

Resolution

Generic error bodies with correlation IDs; details logged server-side.

LW-07InformationalAcknowledgedSupply chain

Development dependencies with published advisories

package-lock.json

Likelihood
Low
Impact
Low
Description

Several development-only packages had published advisories at the time of testing. None ship in the SDK bundle, but they run in CI with access to the publishing token.

Preconditions
  • A malicious or vulnerable development dependency executes in CI
Exploit scenario
  1. 1.A compromised build tool exfiltrates the npm publishing token during CI.
Impact

No runtime impact; supply-chain exposure in the build pipeline.

Recommendation

Update affected packages, enable automated dependency updates, and scope CI publishing tokens to the publish job only.

Resolution

Acknowledged. Automated dependency updates enabled; publishing moved to a separate job with a scoped token.

06

Severity

7findings
  • Critical0 · 0%
  • High2 · 29%
  • Medium2 · 29%
  • Low2 · 29%
  • Informational1 · 14%

By vulnerability class

  • Configuration2
  • Supply chain2
  • Business logic1
  • Cryptography1
  • Denial of service1

Likelihood and impact

Each finding placed by the likelihood of its preconditions and the impact of exploitation.

Findings by likelihood (columns) and impact (rows)
Impact ↓ / Likelihood →LowMediumHigh
High
0
Medium
0
0
Low
0

07

Remediation

6 of 7 findings are resolved, 1 acknowledged, 0 partially resolved and 0 open at remediation commit de6deb2f8980.

Remediation progress100%

Resolved and acknowledged findings count fully; partially resolved findings count half.

100%remediated
  • Resolved6 · 86%
  • Partially resolved0 · 0%
  • Acknowledged1 · 14%
  • Open0 · 0%
Findings by severity and remediation status
SeverityResolvedPartially resolvedAcknowledgedOpenTotal
Critical00000
High20002
Medium20002
Low20002
Informational00101
All findings60107

Residual items

  • LW-07InformationalAcknowledgedAcknowledged. Automated dependency updates enabled; publishing moved to a separate job with a scoped token.
  • ResolvedFixed and verified in the remediation review.
  • Partially resolvedMitigated; the residual risk is documented.
  • AcknowledgedAccepted by the protocol team with a stated rationale.
  • OpenNot yet addressed at the reviewed remediation commit.

08

Formal verification

Not applicable to this engagement. Security properties of the sign path were tested with differential tests that decode each SignDoc independently and compare against the displayed summary.

  • DT-01Not in scopeEvery field shown in the summary equals the corresponding field of the decoded SignDocChecked by differential tests, not proved. Failed on the original commit (LW-01); passes after the fix.

A proved property holds for every execution of the model under its stated assumptions. Fuzzing and property-based results are evidence, not proofs, and are recorded as inconclusive.

09

Recommendations

  1. 01

    Make "what you see is what you sign" a tested property

    Keep the differential tests from DT-01 in CI and extend them to every supported message type. Treat any summary not derived from the signed bytes as a security bug.

  2. 02

    Bind origin end to end

    Carry the requesting origin through pairing, key exchange and every sign request so wallets can display it and refuse mismatches.

  3. 03

    Harden the release pipeline

    Use provenance attestations for published packages and require two-person review for releases of the signing SDK.

10

Methodology and limitations

  1. 01Source-assisted review of the SDK sign path: serialisation, summary generation and signature verification
  2. 02Black-box and grey-box testing of the relay API on staging, following the OWASP Web Security Testing Guide
  3. 03Session and pairing analysis: identifier entropy, origin binding, replay and expiry
  4. 04Supply-chain review of SDK dependencies and build output
  5. 05Re-test of every finding against the remediation commit on a fresh staging deployment

Severity classification

Critical
Direct loss of funds or permanent loss of control is achievable with little or no precondition.
High
Material loss or protocol disruption is achievable under realistic conditions.
Medium
Limited loss, degraded functionality, or impact that depends on specific conditions.
Low
Minor impact, defence-in-depth gaps, or issues with unlikely preconditions.
Informational
Code quality, documentation or best-practice observations without direct security impact.

Limitations

  • Testing was performed against the staging environment; production infrastructure was not tested.
  • Third-party wallets that implement the protocol were not assessed.
  • Denial-of-service testing was limited to basic rate-limit checks.

No assessment can show that a system is free of vulnerabilities. This research assessment reflects the code at the commits listed. Quantix methodology

11

Technical appendix

Tools

  • Intercepting proxyManual API and session testing
  • Differential decoderIndependent SignDoc decoding for summary comparison
  • Dependency auditAdvisory review of npm and Go modules
Reviewed files (SHA-256) · 4 files
SHA-256 hashes of reviewed files
FileSHA-256
packages/sdk-core/src/sign/request.ts175afbfded2d6cec378c725c90d280798187647a040aafce8704af5c7f0f0ec6
packages/connect/src/registry.tsf4ba6479172ef81f802a44a0e4c5ef9a45878c1487c3dec0e9fd7e1c4146cd19
services/relay-api/internal/pairing/id.god840dc93ca602b306dd6c38fcc2f586f62e01aff4d74fe5433bfc527a08d6520
services/relay-api/cmd/server/cors.gofa0856dbd40c66605f27f7eb66dac41a398ef5caa6378c51bbab2734e9fb3b91

Notes

  • Testing followed the OWASP Web Security Testing Guide for the relay API (https://owasp.org/www-project-web-security-testing-guide/).

Technical references

  1. 1.Cosmos SDK: transaction signing modes (opens in a new tab)
  2. 2.CWE-451: User Interface Misrepresentation of Critical Information (opens in a new tab)
  3. 3.Go crypto/rand package (opens in a new tab)
  4. 4.CWE-330: Use of Insufficiently Random Values (opens in a new tab)
  5. 5.OWASP Session Management Cheat Sheet (opens in a new tab)
  6. 6.MDN: Cross-Origin Resource Sharing (opens in a new tab)
  7. 7.CWE-942: Permissive Cross-domain Policy with Untrusted Domains (opens in a new tab)
  8. 8.Cosmos chain registry (opens in a new tab)
  9. 9.CWE-494: Download of Code Without Integrity Check (opens in a new tab)
  10. 10.CWE-770: Allocation of Resources Without Limits or Throttling (opens in a new tab)
  11. 11.CWE-209: Generation of Error Message Containing Sensitive Information (opens in a new tab)
  12. 12.npm docs: npm audit (opens in a new tab)

Quantix Technical Security Assessment QX-TSA-2026-004

Version 1.1, published 9 Sept 2026. A4 PDF generated from this assessment record.

Download Report

12

Report history

  1. 17 Aug 2026Review openedReviewed commit ba74b61eb568 in github.com/example-org/larkspur-wallet
  2. 28 Aug 2026Review closedTwo testers, ten working days (source-assisted)
  3. After reviewRemediation reviewFixes checked at commit de6deb2f8980
  4. 9 Sept 2026Version 1.1 publishedQX-TSA-2026-004, A4 PDF generated from this record

Commission an assessment written to be verified.

Share the repository and the commit you plan to deploy. We reply with scoping questions and an estimate.