163 Commits

Author SHA1 Message Date
Elie Habib
5bb3696f7a chore: bump version to 2.5.21 (#605) 2026-03-01 02:40:41 +04:00
Elie Habib
9ad9117689 feat(aviation): add NOTAM closure detection via ICAO API (#583)
* feat(aviation): add NOTAM closure detection via ICAO API

Adds international airport closure detection via ICAO NOTAMs:
- New fetchNotamClosures() queries ICAO realtime-notams endpoint
- Detects closures via Q-codes (FA/AH/AL/AW/AC/AM) and text patterns
- Batches airports in groups of 20 per API call
- 4-hour cache TTL via cachedFetchJson (stampede-safe)
- NOTAM closures override existing AviationStack alerts for same airport
- Graceful: no ICAO_API_KEY env var = silently skipped

To activate: set ICAO_API_KEY env var (register at dataservices.icao.int)

* feat(settings): add ICAO_API_KEY to desktop app settings

Adds ICAO NOTAM API key to the desktop settings UI:
- Rust: SUPPORTED_SECRET_KEYS [23→24]
- TypeScript: RuntimeSecretKey + RuntimeFeatureId unions
- Feature definition: 'icaoNotams' in Tracking & Sensing category
- Settings UI: label, signup URL, analytics name

* feat(aviation): limit NOTAM queries to MENA airports only

Per user request, ICAO NOTAM closure detection is scoped to
Middle East airports only (region='mena', ~35 airports).
This reduces API calls (2 batches vs 5) and focuses on the
region where closures are most relevant.

* fix(aviation): align NOTAM cache TTL to 30 min (matching FAA/intl)
2026-02-28 23:25:41 +04:00
Elie Habib
74dc8545a8 feat(settings): add AVIATIONSTACK_API to desktop settings page (#553)
Register the key in Rust keychain (SUPPORTED_SECRET_KEYS), frontend
RuntimeSecretKey type, feature toggle, and settings UI under
"Tracking & Sensing" category.
2026-02-28 19:41:47 +04:00
Elie Habib
b4638b281e chore: bump version to 2.5.20 + changelog
Covers PRs #452–#484: Cloudflare edge caching, commodities SWR fix,
security advisories panel, settings redesign, 52 POST→GET migrations.
2026-02-28 00:40:04 +04:00
Elie Habib
84e39ba4b1 fix(desktop): enable click-to-play YouTube embeds + CISA feed fixes (#476)
* fix(tech): use rss() for CISA feed, drop build from pre-push hook

- CISA Advisories used dead rss.worldmonitor.app domain (404), switch to rss() helper
- Remove Vite build from pre-push hook (tsc already catches errors)

* fix(desktop): enable click-to-play for YouTube embeds in WKWebView

WKWebView blocks programmatic autoplay in cross-origin iframes regardless
of allow attributes, Permissions-Policy, mute-first retries, or secure
context. Documented all 10 approaches tested in docs/internal/.

Changes:
- Switch sidecar embed origin from 127.0.0.1 to localhost (secure context)
- Add MutationObserver + retry chain as best-effort autoplay attempts
- Use postMessage('*') to fix tauri://localhost cross-origin messaging
- Make sidecar play overlay non-interactive (pointer-events:none)
- Fix .webcam-iframe pointer-events:none blocking clicks in grid view
- Add expand button to grid cells for switching to single view on desktop
- Add http://localhost:* to CSP frame-src in index.html and tauri.conf.json
2026-02-27 22:02:06 +04:00
Elie Habib
3bd501c4ce fix+feat: RSS feed repairs, HLS native playback, summarization cache fix, embed improvements (#452)
* fix(feeds): replace 25 dead/stale RSS URLs and add feed validation script

- Replace 16 dead feeds (404/403/timeout) with working alternatives
  (Google News proxies or corrected direct RSS endpoints)
- Replace 6 empty feeds with correct RSS paths (VnExpress, Tuoi Tre,
  Live Science, Greater Good, News24, ScienceDaily)
- Replace 3 stale feeds (CNN World, TVN24, Layoffs.fyi) with active sources
- Remove Disrupt Africa (inactive since Jan 2024)
- Add scripts/validate-rss-feeds.mjs to check all 420 feeds
- Add test:feeds npm script

* feat(live-news): use stable CDN HLS feeds for desktop native playback

Direct HLS feeds bypass YouTube's expiring tokenized URLs and iframe
cookie issues on WKWebView. 10 channels (Sky, DW, France24, Euronews,
Al Arabiya, Al Jazeera, CBS News, TRT World, Sky News Arabia, Al Hadath)
now play via native <video> on desktop with automatic YouTube fallback
when CDN feeds are down (5-min cooldown).

Also:
- Fix euronews handle typo (@euabortnews → @euronews)
- Fix TRT World handle (@taborrtworld → @TRTWorld)
- Add fallbackVideoId to CBS News, Sky News Arabia, TRT World
- Extract hlsManifestUrl from YouTube API for non-mapped channels
- Add sidecar /api/youtube-embed endpoint (auth-exempt for iframes)
- Switch webcam/embed iframes from cloud to local sidecar origin
- CSP: allow frame-src http://127.0.0.1:* for sidecar embeds
- Remove legacy WEBKIT_FORCE_SANDBOX env var (deprecated in WebKitGTK)
- Add 37 tests covering HLS map integrity, decision tree ordering,
  cooldown logic, race safety, service layer, sidecar endpoint, and CSP

* fix(summarization): pass panelId as geoContext to prevent Redis cache key collision

When breaking news appeared across multiple panels (World, US, Europe,
Middle East), all panels generated identical cache keys because geoContext
was always undefined. The first panel's summary was served to all others.

* fix(desktop): sidecar embed autoplay, webcam fullscreen, optional channel fallbacks

- Sidecar YouTube embed: use mute param (not hardcoded), add play overlay
  for WKWebView autoplay fallback, add postMessage bridge for play/pause/
  mute/unmute commands matching the cloud embed handler
- Webcam iframes: only set allowFullscreen on web to prevent grid-breaking
  fullscreen on desktop click
- Optional channels: add fallbackVideoId + useFallbackOnly for livenow-fox,
  abc-news, nbc-news, wion so they play instead of showing "not currently live"
- Tests: 9 new assertions covering mute param, postMessage bridge, play
  overlay, yt-ready message, and optional channel fallback coverage (46 total)
2026-02-27 13:55:06 +04:00
Elie Habib
f066c7c34c fix(linux): detect NVIDIA GPU and work around EGL_BAD_ALLOC on Wayland (#446)
Linux users with NVIDIA proprietary drivers on Wayland report crashes:
  "Could not create surfaceless EGL display: EGL_BAD_ALLOC. Aborting..."

WebKitGTK's web process calls eglGetPlatformDisplay with the
EGL_PLATFORM_SURFACELESS_MESA platform, which fails with NVIDIA's EGL
implementation and triggers abort(). WEBKIT_DISABLE_DMABUF_RENDERER=1
(already set) only controls buffer sharing, not EGL initialization.

Detect NVIDIA via /proc/driver/nvidia and:
- Set __NV_DISABLE_EXPLICIT_SYNC=1 to prevent Wayland flickering
- Force GDK_BACKEND=x11 on NVIDIA+Wayland (user can override)

Also bumps version to 2.5.19.

Refs: tauri-apps/tauri#9394, gitbutlerapp/gitbutler#5282
2026-02-27 00:20:50 +04:00
Elie Habib
6106f368f7 chore: bump v2.5.18 (#445) 2026-02-26 23:59:42 +04:00
Elie Habib
2fdecae5e5 fix(linux): stop mixing host GStreamer plugins with bundled AppImage plugins (#444)
The AppImage bundles GStreamer from CI (Ubuntu 24.04, GStreamer 1.24).
Previously, host plugin directories (/usr/lib/gstreamer-1.0/) were
appended as fallback. This caused ABI version mismatches — host plugins
compiled against a different GStreamer version fail with undefined symbol
errors (gst_util_floor_log2, mpg123_open_handle64, etc.), leaving WebKit
without usable codecs for YouTube playback.

Since PR #434 installs the full GStreamer codec suite on CI, the AppImage
is fully self-contained. Remove the host fallback and block host plugin
scanning to prevent ABI conflicts across distro GStreamer versions.
2026-02-26 23:56:56 +04:00
Elie Habib
6e15b99830 chore: bump v2.5.17 (#443) 2026-02-26 23:38:12 +04:00
Elie Habib
a660ca7aaa fix(linux): disable WebKitGTK compositing in VMs to fix black iframes (#441)
WebKitGTK promotes iframes, <video>, and canvas elements to GPU-textured
compositing layers. In VMs (Apple Virtualization.framework, QEMU, VMware,
etc.) the virtio-gpu driver often only supports 2D or limited GL, so GBM
buffer allocation for compositing layers fails silently — rendering
iframe/video content as black while the main page works fine.

Detect VM environments via /proc/cpuinfo hypervisor flag and sys_vendor
strings, then set WEBKIT_DISABLE_COMPOSITING_MODE=1 and
LIBGL_ALWAYS_SOFTWARE=1 to force software rendering for all content.
Native Linux machines with real GPUs are unaffected.
2026-02-26 23:30:21 +04:00
Elie Habib
c66e740f30 chore: bump v2.5.16 (#440) 2026-02-26 22:54:41 +04:00
Elie Habib
e40b2eb47e chore: bump v2.5.15 (#432) 2026-02-26 21:36:44 +04:00
Elie Habib
caeb3ead2c chore: bump v2.5.14 (#428)
Changes since v2.5.13:
- feat: add ARM64 Linux build target and download detection (#427)
- fix(live-channels): tolerate YouTube API failures when adding custom channels (#425)
- fix(linux): append host GStreamer plugins to AppImage search path (#424)
- fix(linux): enable keyring persistence via Secret Service + keyutils (#419)
2026-02-26 20:58:26 +04:00
Elie Habib
bbe814c985 fix(live-channels): tolerate YouTube API failures when adding custom channels (#425)
The /api/youtube/live validation endpoint may return 429 or non-JSON
responses (Vercel WAF, YouTube rate limiting). Previously this caused
res.json() to parse HTML → either throw (caught, channel added) or
return channelExists:false (blocked add with red border).

Now only blocks when the API explicitly returns 200 OK with
channelExists:false — any non-OK status or error allows the add.

Also bumps version to 2.5.13.
2026-02-26 20:33:11 +04:00
Elie Habib
30bd84abb2 fix(linux): append host GStreamer plugins to AppImage search path (#424)
* chore: bump v2.5.12

## Changelog

- fix(linux): enable keyring persistence via Secret Service + keyutils (#419)
- fix(ci): use weston+XWayland for Linux smoke test (#417)
- ci: add standalone Test Linux App workflow (#414)
- ci: skip Typecheck and Lint on fork PRs (#415)
- perf: optimize Wingbits API usage and reduce unnecessary polling (#416)

* fix(linux): append host GStreamer plugins to AppImage search path

The linuxdeploy GStreamer hook force-overrides GST_PLUGIN_PATH_1_0 and
GST_PLUGIN_SYSTEM_PATH_1_0 to only contain bundled plugins from the CI
build system (Ubuntu 24.04, GStreamer 1.24).  On hosts with newer
GStreamer (e.g. Arch 1.28), codec plugins like gst-libav and
fakevideosink from gst-plugins-bad are invisible — WebKit can't play
video.

Append common host GStreamer plugin directories as fallback so the
system's codec plugins are discoverable while bundled plugins retain
priority.

Also fixes:
- tauri.conf.json devUrl port mismatch (5173 → 3000) breaking desktop:dev
- live-channels-window YouTube validation allowing add on non-OK responses
2026-02-26 20:24:50 +04:00
Elie Habib
dd4f4ad786 fix(linux): enable keyring persistence via Secret Service + keyutils (#419)
Linux had no keyring backend feature enabled — keyring v3 fell back to
in-memory mock store. Secrets appeared to save but were lost on restart.

Added `linux-native-sync-persistent` (kernel keyutils + D-Bus Secret
Service combo) and `crypto-rust` for Secret Service encryption. This
uses GNOME Keyring or KDE Wallet on desktop Linux for persistent storage.
2026-02-26 18:47:29 +04:00
Elie Habib
5fa5a73e30 chore: bump v2.5.11 (#412)
Fixes:
- Linux AppImage black screen on WebKit/GStreamer (#411)
- Destroy live news player before showing offline/error message (#410)
2026-02-26 16:45:35 +04:00
Elie Habib
d2e00bc581 fix(linux): address AppImage black screen on WebKit/GStreamer (#411) 2026-02-26 16:41:00 +04:00
Elie Habib
012907ae84 chore: bump v2.5.10 and update README for recent fixes (#408)
* chore: bump v2.5.10 and update README for recent fixes

Version 2.5.9 → 2.5.10. Roadmap entries for:
- Yahoo Finance rate-limit UX across all market panels
- Sidecar auth resilience (401-retry, settings diagFetch)
- Verbose toggle persistence to writable data directory
- Finnhub-to-Yahoo fallback routing

* chore: add v2.5.10 changelog entry
2026-02-26 15:23:21 +04:00
Elie Habib
2db217211e fix: surface Yahoo rate-limit status to user, auth retry, verbose persistence (#407)
- Show rate-limited message instead of generic "Failed to load" on Markets,
  ETF, Commodities, and Sector panels when Yahoo returns 429
- fetchYahooQuotesBatch returns rateLimited flag; early-exit after 3 misses
- ETF panel skips retry loop when rate-limited, shows specific i18n message
- Fallback Finnhub symbols through Yahoo when API key missing
- 401-retry in runtime fetch patch for stale sidecar token after restart
- diagFetch auth helper for settings window diagnostic endpoints
- Verbose toggle writes to writable dataDir instead of read-only app bundle
2026-02-26 14:49:25 +04:00
Elie Habib
a560efff49 chore: bump v2.5.9 and update README for recent features (#398)
* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns

* chore: bump version to 2.5.9 and make pre-push hook executable

* docs: update README with supply chain intel, universal CII, Happy Monitor, security hardening, and recent features
2026-02-26 10:26:08 +04:00
Elie Habib
f912c5f15a fix: restore Linux AppImage updater routing and fallback port reporting (#397) 2026-02-26 10:07:59 +04:00
Elie Habib
5221486c88 fix: Linux AppImage crashes, RSS proxy 403s, and console noise (#395)
Linux AppImage (#370, #257):
- Upgrade CI from Ubuntu 22.04 to 24.04 (GLib 2.80 fixes g_task_set_static_name symbol mismatch)
- Set GDK_BACKEND=wayland,x11 when WAYLAND_DISPLAY detected (fixes GTK init on niri/river)
- Disable WebKit bubblewrap sandbox in AppImage context (FUSE mount breaks it → blank screen)
- Set GTK_IM_MODULE to built-in simple context in AppImage (prevents host module conflicts)

RSS proxy:
- Add 32 missing domains to allowlist (EuroNews lang variants, international news feeds)
- Normalize www prefix in domain validation (prevents entire class of www/non-www mismatch 403s)

Console cleanup:
- TrendingKeywords: console.log → console.debug for suppressed terms (30+ noisy log lines)
- PostHog: add ui_host for reverse proxy setup (fixes /ingest/ 404s)
2026-02-26 09:37:26 +04:00
Elie Habib
3983278f53 feat: dynamic sidecar port with EADDRINUSE fallback + let scoping bug (#375)
* feat: dynamic sidecar port with EADDRINUSE fallback

Rust probes port 46123 via TcpListener::bind; if busy, binds port 0 for
an OS-assigned ephemeral port. The actual port is stored in LocalApiState,
passed to sidecar via LOCAL_API_PORT env, and exposed to frontend via
get_local_api_port IPC command.

Frontend resolves the port lazily on first API call (with retry-on-failure
semantics) and caches it. All hardcoded 46123 references replaced with
dynamic getApiBaseUrl()/getLocalApiPort() accessors. CSP connect-src
broadened to http://127.0.0.1:* (frame-src unchanged).

* fix: scope CSP to desktop builds and eliminate port TOCTOU race

P1: Remove http://127.0.0.1:* from index.html (web build CSP). The
wildcard allowed web app JS to probe arbitrary localhost services.
Vite's htmlVariantPlugin now injects localhost CSP only when
VITE_DESKTOP_RUNTIME=1 (desktop builds).

P2: Replace Rust probe_available_port() (bind→release→spawn race)
with a confirmed port handshake. Sidecar now handles EADDRINUSE
fallback internally and writes the actual bound port to a file.
Rust polls the port file (up to 5s) to store only the confirmed port.

* fix: isSafeUrl ReferenceError — addresses scoped inside try block

`let addresses = []` was declared inside the outer `try` block but
referenced after the `catch` on line 200. `let` is block-scoped so
every request through isSafeUrl crashed with:
  ReferenceError: addresses is not defined

Move the declaration before the `try` so it's in scope for the return.
2026-02-26 08:51:59 +04:00
Elie Habib
07d0803014 Add WTO trade policy intelligence service with tariffs, flows, and barriers (#364)
* feat: add WTO trade policy service with 4 RPC endpoints and TradePolicyPanel

Adds a new `trade` RPC domain backed by the WTO API (apiportal.wto.org) for
trade policy intelligence: quantitative restrictions, tariff timeseries,
bilateral trade flows, and SPS/TBT barrier notifications.

New files: 6 protos, generated server/client, 4 server handlers + shared WTO
fetch utility, client service with circuit breakers, TradePolicyPanel (4 tabs),
and full API key infrastructure (Rust keychain, sidecar, runtime config).

Panel registered for FULL and FINANCE variants with data loader integration,
command palette entry, status panel tracking, data freshness monitoring, and
i18n across all 17 locale files.

https://claude.ai/code/session_01HZXyoQp6xK3TX8obDzv6Ye

* chore: update package-lock.json

https://claude.ai/code/session_01HZXyoQp6xK3TX8obDzv6Ye

* fix: move tab click listener to constructor to prevent leak

The delegated click handler was added inside render(), which runs
on every data update (4× per load cycle). Since the listener targets
this.content (a persistent container), each call stacked a duplicate
handler. Moving it to the constructor binds it exactly once.

https://claude.ai/code/session_01HZXyoQp6xK3TX8obDzv6Ye

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-25 10:50:12 +00:00
Elie Habib
e3bdde0d92 chore: bump version to 2.5.8 and align README with recent features (#359)
Update data layer count to 36+, add Happy Monitor variant to Live Demos,
expand Cmd+K command palette description, and add trade routes to
Infrastructure section.
2026-02-25 08:12:39 +00:00
Elie Habib
408d5d3374 security: harden IPC, gate DevTools, isolate external windows, exempt /api/version (#348)
* security: harden IPC commands, gate DevTools, and isolate external windows

- Remove devtools from default Tauri features; gate behind opt-in
  Cargo feature so production builds never expose DevTools
- Add IPC origin validation (require_trusted_window) to 9 sensitive
  commands: get_secret, get_all_secrets, set_secret, delete_secret,
  get_local_api_token, read/write/delete_cache_entry, fetch_polymarket
- Isolate youtube-login window into restricted capability (core:window
  only) — prevents external-origin webview from invoking app commands
- Add 5-minute TTL to cached sidecar auth token in fetch patch closure
- Document renderer trust boundary threat model in runtime.ts

* docs: add contributors, security acknowledgments, and desktop security policy

- Add Contributors section to README with all 16 GitHub contributors
- Add Security Acknowledgments crediting Cody Richard for 3 disclosures
- Update SECURITY.md with desktop runtime security model (Tauri IPC
  origin validation, DevTools gating, sidecar auth, capability isolation,
  fetch patch trust boundary)
- Add Tauri-specific items to security report scope
- Correct API key storage description to cover both web and desktop

* fix: exempt /api/version from bot-blocking middleware

The desktop update check and sidecar requests were getting 403'd by the
middleware's bot UA filter (curl/) and short UA check.
2026-02-25 06:14:16 +00:00
Elie Habib
fb6c61d4d9 fix: suppress notification sound when alerts disabled + bump v2.5.7 (#346)
* fix: suppress notification sound when popup alerts are disabled

Badge playSound() was firing on new findings regardless of the
"Pop up new alerts" toggle. Gate sound on popupEnabled so both
the modal and audio respect the user preference.

* chore: bump version to 2.5.7 with changelog

## What's Changed

### Performance
- perf: defer YouTube/map init and stagger data loads (#287)

### Features
- feat: universal country detection — CII scoring for all countries (#344)
- feat: add Mexico as CII hotspot (#327)
- feat: add Mexico and LatAm security feeds (#325)
- feat: add category pills and search filter to Panels tab (#322)
- feat: consolidate settings into unified tabbed modal (#319)
- feat: optional channels with tab-based region browse UI (#295)
- feat: custom channel management (#282)

### Bug Fixes
- fix: suppress notification sound when popup alerts are disabled
- fix: prevent entity conflation in pane summarization (#341)
- fix: add Mexico to COUNTRY_BOUNDS and COUNTRY_ALIASES (#338)
- fix: OpenSky cache TTLs, serialization, and auth resilience (#329-#333)
- fix: replace RSSHub feeds with native/Google News alternatives (#331)
- fix: replace HTML5 drag API with mouse events for WKWebView (#313)
- fix: sync YouTube mute state with native player controls (#285)
- fix: strip Ollama reasoning tokens from summaries (#299)
- fix: infra cost optimizations (#275, #283)
- fix: circuit breaker persistent cache (#281)
- fix: immediately refresh stale services on tab focus (#277)

### Security
- Security hardening: SSRF protection, auth gating, token generation (#343)
- Harden Railway relay auth, caching, and proxy routing (#320)
- Build/runtime hardening and dependency security updates (#286)
2026-02-25 00:05:31 +00:00
Elie Habib
9ef3cd8402 perf: defer YouTube/map init and stagger data loads to reduce blocking time (#287) (#345)
- Defer YouTube player init via IntersectionObserver + requestIdleCallback
  gate with clickable placeholder (no eager iframe_api load)
- Stagger loadAllData() into 3 priority tiers: critical (immediate),
  important (after rAF yield), deferred (requestIdleCallback fire-and-forget)
- Move DeckGL supercluster rebuilds into map 'load' callback
- Cancel deferred tier-3 callbacks on App destroy (prevents post-teardown work)
- Add bot-check detection with YouTube sign-in window for desktop (Tauri)
- Safe DOM construction for all new UI paths (no innerHTML with user data)
2026-02-24 23:54:40 +00:00
Elie Habib
e35f0f70e9 Security hardening: SSRF protection, auth gating, and token generation (#343)
* security: block SSRF and enforce global auth on sidecar endpoints

Addresses trust boundary vulnerabilities in the desktop sidecar's
locally-exposed API server (127.0.0.1:46123) reported in
"Breaking the Trust Boundary in a 14k Star OSINT Dashboard":

- SSRF protection on /api/rss-proxy: block private/reserved IPs
  (127.x, 10.x, 172.16-31.x, 192.168.x, 169.254.x, multicast),
  validate DNS resolution to prevent rebinding, reject non-http(s)
  protocols and URLs with embedded credentials

- Global auth gate: move LOCAL_API_TOKEN check above ALL endpoints
  so /api/rss-proxy, /api/local-status, /api/local-traffic-log,
  /api/local-debug-toggle, and /api/register-interest now require
  authentication (only /api/service-status health check is exempt)

- Cryptographic token generation: replace RandomState-based token
  in main.rs with getrandom crate (OS-backed CSPRNG, 32 bytes)

- Traffic log privacy: strip query strings from logged paths to
  prevent leaking feed URLs and user research patterns

- CORS hardening: tighten worldmonitor.app origin regex from
  (.*\.)? to ([a-z0-9-]+\.)? to block multi-level subdomain spoofing

- 10 new security tests covering auth enforcement on every endpoint,
  SSRF blocking for private IPs/localhost/non-http/credentials,
  health check exemption, and traffic log sanitization

https://claude.ai/code/session_018vNVfwPh25tbZmtiX66KxP

* security: pin resolved IP in rss-proxy to close TOCTOU DNS rebinding window

isSafeUrl() now returns the resolved addresses, and fetchWithTimeout()
accepts a resolvedAddress option that bypasses runtime DNS via a custom
lookup callback (HTTPS) or URL rewrite with Host header (HTTP).
The rss-proxy handler threads the first validated IPv4 through, so the
TCP connection is guaranteed to reach the same IP that passed the
private-range check.

https://claude.ai/code/session_018vNVfwPh25tbZmtiX66KxP

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-24 23:01:19 +00:00
Elie Habib
da680d7397 fix: harden embed postMessage origin check and add channel validation (#302)
* fix: harden desktop embed messaging and secret validation

* fix: harden embed postMessage origin check and add custom channel validation

Security:
- Block wildcard parentOrigin from query params (server-side sanitizer)
- Validate e.origin on incoming postMessage commands in embed
- Remove misleading asset: protocol from allowed list
- Require 2+ markers for Cloudflare challenge detection (drop overly broad 'cloudflare' marker)
- Add ordering comment on isAuthFailure vs isCloudflareChallenge403
- Strengthen embed test assertions with regex + wildcard rejection test

Channel validation:
- Validate YouTube handle format (@<3-30 chars>) before adding
- Verify channel exists on YouTube via /api/youtube/live before adding
- Show "Verifying…" loading state, red border on invalid, offline tolerance
- Return channelExists flag from /api/youtube/live endpoint
2026-02-24 07:40:03 +00:00
Elie Habib
d593fbf413 fix: increase live channels window size to fit channel grid (#301) 2026-02-24 06:53:42 +00:00
Elie Habib
4721fb0873 feat(live): optional channels with tab-based region browse UI (#295)
* fix: add User-Agent and Cloudflare 403 detection to all secret validation probes

Sidecar validation probes were missing User-Agent headers, causing
Cloudflare-fronted APIs (e.g. Wingbits) to return 403 which was
incorrectly treated as an auth rejection. Added CHROME_UA to all 13
probes and isCloudflare403() helper to soft-pass CDN blocks.

* fix: open external links in system browser on Tauri desktop

Tauri WKWebView/WebView2 traps target="_blank" navigation, so news
links and other external URLs silently fail to open. Added a global
capture-phase click interceptor that routes cross-origin links through
the existing open_url Tauri command, falling back to window.open.

* feat(live): add 35 optional channels across 5 regions with tab-based browse UI

Users can now browse and add live YouTube news channels from North America,
Europe, Latin America, Asia, and Africa via a tab-based region card grid
in the Manage Channels modal. Cards are add-only (removal via existing
edit/confirm flow). All dynamic text uses DOM API for XSS safety.
2026-02-24 05:32:49 +00:00
Elie Habib
6271fafd40 feat(live): custom channel management with review fixes (#282)
* feat(live): custom channel management — add/remove/reorder, standalone window, i18n

- Standalone channel management window (?live-channels=1) with list, add form, restore defaults
- LIVE panel: gear icon opens channel management; channel tabs reorderable via DnD
- Row click to edit; custom modal for delete confirmation (no window.confirm)
- i18n for all locales (manage, addChannel, youtubeHandle, displayName, etc.)
- UI: margin between channel list and add form in management window
- settings-window: panel display settings comment in English

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(tauri): channel management in desktop app, dev base_url fix

- Add live-channels.html and live-channels-main.ts for standalone window
- Tauri: open_live_channels_window_command, close_live_channels_window, open live-channels window (WebviewUrl::App or External from base_url)
- LiveNewsPanel: in desktop runtime invoke Tauri command with base_url (window.location.origin) so dev works when Vite runs on a different port than devUrl
- Vite: add liveChannels entry to build input
- capabilities: add live-channels window
- tauri.conf: devUrl 3000 to match vite server.port
- docs: PR_LIVE_CHANNEL_MANAGEMENT.md for PR #276

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: address review issues in live channel management PR

- Revert settings button to open modal (not window.open popup)
- Revert devUrl from localhost:3000 to localhost:5173
- Guard activeChannel against empty channels (fall back to defaults)
- Escape i18n strings in innerHTML with escapeHtml() to prevent XSS
- Only store displayNameOverrides for actually renamed channels
- Use URL constructor for live-channels window URL
- Add CSP meta tag to live-channels.html
- Remove unused i18n keys (edit, editMode, done) from all locales
- Remove unused CSS classes (live-news-manage-btn/panel/wrap)
- Delete PR instruction doc (PR_LIVE_CHANNEL_MANAGEMENT.md)

---------

Co-authored-by: Masaki <yukkurihakutaku@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 22:51:44 +00:00
Elie Habib
a37dced84e fix: circuit breaker persistent cache with safety fixes (#281)
* fix: persist circuit breaker cache to IndexedDB across page reloads

On page reload, all 28+ circuit breaker in-memory caches are lost,
triggering 20-30 simultaneous POST requests to Vercel edge functions.

Wire the existing persistent-cache.ts (IndexedDB + localStorage +
Tauri fallback) into CircuitBreaker so every breaker automatically:

- Hydrates from IndexedDB on first execute() call (~1-5ms read)
- Writes to IndexedDB fire-and-forget on every recordSuccess()
- Falls back to stale persistent data on network failure
- Auto-disables for breakers with cacheTtlMs=0 (live pricing)

Zero consumer code changes -- all 28+ breaker call sites untouched.
Reloads within the cache TTL (default 10min) serve instantly from
IndexedDB with zero network calls.

Also adds deletePersistentCache() to persistent-cache.ts for clean
cache invalidation via clearCache().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add Playwright e2e tests for circuit breaker persistent cache

7 tests covering: IndexedDB persistence on success, hydration on new
instance, TTL expiry forcing fresh fetch, 24h stale ceiling rejection,
clearCache cleanup, cacheTtlMs=0 auto-disable, and network failure
fallback to stale persistent data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: desktop cache deletion + clearCache race condition

P1: deletePersistentCache sent empty string to write_cache_entry,
which fails Rust's serde_json::from_str (not valid JSON). Add
dedicated delete_cache_entry Tauri command that removes the key
from the in-memory HashMap and flushes to disk.

P2: clearCache() set persistentLoaded=false, allowing a concurrent
execute() to re-hydrate stale data from IndexedDB before the async
delete completed. Remove the reset — after explicit clear there is
no reason to re-hydrate from persistent storage.

* fix: default persistCache to false, fix falsy data guard

P1b: 6 breakers store Date objects (weather, aviation, ACLED,
military-flights, military-vessels, GDACS) which become strings
after JSON round-trip. Callers like MapPopup.getTimeUntil() call
date.getTime() on hydrated strings → TypeError. Change default
to false (opt-in) so persistence requires explicit confirmation
that the payload is JSON-safe.

P2: `if (!entry?.data) return` drops valid falsy payloads (0,
false, empty string). Use explicit null/undefined check instead.

* fix: address blocking review issues on circuit breaker persistence

- clearCache() nulls persistentLoadPromise to orphan in-flight hydration
- delete_cache_entry defers disk flush to exit handler (avoids 14MB sync write)
- hydratePersistentCache checks TTL before setting lastDataState to 'cached'
- deletePersistentCache resets cacheDbPromise on IDB error + logs warning
- hydration catch logs warning instead of silently swallowing
- deletePersistentCache respects isStorageQuotaExceeded() for localStorage

---------

Co-authored-by: Elias El Khoury <efk@anghami.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:35:45 +00:00
Elie Habib
eafc4cb955 chore: bump version to 2.5.6 with changelog (#272) 2026-02-23 18:50:43 +00:00
Elie Habib
f06acd3c88 Fix GLib symbol mismatch when running as AppImage on newer distros (#263)
* fix: resolve AppImage crash on Ubuntu 25.10+ (GLib symbol mismatch)

The AppImage bundles GLib from the build system, but host GIO modules
(e.g. GVFS libgvfsdbus.so) compiled against a newer GLib reference
symbols like g_task_set_static_name that don't exist in the older
bundled copy, causing "undefined symbol" errors and WebKit crashes.

Set GIO_MODULE_DIR="" when running as AppImage to prevent host GIO
modules from loading against the incompatible bundled GLib. GVFS
features (network mounts, trash, MTP) are unused by this app.

Note: the CI should also be upgraded from ubuntu-22.04 to ubuntu-24.04
in .github/workflows/build-desktop.yml to ship GLib 2.80+ and extend
forward-compatibility. This requires workflows permission to push.

https://claude.ai/code/session_01J8HBrfb26GJm22MFCeGoAA

* fix(appimage): keep bundled GIO modules for Ubuntu 25.10

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-23 17:07:37 +00:00
Elie Habib
080f49b0b2 fix: allow http:// origin from tauri.localhost for Windows CORS (#262)
On Windows, Tauri webviews send requests with origin
`http://tauri.localhost` (HTTP), but the CORS allowlist only permitted
`https://tauri.localhost` (HTTPS). This caused every sidecar API
request to be blocked by CORS, making the app non-functional on
Windows with a "sidecar not reachable" error.

Change the regex from `^https:` to `^https?:` so both HTTP and HTTPS
origins from tauri.localhost are accepted.

https://claude.ai/code/session_016XMWtTPfE81bitu3QEoUwy

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-23 17:07:04 +00:00
Elie Habib
8504d5649a fix: layer help, SW ingest routing, toggle colors, v2.5.5 (#244)
* feat: make intelligence alert popup opt-in via dropdown toggle

Auto-popup was interrupting users every 10s refresh cycle. Badge still
counts and pulses silently. New toggle in dropdown (default OFF) lets
users explicitly opt in to auto-popup behavior.

* chore: bump version to 2.5.5

## Changelog

### Features
- Intelligence alert popup is now opt-in (default OFF) — badge counts silently, toggle in dropdown to enable auto-popup

### Bug Fixes
- Linux: disable DMA-BUF renderer on WebKitGTK to prevent blank white screen (NVIDIA/immutable distros)
- Linux: add DejaVu Sans Mono + Liberation Mono font fallbacks for monospace rendering
- Consolidate monospace font stacks into --font-mono CSS variable (fixes undefined var bug)
- Reduce dedup coordinate rounding from 0.5° to 0.1° (~10km precision)
- Vercel build: handle missing previous deploy SHA
- Panel base class: add missing showRetrying method
- Vercel ignoreCommand shortened to fit 256-char limit

### Infrastructure
- Upstash Redis shared caching for all RPC handlers + cache key contamination fix
- Format Rust code and fix Windows focus handling

### Docs
- Community guidelines: contributing, code of conduct, security policy
- Updated .env.example

* chore: track Cargo.lock for reproducible Rust builds

* fix: update layer help popup with all current map layers

Added missing layers to the ? help popup across all 3 variants:
- Full: UCDP Events, Displacement, Spaceports, Cyber Threats, Fires,
  Climate Anomalies, Critical Minerals; renamed Shipping→Ship Traffic
- Tech: Tech Events, Cyber Threats, Fires
- Finance: GCC Investments

* docs: update README with crypto prices, analytics, typography, and dedup grid fix

* fix: add /ingest to service worker NetworkOnly routes

The SW was intercepting PostHog /ingest/* requests and returning
no-response (404) because no cache match existed. Adding NetworkOnly
ensures analytics requests pass through to Vercel's rewrite proxy.

* chore: update Cargo.lock for v2.5.5

* fix: use explicit colors for findings toggle switch visibility
2026-02-23 08:01:46 +00:00
Elie Habib
d24d61f333 Fix Linux rendering issues and improve monospace font fallbacks (#243)
* fix: resolve AppImage blank white screen and font crash on Linux (#238)

Disable WebKitGTK DMA-BUF renderer by default on Linux to prevent blank
white screens caused by GPU buffer allocation failures (common with
NVIDIA drivers and immutable distros like Bazzite). Add Linux-native
monospace font fallbacks (DejaVu Sans Mono, Liberation Mono) to all font
stacks so WebKitGTK font resolution doesn't hit out-of-bounds vector
access when macOS-only fonts (SF Mono, Monaco) are unavailable.

https://claude.ai/code/session_01TF2NPgSSjgenmLT2XuR5b9

* fix: consolidate monospace font stacks into --font-mono variable

- Define --font-mono in :root (main.css) and .settings-shell (settings-window.css)
- Align font stack: SF Mono, Monaco, Cascadia Code, Fira Code, DejaVu Sans Mono, Liberation Mono
- Replace 3 hardcoded JetBrains Mono stacks with var(--font-mono)
- Replace 4 hardcoded settings-window stacks with var(--font-mono)
- Fix pre-existing bug: var(--font-mono) used in 4 places but never defined
- Match index.html skeleton font stack to --font-mono

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-23 07:16:48 +00:00
Elie Habib
63a4c9ab9c feat: Upstash Redis shared caching + cache key contamination fixes (#232)
* fix(sentry): add noise filters for 5 non-actionable error patterns

Filter dynamic import alt phrasing, script parse errors, maplibre
style/WebGL crashes, and CustomEvent promise rejections. Also fix
beforeSend to catch short Firefox null messages like "E is null".

* fix: cache write race, settings stale key status, yahoo gate concurrency

P1: Replace async background thread cache write with synchronous fs::write
to prevent out-of-order writes and dirty flag cleared before persistence.

P2: Add WorldMonitorTab.refresh() called after loadDesktopSecrets() so
the API key badge reflects actual keychain state.

P3: Replace timestamp-based Yahoo gate with promise queue to ensure
sequential execution under concurrent callers.

* feat: add Upstash Redis shared caching to all RPC handlers + fix cache key contamination

- Add Redis L2 cache (getCachedJson/setCachedJson) to 28 RPC handlers
  across all service domains (market, conflict, cyber, economic, etc.)
- Fix 10 P1 cache key contamination bugs where under-specified keys
  caused cross-request data pollution (e.g. filtered requests returning
  unfiltered cached data)
- Restructure list-internet-outages to cache-then-filter pattern so
  country/timeRange filters always apply after cache read
- Add write_lock mutex to PersistentCache in main.rs to prevent
  desktop cache write-race conditions
- Document FMP (Financial Modeling Prep) as Yahoo Finance fallback TODO
  in market/v1/_shared.ts

* fix: cache-key contamination and PizzINT/GDELT partial-failure regression

- tech-events: fetch with limit=0 and cache full result, apply limit
  slice after cache read to prevent low-limit requests poisoning cache
- pizzint: restore try-catch around PizzINT fetch so GDELT tension
  pairs are still returned when PizzINT API is down

* fix: remove extra closing brace in pizzint try-catch

* fix: recompute conferenceCount/mappableCount after limit slice

* fix: bypass WM API key gate for registration endpoint

/api/register-interest must reach cloud without a WorldMonitor API key,
otherwise desktop users can never register (circular dependency).
2026-02-23 10:09:12 +04:00
Elie Habib
8f8b0c843b Format Rust code and fix Windows focus handling (#242)
* chore: apply cargo fmt formatting to main.rs

Pure formatting normalization with no logic changes. Separated from
the behavioral fix to keep git blame clean.

https://claude.ai/code/session_01RPQ1PEqxTSEG6rB5XadzEz

* fix: restrict settings-window re-focus to macOS to avoid Windows focus churn

On Windows, the Focused(true) handler on the main window calls
show()+set_focus() on the settings window, which steals focus back,
retriggering the event in a tight loop and presenting as a UI hang.

Gate the match arm with #[cfg(target_os = "macos")] (compile-time
attribute) instead of cfg!() (runtime macro) to match the convention
used by the adjacent macOS-only handlers and eliminate dead code on
non-macOS builds entirely.

https://claude.ai/code/session_01RPQ1PEqxTSEG6rB5XadzEz

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-23 05:29:49 +00:00
Elie Habib
84f17e6c4c chore: bump version to 2.5.4
## Changelog

### Bug Fixes
- market: Fix price falsy bug (price of 0 treated as null)
- market: Per-symbol-set caching prevents stock/commodity data leakage
- market: Yahoo request gate (600ms) reduces IP-level rate limiting
- market: ETF panel 8s delayed fetch reduces Yahoo contention on startup
- ucdp: Clear circuit breaker cache on empty responses
- ucdp: Retry loop (3 attempts, 15s) for cold start resilience
- ucdp: Negative cache, version cache, stale-on-error fallback
- analytics: Proxy PostHog through own domain to bypass ad blockers
- settings: Skip API key re-verification when no keys changed
- csp: Allow PostHog scripts from us-assets.i.posthog.com
- api: Sanitize og-story level input
- api: Restore API-key gate on config import failure

### Features
- Cable health scoring via sebuf InfrastructureService
- PostHog analytics with privacy-first design

### i18n
- Cable health evidence key added to all locales
2026-02-22 09:17:18 +00:00
Elie Habib
e1b9e9e8a9 fix(csp): allow PostHog scripts from us-assets.i.posthog.com 2026-02-21 17:06:53 +00:00
Elie Habib
8699dae5e5 fix: registration via direct Convex call + compact WM tab layout
- Sidecar calls Convex HTTP API directly (Vercel Attack Challenge Mode
  blocks server-side proxy). CONVEX_URL read from env, not hardcoded.
- Rust injects CONVEX_URL into sidecar via option_env! (CI) / env var (dev)
- GitHub Actions passes CONVEX_URL secret to all 4 build steps
- Tighten WM tab CSS spacing so all content fits in one viewport
2026-02-21 11:36:03 +00:00
Elie Habib
1fc6dc2dbb fix: World Monitor tab first, registration proxy, empty key guard
- Move World Monitor tab to first position in settings.html
- Add registration proxy in sidecar to bypass Vercel bot protection
- Fix sidecar RSS/registration handlers to use response.text()
- Skip empty values in loadDesktopSecrets (NO LICENSE vs LICENSED)
- Add skip-setup text to desktop config alert panel
2026-02-21 11:09:18 +00:00
Elie Habib
68e6a367d6 feat: redesign settings World Monitor tab + sidecar RSS proxy + v2.5.3
Rebuild the World Monitor settings tab with hero banner, license key
input, waitlist registration, and BYOK footer. Only validate API key
panels that have pending changes on save. Add local RSS proxy handler
to sidecar so desktop fetches feeds directly without cloud fallback.
Bump version to 2.5.3.
2026-02-21 11:01:01 +00:00
Elie Habib
a388afe400 feat: API key gating for desktop cloud fallback + registration (#215)
* feat: API key gating for desktop cloud fallback + registration system

Gate desktop cloud fallback behind WORLDMONITOR_API_KEY — desktop users
need a valid key for cloud access, otherwise operate local-only (sidecar).
Add email registration system via Convex DB for future key distribution.

Client-side: installRuntimeFetchPatch() checks key presence before
allowing cloud fallback, with secretsReady promise + 2s timeout.
Server-side: origin-aware validation in sebuf gateway — desktop origins
require key, web origins pass through.

- Add WORLDMONITOR_API_KEY to 3-place secret system (Rust, TS, sidecar)
- New "World Monitor" settings tab with key input + registration form
- New api/_api-key.js server-side validation (origin-aware)
- New api/register-interest.js edge function with rate limiting
- Convex DB schema + mutation for email registration storage
- CORS headers updated for X-WorldMonitor-Key + Authorization
- E2E tests for key gate (blocked without key, allowed with key)
- Deployment docs (API_KEY_DEPLOYMENT.md) + updated desktop config docs

* fix: harden worldmonitor key + registration input handling

* fix: show invalid WorldMonitor API key status

* fix: simplify key validation, trim registration checks, add env example vars

- Inline getValidKeys() in _api-key.js
- Remove redundant type checks in register-interest.js
- Simplify WorldMonitorTab status to present/missing
- Add WORLDMONITOR_VALID_KEYS and CONVEX_URL to .env.example

* feat(sidecar): integrate proto gateway bundle into desktop build

The sidecar's buildRouteTable() only discovers .js files, so the proto
gateway at api/[domain]/v1/[rpc].ts was invisible — all 45 sebuf RPCs
returned 404 in the desktop app. Wire the existing build script into
Tauri's build commands and add esbuild as an explicit devDependency.
2026-02-21 10:36:23 +00:00
Lawyered
f082e09d55 fix(sidecar): preserve request body on cloud fallback (#209)
Cache inbound request body once so local handler dispatch and cloud fallback can both access the same payload. Adds regression coverage for POST fallback after a local non-OK response.
2026-02-21 04:10:53 +04:00