Skip to content

chore(flutter): bundle the Freighter API locally and add a CSP before the public demo #116

Closed

Description

@TOMOKI977

Context

The Freighter wallet bridge from #113 (#25) loads @stellar/freighter-api at runtime from a third-party CDN:

// puls3_flutter/web/freighter_bridge.js:6
import { ... } from 'https://esm.sh/@stellar/freighter-api@6.0.1?bundle';

Only the package version is pinned. There is no integrity check, and web/index.html has no Content-Security-Policy. That code runs with full page privileges in the same origin as the signing UI. A compromised or changed esm.sh response could:

  • report a false address or network to the app,
  • read the connected address,
  • call Freighter directly, outside the Dart pre-sign checks.

The post-sign checks would still catch a tampered transaction, but they cannot stop a malicious script acting on its own. Besides the security risk, index.html blocks app startup on this import. A slow CDN delays the app for every visitor, even those who never open the wallet. A CDN that is down shows "Freighter not found" to users who do have it installed.

The #70 spike already says production must bundle the dependency and enforce a CSP (docs/spikes/flutter-stellar-wallet.md:231,308-309), and the bridge comment says the same. This issue tracks doing it before the app is public on Cloudflare Pages (#31).

Deliverables

  1. Bundle @stellar/freighter-api 6.0.1 into the repository or the build output (vendored file or a small build step), so the web app loads no wallet code from a third-party origin at runtime. Record its version and source hash.
  2. Add a Content-Security-Policy for the Pages deployment (_headers file or <meta> in web/index.html). script-src must not allow third-party script origins. connect-src must allow only the Serverpod Cloud API and what Flutter web needs.
  3. Load the bridge without blocking Flutter startup: the app must start even if the bridge is slow or fails, and the wallet panel shows "wallet unavailable" instead of "Freighter not found" when the bridge itself failed.
  4. Update puls3_flutter/README.md (wallet section) and remove the "production must bundle" TODO from freighter_bridge.js.

Acceptance criteria

  • A production web build makes no request to esm.sh or any other third-party script origin (DevTools Network tab on the Pages preview).
  • The deployed page sends a CSP that blocks inline and third-party scripts, and the browser console shows no CSP violations during connect and sign with Freighter on Testnet.
  • With the bridge file blocked (DevTools request blocking), the app still starts and the wallet panel reports the wallet as unavailable.
  • Connect and sign with real Freighter on Testnet still work on the Pages preview.
  • flutter analyze and flutter test pass.

How to verify

cd puls3_flutter && flutter build web --release
# Serve build/web, open DevTools > Network: filter "esm.sh" → no requests
curl -sI https://<preview>.puls3-4lw.pages.dev/ | grep -i content-security-policy

On the Pages preview, connect Freighter on Testnet, sign the demo envelope, and check the console for CSP violations. Block freighter_bridge.js in DevTools, reload, and confirm the app starts.

Out of scope

Depends on

#113 (#25)

Activity

  1. added this to the Serverpod (Oct 14) milestone on Oct 7, 2026
  2. self-assigned this
    on Oct 8, 2026
  3. XxHugheadxX commented on Oct 10, 2026

    @XxHugheadxX
    Contributor

    @TOMOKI977 I can take #116 so it lands before the app is public, if you are not on it yet. If that works for you, reassign it to me (or say so here) and I'll start; per CONTRIBUTING I won't touch it before you confirm.

    Re-checked on main (e55a0a9):

    • web/freighter_bridge.js:6 still imports https://esm.sh/@stellar/freighter-api@6.0.1?bundle, and the "Production must bundle it locally" TODO is still on line 4.
    • web/index.html still awaits import('./freighter_bridge.js') before it adds flutter_bootstrap.js, so a slow or blocked esm.sh still delays or breaks startup for every visitor.
    • There is no CSP: no web/_headers, no <meta http-equiv>.

    Plan:

    1. Vendor @stellar/freighter-api 6.0.1 as a file under web/, taken from the npm tarball. Record its sha256 in puls3_flutter/README.md and import it with a relative path.

    2. Start Flutter without waiting for the bridge. The bridge resolves a promise that the Dart side awaits with a timeout, and a failed bridge reports the wallet as unavailable.

    3. Add web/_headers with a CSP: script-src 'self' plus only what Flutter's own bootstrap needs. connect-src has to cover more than the issue lists today:

      I'll take the final list from the Network tab of a release build, not from memory.

    4. Deliverable 3, "wallet unavailable" instead of "Freighter not found" when the bridge fails, is already in fix(flutter): show 'wallet connector could not load' instead of 'install Freighter' when the Freighter bridge fails to load #148. It is waiting for one approval.

    Verification: the issue's steps on the Pages preview.

    • The Network tab shows no request to esm.sh.
    • curl -sI shows the CSP header.
    • Freighter connects and signs on Testnet with no console violations.
    • With freighter_bridge.js blocked, the app still starts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P1Important, next in linearea: frontendFlutter implementationtype: choreMaintenance and tooling

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions