Repository navigation
chore(flutter): bundle the Freighter API locally and add a CSP before the public demo #116
Description
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
- Bundle
@stellar/freighter-api6.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. - Add a Content-Security-Policy for the Pages deployment (
_headersfile or<meta>inweb/index.html).script-srcmust not allow third-party script origins.connect-srcmust allow only the Serverpod Cloud API and what Flutter web needs. - 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.
- Update
puls3_flutter/README.md(wallet section) and remove the "production must bundle" TODO fromfreighter_bridge.js.
Acceptance criteria
- A production web build makes no request to
esm.shor 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 analyzeandflutter testpass.
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-policyOn 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
- A mobile wallet adapter.
- Changes to the Dart pre-sign or post-sign checks.
- Wallet-call timeouts (requested in the Feat/27 deploy flow #112 and feat(flutter): wallet connection with Freighter and a mock wallet #113 reviews).
Activity
XxHugheadxX commentedon Oct 10, 2026
@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:6still importshttps://esm.sh/@stellar/freighter-api@6.0.1?bundle, and the "Production must bundle it locally" TODO is still on line 4.web/index.htmlstill awaitsimport('./freighter_bridge.js')before it addsflutter_bootstrap.js, so a slow or blocked esm.sh still delays or breaks startup for every visitor.web/_headers, no<meta http-equiv>.Plan:
Vendor
@stellar/freighter-api6.0.1 as a file underweb/, taken from the npm tarball. Record its sha256 inpuls3_flutter/README.mdand import it with a relative path.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.
Add
web/_headerswith a CSP:script-src 'self'plus only what Flutter's own bootstrap needs.connect-srchas to cover more than the issue lists today:PULS3_API_URL);https://horizon-testnet.stellar.org, which the USDC balance check in feat(flutter): check the wallet's testnet USDC on Horizon before any signature in the hire flow (#28, F5-2) #159 calls before any signature;Puls3Fonts.useGoogleFontsis on, unless the fonts are bundled;I'll take the final list from the Network tab of a release build, not from memory.
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.
esm.sh.curl -sIshows the CSP header.freighter_bridge.jsblocked, the app still starts.fix(flutter): tell a wallet connector that failed to load apart from …