The Ancore browser extension enforces a strict Content Security Policy (CSP) via
manifest.json (content_security_policy.extension_pages). This document explains
each directive, the rationale behind it, and how to maintain the policy as the
codebase evolves.
script-src 'self';
object-src 'none';
connect-src 'self'
https://*.stellar.org
https://relayer.ancore.io
https://relayer-staging.ancore.io
https://indexer.ancore.io
https://indexer-staging.ancore.io;
img-src 'self' data:;
style-src 'self' 'unsafe-inline';
default-src 'none'
| Directive | Value | Rationale |
|---|---|---|
script-src |
'self' |
Only scripts bundled with the extension may execute. Blocks XSS via injected <script> tags and eval(). MV3 prohibits 'unsafe-eval' and remote script sources. |
object-src |
'none' |
Disables Flash/plugin execution vectors entirely. |
connect-src |
'self' + allowlist |
Restricts fetch/XMLHttpRequest/WebSocket to the extension origin and the explicit service allowlist below. Prevents data exfiltration to arbitrary hosts. |
img-src |
'self' data: |
Allows bundled icons and inline QR-code data: URIs (used by PaymentQRCode.tsx). |
style-src |
'self' 'unsafe-inline' |
Tailwind CSS injects runtime style attributes; 'unsafe-inline' is required. Styles cannot execute code, so the risk is low. |
default-src |
'none' |
Deny-by-default for any fetch type not explicitly listed above. |
| Host | Purpose |
|---|---|
https://*.stellar.org |
Horizon API, Soroban RPC, Friendbot (testnet) |
https://relayer.ancore.io |
Production transaction relayer |
https://relayer-staging.ancore.io |
Staging relayer (QA / pre-release) |
https://indexer.ancore.io |
Production indexer |
https://indexer-staging.ancore.io |
Staging indexer |
When adding a new service origin, update both manifest.json and this table.
MV3 forbids 'unsafe-inline' in script-src. The build pipeline enforces this:
- Vite plugin (
cspInlineScriptGuardinvite.config.ts) — fails the build if any emitted HTML file contains an inline<script>tag or inline event handler. - Post-build script (
scripts/check-csp.js) — scansdist/for inline scripts, event handlers,javascript:URIs,eval(), andnew Function(). Run with:pnpm check:csp - ESLint rule (
no-script-url: errorineslint.config.cjs) — catchesjavascript:URIs in source code at lint time.
If a third-party library introduces an inline script, the build will fail. Options:
- Find an alternative library.
- Fork/patch the library to use external scripts.
- If unavoidable, document the exception here with a hash (
'sha256-...') and a justification, and add it toscript-srcinmanifest.json.
Run these checks before every release on both Chrome and Firefox.
- Extension installs without errors in Chrome (MV3) and Firefox (MV2 polyfill).
- Popup opens; no CSP errors in the browser console (
F12 → Console). - Onboarding flow completes end-to-end.
- Wallet unlocks with correct password; lock button re-locks.
- Send transaction reaches the relayer (
Networktab shows request torelayer.ancore.ioor configured URL, not blocked). - Balance fetch reaches Horizon (
*.stellar.org, not blocked). - No requests to unlisted origins appear in the
Networktab.
- Open
chrome://extensions→ click Errors on the Ancore entry — no CSP violations listed. - In Firefox:
about:debugging→ Inspect → Console — no CSP errors.
- Run
pnpm build && pnpm check:csp— exits 0. - Run
pnpm lint— nono-script-urlerrors.
- Receive screen displays QR code (validates
img-src data:is working).
- Add the origin to
content_security_policy.extension_pagesinmanifest.json. - Add a row to the allowlist table in this document with the purpose.
- If the origin is a configurable service URL, add it to
src/config/urls.ts. - Run
pnpm build && pnpm check:cspto confirm no regressions.
The extension wallet enforces optional daily transfer limits and step-up thresholds configured in Settings. Policy types live in @ancore/types (TransferPolicy, validateTransferPolicy). Amounts above the step-up threshold require additional confirmation; transfers that would exceed the daily limit are blocked. Defaults and persistence are managed in apps/extension-wallet/src/stores/settings.ts (dailyTransferLimit, transferStepUpThreshold).
The extension requests the following permissions in manifest.json:
Required for persisting wallet state, settings, and allowlist entries via chrome.storage.local and chrome.storage.session.
Enables the side panel approval UX for dApp sign requests. When the background receives a signTransaction request, it opens a side panel pinned to the current tab rather than a popup window. The side panel stays visible while the user interacts with the dApp, unlike a popup which closes on outside click.
- Chrome 116+:
chrome.sidePanel.setOptions({ path })+chrome.sidePanel.open() - Firefox / no
sidePanelAPI: Falls back tochrome.windows.create()popup - The side panel loads
sidepanel/index.htmland renders the sameSignTransactionApprovalScreencomponent used in the popup/sign-transactionroute
When adding or removing permissions, update both manifest.json and this section.