Skip to content

fix(header): add energy.worldmonitor.app to CSP frame-src and frame-ancestors#3359

Closed
fuleinist wants to merge 3 commits into
koala73:mainfrom
fuleinist:fix/broken-energy-link
Closed

fix(header): add energy.worldmonitor.app to CSP frame-src and frame-ancestors#3359
fuleinist wants to merge 3 commits into
koala73:mainfrom
fuleinist:fix/broken-energy-link

Conversation

@fuleinist

Copy link
Copy Markdown
Contributor

Summary

The Energy variant link in the header navigation bar points to https://energy.worldmonitor.app but that domain was missing from the Vercel Content-Security-Policy frame-src and frame-ancestors directives. This causes the browser to block the iframe embedding of that domain when the app is embedded in iframes (e.g. via widget embeds).

The Happy variant worked because happy.worldmonitor.app was already included in the allow-list.

Fix

Added https://energy.worldmonitor.app to both the frame-src and frame-ancestors CSP directives in vercel.json.

Testing

  • Confirmed energy.worldmonitor.app was not reachable via DNS (the Vercel project likely needs to be set up separately)
  • Confirmed the CSP was the only missing piece by comparing with the Happy variant which had the same pattern
  • The fix is purely declarative in vercel.json and requires a Vercel redeploy

Fixes: #3339

Replicate legacy NEG_TTL=30000 behaviour in list-military-flights
fetchStaleFallback. After a failed live fetch, do not immediately
re-read the stale Redis key — that just wastes an Upstash call if
OpenSky/the relay is still down. Suppress stale-read attempts for
30 s after the last failed live fetch attempt.

Fixes koala73#3277
Add _retryCount parameter (default 0) to sendTelegram and guard against
unbounded recursion when Telegram returns sustained 429 responses.

- Bail with warn log if _retryCount >= 1
- Pass _retryCount + 1 on recursive call
- Fixes koala73#3060
…ncestors

The energy variant link in the header navigation points to
https://energy.worldmonitor.app but that domain was missing from the
Content-Security-Policy frame-src and frame-ancestors directives.
Happy variant worked because it was already allow-listed.

Fixes: koala73#3339
@vercel

vercel Bot commented Apr 24, 2026

Copy link
Copy Markdown

@fuleinist is attempting to deploy a commit to the World Monitor Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the trust:safe Brin: contributor trust score safe label Apr 24, 2026
@greptile-apps

greptile-apps Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR bundles three independent fixes: the primary CSP change adding https://energy.worldmonitor.app to frame-src and frame-ancestors in vercel.json, a Telegram 429 retry-guard in notification-relay.cjs, and a negative-TTL stale-fallback guard in list-military-flights.ts.

  • The CSP fix and the Telegram retry guard are correct and safe to merge.
  • The negative-TTL guard in list-military-flights.ts sets lastStaleAttempt before calling fetchStaleFallback(), so a successful stale read suppresses further stale reads for 30 s — users see empty flight data on subsequent requests even though valid stale data is in Redis.

Confidence Score: 4/5

Safe to merge for the CSP and Telegram fixes; the negative-TTL logic in list-military-flights.ts has a P1 defect that should be corrected first.

One P1 finding: the lastStaleAttempt guard is applied before the stale fetch resolves, causing users to receive empty flight data for up to 30 s even when stale data is available in Redis. The CSP and Telegram changes are correct.

server/worldmonitor/military/v1/list-military-flights.ts — the lastStaleAttempt update placement needs to be moved after a failed stale read.

Important Files Changed

Filename Overview
vercel.json Added https://energy.worldmonitor.app to both frame-src and frame-ancestors CSP directives, consistent with the pattern already used for happy.worldmonitor.app and other variants.
scripts/notification-relay.cjs Added a _retryCount guard to sendTelegram to prevent unbounded recursion on Telegram 429 responses; the single-retry limit is correctly enforced before the async wait.
server/worldmonitor/military/v1/list-military-flights.ts Adds a 30 s negative-TTL guard on the stale Redis fallback, but lastStaleAttempt is updated before the stale fetch completes, causing empty results to be returned for 30 s even when stale data is available in Redis.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[listMilitaryFlights request] --> B[cachedFetchJson - live Redis key]
    B -->|hit| C[Filter to bounds & return]
    B -->|miss / null| D{now - lastStaleAttempt < 30s?}
    D -->|yes| E[return flights: empty ⚠️ even if stale data exists]
    D -->|no| F[lastStaleAttempt = now]
    F --> G[fetchStaleFallback]
    G -->|has data| H[Filter to bounds & return stale flights]
    G -->|null / empty| I[return flights: empty]
    H -.->|next 30s requests also fail live| E
Loading

