Commit graph

14 commits

Author SHA1 Message Date
SkateSpots
bb1cedb68d perf: self-host Leaflet CSS+icons (drop unpkg), preconnect relay/blossom
Bundles leaflet CSS + MarkerCluster CSS (main.tsx) and the default marker PNGs (SpotMap) instead of loading them from unpkg.com, so boot makes no third-party request; unpkg.com removed from the CSP (index.html meta + _headers + netlify.toml). Adds dns-prefetch/preconnect for relay.skate-spots.org and blossom.skate-spots.org.
2026-10-12 20:14:34 +07:00
shakespeare.diy
a0673a3244 Fix ESLint gate: scope ignores to app source (exclude android/.agents/cvm-server/public html/scripts), add index.html title, downgrade unused-vars & no-explicit-any to warnings, fix real lint errors (SkateGlobe unused-expr, EventsPage prefer-const, LicensePage Infinity shadow, useless-escape in SasquatchGuide/RiderCardPage, stale eslint-disable directives)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 21:30:56 +07:00
shakespeare.diy
df6b6fdf92 Ship CSP in the bundle so it also protects the native (mobile) build
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>
2026-08-16 22:30:19 +07:00
shakespeare.diy
b4b2375921 Security: fix stored XSS in map popups + add web CSP
Critical: Leaflet marker popups built untrusted Nostr event fields (and
BTC Map API fields) into innerHTML, bypassing React's output encoding. Any
user could publish a spot/tour/crew/event with an <img onerror> payload in a
text field; it executed on /map render with no interaction, letting injected
script read the plaintext nsec from localStorage (nostr:login) and the Cashu
seed (cashu-mnemonic) — permanent, unrotatable account + wallet takeover.

Fixes in src/components/SpotMap.tsx:
- Rebuild all popup DOM with createElement + textContent instead of innerHTML
  for every untrusted field: spot.location; tour.title/location/meetingPoint;
  evt.title/location; crew.name/location; BTC Map name/address/opening_hours/
  phone/description.
- Validate every event/API URL with sanitizeUrl() before it reaches an
  href/src: crew.image (popup + marker icon) and BTC Map website. Drop
  anything that isn't http(s). tel: links reduced to safe characters.
- Replace inline onerror handler on the crew marker image (would be blocked
  by CSP anyway) with a scripted img.onerror fallback.
- Add popupRow() helper so untrusted detail rows can never be innerHTML'd.

Defense in depth (web only, native Capacitor build unaffected):
- Add public/_headers with a strict Content-Security-Policy: default-src
  'none'; script-src 'self' 'wasm-unsafe-eval' (no unsafe-inline/eval);
  object-src 'none'; base-uri 'none'; frame-ancestors 'none'; plus the
  https:/wss: allowances a Nostr client genuinely needs for relays, media,
  and mints. Also X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
  Permissions-Policy.
- Move the inline boot-crash-guard script to public/boot-guard.js so
  script-src can stay 'self' with no 'unsafe-inline'; index.html now loads it
  externally. The guard also now paints errors via textContent, not innerHTML.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-16 22:12:26 +07:00
shakespeare.diy
5334144d27 Fix native white screen: relative asset base + boot crash guard
Two changes targeting the post-update white screen on the Zapstore APK:

1. vite.config: set base './' when CAPACITOR_BUILD is set. In a native
   WebView the bundle is served from the local filesystem, so absolute '/'
   asset paths are the classic cause of a silent white screen. The web
   deploy keeps '/'. This is the documented Capacitor requirement and is
   zero-risk to the working web build.

2. index.html: add a dependency-free "boot crash guard" that runs before
   the app bundle and paints any uncaught error / unhandled rejection —
   plus a 12s "did not render" timeout — directly to the screen using
   inline styles only. A boot failure that would otherwise be an
   info-less white screen now shows the actual error ON DEVICE, with no
   USB/remote-debugging needed. main.tsx signals a clean boot to cancel
   the timeout guard.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:21:57 +07:00
shakespeare.diy
3f654f4a35 fix: single bundle APK build, remove debug banner, clean build scripts
- Remove debug red banner from index.html and main.tsx
- Add android:build:apk script that uses CAPACITOR_BUILD=1 for single
  bundle output + strips Shakespeare build artifacts from dist/index.html
- Update all android:* scripts to use android:build:apk

Root cause of white screen: 100+ JS chunk files overwhelm Android
WebView local file serving. Single bundle with inlineDynamicImports
fixes this.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 22:51:49 +07:00
shakespeare.diy
379a5f7b34 debug: add visible HTML marker to diagnose white screen in APK
Red banner renders from pure HTML before any JS loads.
If visible → HTML loads but JS fails.
If not visible → WebView can't even load index.html.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 22:32:18 +07:00
shakespeare.diy
4b61b14d36 fix: splash screen + ErrorBoundary dark bg for Capacitor APK white screen
- Add SplashScreen.hide() call in main.tsx so splash dismisses when JS loads
- Set launchShowDuration: 0 + launchAutoHide: true in capacitor.config.ts
- Fix ErrorBoundary to use bg-black text-white instead of bg-background
  (bg-background = white before dark class is applied = white screen on error)
- These three combined should eliminate the white screen in the APK

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:27:34 +07:00
shakespeare.diy
26185c617a fix: remove CSP meta tag - was causing white screen in Capacitor APK
The CSP meta tag blocks Capacitor's internal asset loading (https://localhost
scheme) causing a white screen. Android/iOS provide OS-level sandboxing so
the CSP is not needed for security in native APK context.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:24:02 +07:00
shakespeare.diy
6a059a067b fix: relax CSP for Capacitor WebView - white screen fix
The strict CSP was blocking Capacitor's internal capacitor:// and ionic://
schemes causing a white screen in the Android APK. Added capacitor: and
ionic: to all relevant CSP directives (default-src, script-src, connect-src,
img-src, media-src, font-src).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:16:39 +07:00
shakespeare.diy
576df73a6b fix: restore original SkateSpots logo (icon-1024.png) everywhere
Replace all icon.svg references with icon-1024.png across:
- index.html (favicon, apple-touch-icon, og:image, twitter:image)
- src/components/Layout.tsx (desktop header + mobile header)
- src/pages/Index.tsx (footer)
- public/manifest.webmanifest (PWA icon entry)

Also fix og:url and og:image/twitter:image to use correct domain
skate-spots.org instead of skate-spots.com

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 20:02:27 +07:00
shakespeare.diy
f19851da92 security: implement all 8 protection measures
## 1. sanitizeUrl() (already existed, verified applied)
- Applied to ShopDetailPage, CoachDetailPage with urlDomain() tooltip hints
- Blocks javascript:, data:, vbscript: and all non-http(s) schemes
- Returns null for invalid URLs — link simply doesn't render

## 2. Geohash validation in parsers
- Added isValidGeohash() in types.ts using ngeohash.decode()
- Min 4 chars (country-level precision rejected), max 12 chars
- Rejects lat/lng outside Earth bounds
- Rejects 0,0 "null island" — classic bot artifact
- VPN/Tor unaffected: geohash comes from map pin, not user IP

## 3. Content length limits in all parsers
- MAX_TITLE_LEN=100, MAX_DESCRIPTION_LEN=3000, MAX_LOCATION_LEN=200
- MIN_TITLE_LEN=2, MIN_GEOHASH_LEN=4
- Applied to parseSkateSpot, parseSkateShop, parseSkateCoach,
  parseSkateFilmer, parseSkateEditor, parseSkateEvent

## 4. Smart WoT default (already in codebase, verified correct)
- Logged out / <5 follows → 'all' (don't punish new/anon users)
- Logged in with ≥5 follows → 'trusted' (sort trusted first)
- Soft mode: trusted content floats to top, nothing hidden
- Active in MapPage and EventsPage

## 5. Trusted count stats in useGrowthStats
- Added trustedSpots and trustedMappers to GrowthStats type
- Heuristic: pubkeys with ≥2 published items = organic community member
- CommunityStatsPage now shows "🛡 X from active accounts" below spot count
- Purely additive — both numbers always visible

## 6. OfficialBadge component (already existed, wired up)
- Added to SpotCard, EventCard, ShopDetailPage, CoachDetailPage
- Only renders for FOUNDER_PUBKEY (20e7c953...)
- Zero-friction: invisible for all other pubkeys
- Impersonation attempts immediately visible

## 7. Leaflet marker clustering
- Installed leaflet.markercluster v1.5.3 + @types
- MarkerClusterGroup replaces direct marker.addTo(map)
- Custom cluster icon: red/orange gradient circle with count
- maxClusterRadius: 60px, spiderfyOnMaxZoom: true
- MarkerCluster CSS loaded from CDN in index.html

## 8. DM phishing warning banner
- PhishingWarningBanner component in DirectMessagesPage
- One-time dismissible (localStorage key: dm-phishing-warning-dismissed)
- Message: SkateSpots will never DM you asking for keys/payments
- Uses useLocalStorage hook, Shield icon, X dismiss button

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 14:03:15 +07:00
shakespeare.diy
27e14994b5 Import files from skate-spots project
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 20:04:15 +07:00
shakespeare.diy
06fae294b8 New project created with Shakespeare
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 19:49:07 +07:00