Please do not open a public issue for security problems.
Report privately through GitHub: go to the repository's Security tab → Report a vulnerability (this opens a private advisory). If you can't use that, email daveproxy80@gmail.com.
Include what you can: affected component, steps to reproduce, and impact. We aim to acknowledge within a few days and will coordinate a fix and disclosure with you.
This is testnet, pre-production software. The smart contracts have not yet been audited. Treat anything on-chain as experimental until a release notes otherwise.
The app signs transactions in the browser, so it ships a strict policy (#601):
Content-Security-Policyis set per request insrc/proxy.tswith a nonce. Scripts run only from'self'plus that nonce ('strict-dynamic'); the inline theme bootstrap insrc/app/layout.tsxcarries the nonce.connect-srcis limited to the configured Horizon, Soroban RPC, backend and error-report origins plus WalletConnect.frame-ancestors 'none'protects the signing flow from clickjacking. Styles keep'unsafe-inline'because React inlinestyleattributes are used throughout.Strict-Transport-Security,X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin,Permissions-PolicyandX-Frame-Options: DENYare set for every route innext.config.ts.- Enforcement and rollout: production sends
Content-Security-Policyby default.CSP_MODE=report-onlyis an explicit temporary opt-out for monitoring a policy change; setNEXT_PUBLIC_CSP_REPORT_URIto collect violations and watch them through a full connect → deposit → withdraw session with each supported wallet (Freighter, xBull, Lobstr, WalletConnect) before returning to enforcement. A production build warns if report-only is enabled without a reporting URI. Development defaults to report-only. - If you add a third-party origin (RPC, API, analytics), it must be configured
through the env vars in
.env.exampleor added tosrc/lib/securityHeaders.ts.
Vercel builds main through its git integration, so every merged build script
runs with whatever the Vercel project exposes at build time (#920). The project
holds no server secrets today, and it must stay that way until production
builds move to .github/workflows/deploy.yml, which builds without secrets and
deploys the prebuilt output. Until then, do not add HELIOBOND_API_KEY or any
other server-only secret to the Vercel project.
Client error reporting and web-vitals (#609) go to NEXT_PUBLIC_ERROR_REPORT_URL
when it is set; nothing is sent otherwise.
- Collected: error kind (render, route, transaction, RPC timeout, unhandled), the scrubbed message and stack, the Soroban contract error code, the operation name (deposit/withdraw), page path, release tag, and LCP/INP/CLS/FCP/TTFB.
- Never collected: wallet addresses, transaction hashes, emails or XDR — these
are redacted from messages, stacks and caller-supplied
contextvalues before sending (scrub()insrc/lib/errorReporting.ts) — and no cookies or persistent identifiers. - Opt-in (#658): telemetry is off until the visitor agrees. A first visit
shows a consent banner (
src/components/TelemetryConsent.tsx); the choice is stored ashb-telemetry-consent=granted|deniedand can be changed at any time from the privacy control in the footer. With no recorded choice — including a visitor who never sees the banner — nothing is sent (isTelemetryAllowed()requiresgranted). - Browser signals override consent: nothing is sent when the browser sends Do-Not-Track or Global Privacy Control, even if consent was granted.
- Source maps: build with
SOURCE_MAPS=trueto publish browser source maps so the sink can symbolicate stacks.
Backend-authenticated features use a wallet signature (SEP-53 message signing, or a SEP-10 challenge transaction) exchanged for a short-lived JWT bound to the G-address (#603). The token is kept in memory only; the session is restored through an HttpOnly refresh cookie. See docs/AUTH.md.
Code runs on developer machines and in the production build whenever a dependency, lifecycle script, git hook or tool config runs, so those are reviewed as code:
- Package manager: bun only, with
bun install --frozen-lockfilein CI.bun.lockis the only lockfile that counts; do not addpnpm-lock.yamloryarn.lock(the stalepackage-lock.jsonis being removed in #918). - Scripts and hooks: changes to
package.jsonscripts,.husky/*,.github/workflows/*,scripts/*and tool configs (ESLint, Next, Vitest, Playwright, PostCSS, Tailwind,bunfig.toml,.npmrc) need a maintainer's review. Thesupply-chain review gatecheck blocks such a PR until a maintainer applies thesupply-chain-reviewedlabel, and prints the exact changes in the job summary. A new push ignores the label, so remove and re-add it after reviewing the new commits. - Hidden content: the
hidden-content checkCI job fails on tab-padded lines, zero-width or bidi characters, obfuscated code and new root-level scripts (scripts/check-hidden-content.mjs). - Dependencies: a new dependency needs a justification in the PR, its
publish date and its install-script status. Do not add a package to
trustedDependencieswithout review. - Actions: workflows that run fork code use the
pull_requestevent, which gets no secrets and a read-only token. The onepull_request_targetworkflow (auto-review.yml) checks out the base branch and only reads the PR diff through the API; it must never check out or run fork code. - CI hardening (#916): every
uses:in.github/workflowsis pinned to a full commit SHA with the release in a trailing comment, so retagging an upstream version cannot change what runs here; the weeklygithub-actionsDependabot entry is the thing that moves those SHAs. Each workflow setspermissions: {}and each job then grants only what it needs, and every checkout passespersist-credentials: falseso the workflow token never lands in.git/configfor a later step to use.e2e-chain.ymlverifies thestellar-clitarball against a pinned SHA-256 before extracting it.dtolnay/rust-toolchainpublishes no tag for the ref it uses, so its pin is bumped by hand. - Actions settings (maintainer, dashboard): these are admin toggles that a
PR cannot set, and #916 asks for them too. Under Settings > Actions >
General, select "Allow Heliobond, and select non-Heliobond, actions and
secrets" (GitHub-created and verified creators, plus
oven-sh/setup-bun,Swatinem/rust-cacheanddtolnay/rust-toolchain), turn on "Require actions to be pinned to full-length commit SHA", and keep fork workflow approval on for first-time contributors.
If a malicious change reaches a branch:
- Revert the merge commit on
mainand block the deploy pipeline. - Rotate every secret the build or runtime could read, including deploy tokens, API keys and error-sink tokens, starting with anything that was present in the environment of the affected build.
- Check the affected developer machines and CI runs that installed or built the change, then rebuild them from a clean checkout.
- Report it privately (see above) and record the incident in the changelog.