Skip to content

The transaction signing path loads its code from a third-party CDN at runtime, with no SRI and no CSP #211

Description

@Muyideen-js

Problem

Every line of code that builds, signs, and submits a transaction is fetched from a third-party CDN
at page load, over a URL with no integrity check:

  • frontend/soroban.js:3 -- const SDK_URL = "https://esm.sh/@stellar/stellar-sdk@14.5.0?bundle",
    loaded via import(SDK_URL) on line 9
  • frontend/wallet.js:14 -- import("https://esm.sh/@stellar/freighter-api@5.0.0?bundle")

Neither is pinned by hash. A dynamic import() of a remote URL cannot carry a Subresource
Integrity attribute, so there is no mechanism verifying that the bytes esm.sh returns are the bytes
the package published.

And nothing constrains where scripts may be loaded from. netlify.toml sets
X-Content-Type-Options, Referrer-Policy, and Permissions-Policy, but no
Content-Security-Policy
-- confirmed against the live response headers from
https://stellar-pulse.netlify.app/. There is no script-src, no connect-src, no
default-src.

Impact

stellar-sdk is the module that constructs the transaction, and freighter-api is the module that
hands it to the wallet for signature. If esm.sh is compromised, or its DNS or BGP is hijacked, or
its edge cache is poisoned for this origin, an attacker controls both. They can:

  • rewrite the operation before it is built, so the XDR the user is asked to approve pays their
    address instead of the market contract
  • swap the place_bet invocation for a transfer of the wallet's entire XLM balance
  • intercept the signed XDR and submit a different transaction
  • exfiltrate every connected address

The user's protection at that point is reading raw XDR in the Freighter confirmation dialog, which
in practice nobody does. This drains any wallet that connects to the site, and it requires no
vulnerability in this repo's own code -- only in a dependency delivery path this repo has chosen not
to control.

The exposure is not theoretical for a CDN of this kind: unpinned, unverified runtime module loading
from a public transpiling CDN is the exact shape of the polyfill.io class of incident.

What to do

  • Vendor @stellar/stellar-sdk and the wallet library into the repo, or self-host them from the
    same origin, so the signing path ships with the site and is reviewable in git
  • If a CDN is kept for any asset, load it with a static <script> tag carrying a
    Subresource Integrity hash and crossorigin, never a bare dynamic import() of a URL
  • Add a Content-Security-Policy header in netlify.toml with an explicit script-src,
    connect-src limited to the Soroban RPC and Horizon endpoints in frontend/contracts.json,
    object-src 'none', and base-uri 'self'
  • Pin exact dependency versions and record their hashes, so an upgrade is a reviewed commit
    rather than a silent CDN change
  • Add frame-ancestors 'none' to stop the app being framed by a look-alike site

Why this is critical

This is the highest-severity issue in the repository. Every other bug here costs a user
correctness or convenience. This one can cost them their balance, silently, without any bug in
SPulse's own source, and there is currently no control anywhere in the stack that would stop it or
even detect it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: infrastructureRPC, indexing, operations, and reliabilityarea: securitySecurity engineering and adversarial analysisbugSomething isn't workingpriority: criticalCritical to protocol safety or mainnet readiness

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions