Follow-up to the map-XSS work. The Capacitor app ships the same dist/ bundle (webDir: 'dist') and renders the same untrusted relay content, so it shared the same injection surface — but the CSP only existed as HTTP headers (public/_headers, netlify.toml), which never reach a WebView loading from a local scheme. The prior "OS sandbox is enough" comment was wrong: the sandbox isolates the app from OTHER apps; it does not stop a same-origin injected script from reading this app's own localStorage (nsec, NWC string, Cashu seed) or IndexedDB (Cashu bearer ecash proofs, native-only wallet). Changes: - index.html: add a <meta http-equiv="Content-Security-Policy"> so the policy ships inside the bundle and applies to BOTH web and native. script-src stays 'self' 'wasm-unsafe-eval' (no unsafe-inline/eval). Capacitor local origins (https://localhost, capacitor://localhost, ionic://localhost) are allowlisted on script/style/img/connect so asset loading & live-reload still work. Replaces the old "CSP intentionally omitted" comment. Verified the app boots and the map renders with no CSP violations. - SECURITY.md: correct the CSP section (now three synced locations incl. the meta tag; explain the local-scheme reason and the OS-sandbox misconception), add Cashu ecash/seed to the threat model and residual-risks, note the shared bundle, and extend the auditor file map (useCashu.ts, platform.ts, capacitor.config.ts). Note: the actual injection fix (createElement + textContent in map popups) already shipped on mobile since it lives in the shared bundle; this commit adds the defense-in-depth layer that was previously web-only. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
91 lines
4.5 KiB
HTML
91 lines
4.5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
|
|
<!--
|
|
Content-Security-Policy (defense-in-depth) — shipped in the HTML so it
|
|
applies to BOTH the web build AND the native Capacitor build.
|
|
|
|
Why a <meta> tag and not just HTTP headers: Capacitor loads the bundle
|
|
from a local scheme (https://localhost on Android, capacitor://localhost
|
|
on iOS), so the server headers in public/_headers / netlify.toml never
|
|
reach the mobile WebView. The OS sandbox isolates the app from OTHER apps;
|
|
it does NOT stop a same-origin injected script from reading this app's own
|
|
IndexedDB (Cashu ecash proofs) or localStorage (nsec, NWC string, Cashu
|
|
seed). A CSP is therefore just as important on mobile as on web.
|
|
|
|
Capacitor's local origins/schemes are allowlisted below so asset loading,
|
|
the local dev server, and live-reload continue to work. Notably there is
|
|
no 'unsafe-inline'/'unsafe-eval' on script-src (only 'wasm-unsafe-eval'
|
|
for the Bitcoin/Cashu WASM), which is the directive that actually blocks
|
|
injected <script>/onerror payloads. Keep this in sync with
|
|
public/_headers and netlify.toml.
|
|
-->
|
|
<meta http-equiv="Content-Security-Policy" content="
|
|
default-src 'none';
|
|
script-src 'self' 'wasm-unsafe-eval' https://localhost capacitor://localhost ionic://localhost;
|
|
style-src 'self' 'unsafe-inline' https://unpkg.com https://localhost capacitor://localhost ionic://localhost;
|
|
img-src 'self' data: blob: https: https://localhost capacitor://localhost ionic://localhost;
|
|
font-src 'self' data: https://fonts.gstatic.com;
|
|
connect-src 'self' https: wss: https://localhost capacitor://localhost ionic://localhost;
|
|
media-src 'self' blob: https:;
|
|
frame-src 'self' https:;
|
|
worker-src 'self' blob:;
|
|
manifest-src 'self';
|
|
object-src 'none';
|
|
base-uri 'none';
|
|
form-action 'self'
|
|
">
|
|
|
|
<!-- PWA Manifest -->
|
|
<link rel="manifest" href="/manifest.webmanifest">
|
|
|
|
<!-- App Icons -->
|
|
<link rel="icon" type="image/png" href="/images/icon-1024.png">
|
|
<link rel="apple-touch-icon" href="/images/icon-1024.png">
|
|
<link rel="apple-touch-icon" sizes="152x152" href="/images/icon-1024.png">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/icon-1024.png">
|
|
<link rel="apple-touch-startup-image" href="/images/icon-1024.png">
|
|
|
|
<!-- Theme Color -->
|
|
<meta name="theme-color" content="#ff0040">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="apple-mobile-web-app-title" content="SkateSpots">
|
|
|
|
<!-- SEO Meta Tags -->
|
|
<meta name="description" content="Discover skate spots, find coaches, join events, and connect with the skateboarding community on Nostr. Free, decentralized, censorship-resistant.">
|
|
<meta name="keywords" content="skateboarding, skate spots, skate parks, nostr, decentralized, bitcoin, lightning, zaps">
|
|
|
|
<!-- Open Graph -->
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:title" content="SkateSpots - Decentralized Skate Map & Community">
|
|
<meta property="og:description" content="Discover skate spots, find coaches, join events, and connect with the skateboarding community on Nostr.">
|
|
<meta property="og:image" content="https://skate-spots.org/images/icon-1024.png">
|
|
<meta property="og:url" content="https://skate-spots.org">
|
|
|
|
<!-- Twitter Card -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="SkateSpots - Decentralized Skate Map">
|
|
<meta name="twitter:description" content="Discover skate spots, find coaches, join events on Nostr.">
|
|
<meta name="twitter:image" content="https://skate-spots.org/images/icon-1024.png">
|
|
|
|
<!-- Leaflet CSS -->
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css">
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css">
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
|
|
<!--
|
|
BOOT CRASH GUARD (native-safe) — external so the web build can ship a
|
|
strict CSP (script-src 'self') with no 'unsafe-inline'. See boot-guard.js.
|
|
-->
|
|
<script src="/boot-guard.js"></script>
|
|
|
|
<script type="module" src="/src/main.tsx"></script>
|
|
</body>
|
|
</html>
|