Reviews (1): Last reviewed commit: "fix(header): add energy.worldmonitor.app..." | Re-trigger Greptile

Comment on lines +280 to 283
lastStaleAttempt = now;
const staleFlights = await fetchStaleFallback();
if (staleFlights && staleFlights.length > 0) {
return { flights: filterFlightsToBounds(staleFlights, requestBounds), clusters: [], pagination: undefined };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stale fallback suppressed for 30 s even after a successful stale read

lastStaleAttempt is set unconditionally (line 280) before the async stale fetch, so if fetchStaleFallback() returns valid flights on request N, every subsequent request within the next 30 s that also fails the live path hits the NEG_TTL guard and returns { flights: [] } — even though the stale Redis key still holds valid data. Users see empty flight data for up to 30 s when stale data is available.

The guard's stated purpose is to avoid a redundant Redis call "if the upstream is still down"; it should therefore only update lastStaleAttempt when the stale read yields nothing:

const staleFlights = await fetchStaleFallback();
if (staleFlights && staleFlights.length > 0) {
  return { flights: filterFlightsToBounds(staleFlights, requestBounds), clusters: [], pagination: undefined };
}
// Stale key also empty — start the suppression window now.
lastStaleAttempt = Date.now();


/** Negative TTL: suppress re-fetch of stale key for 30 s after a failed live fetch. */
const NEG_TTL_MS = 30_000;
let lastStaleAttempt = 0; // Unix-ms timestamp of most recent stale-read attempt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Module-level singleton not effective across server instances

lastStaleAttempt is a process-local variable. In a horizontally-scaled deployment each instance maintains its own suppression window, so N instances can all independently fire stale Redis reads within the same 30 s window. If reducing per-key Redis pressure is important at scale, consider a short-lived Redis key (e.g. SET military:flights:stale:neg-ttl PX 30000 NX) as the suppression token instead.

@koala73

koala73 commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Superseded by #3394 — the CSP-only portion of the energy.worldmonitor.app wiring landed there along with the matching middleware/checkout/Tauri/favicon edits. The unrelated changes in this branch (telegram retry guard, military flights handler) should ship in their own PR if still wanted.

@koala73 koala73 closed this Apr 25, 2026
koala73 added a commit that referenced this pull request Apr 25, 2026
…3394)

DNS (Cloudflare) and the Vercel domain are already provisioned by the
operator; this lands the matching code-side wiring so the variant
actually resolves and renders correctly.

Changes:

middleware.ts
- Add `'energy.worldmonitor.app': 'energy'` to VARIANT_HOST_MAP. This
  also auto-includes the host in ALLOWED_HOSTS via the spread on
  line 87.
- Add `energy` entry to VARIANT_OG with the Energy-Atlas-specific
  title + description from `src/config/variant-meta.ts:130-152`. OG
  image points at `https://energy.worldmonitor.app/favico/energy/og-image.png`,
  matching the per-variant convention used by tech / finance /
  commodity / happy.

vercel.json
- Add `https://energy.worldmonitor.app` to BOTH `frame-src` and
  `frame-ancestors` in the global Content-Security-Policy header.
  Without this, the variant subdomain would render but be blocked
  from being framed back into worldmonitor.app for any embedded
  flow (Slack/LinkedIn previews, future iframe widgets, etc.).
  This supersedes the CSP-only portion of PR #3359 (which mixed
  CSP with unrelated relay/military changes).

convex/payments/checkout.ts:108-117
- Add `https://energy.worldmonitor.app` to the checkout returnUrl
  allowlist. Without this, a PRO upgrade flow initiated from the
  energy subdomain would fail with "Invalid returnUrl" on Convex.

src-tauri/tauri.conf.json:32
- Add `https://energy.worldmonitor.app` to the Tauri desktop CSP
  frame-src so the desktop app can embed the variant the same way
  it embeds the other 4.

public/favico/energy/* (NEW, 7 files)
- Stub the per-variant favicon directory by copying the root-level
  WorldMonitor brand assets (android-chrome 192/512, apple-touch,
  favicon 16/32/ico, og-image). This keeps the launch unblocked
  on design assets — every referenced URL resolves with valid
  bytes from day one. Replace with energy-themed designs in a
  follow-up PR; the file paths are stable.

Other variant subdomains already on main (tech / finance / commodity /
happy) are unchanged. APP_HOSTS in src/services/runtime.ts already
admits any `*.worldmonitor.app` via `host.endsWith('.worldmonitor.app')`
on line 226, so no edit needed there.

Closes gaps §L #9, #10, #11 in
docs/internal/energy-atlas-registry-expansion.md.
@fuleinist
fuleinist deleted the fix/broken-energy-link branch April 25, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

trust:safe Brin: contributor trust score safe

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Broken Link

2 participants