Release 0.13.0-alpha #39

Merged
uz1mani merged 22 commits from staging into main 2026-03-02 22:51:53 +00:00
uz1mani commented 2026-03-02 22:38:12 +00:00 (Migrated from github.com)

Summary

  • SWR dashboard migration, security hardening, expanded settings, and 15+ bug fixes
  • Migrated dashboard data-fetching to SWR with factory pattern, eliminating 13 duplicate endpoint functions and all any[] state
  • Added automated test suite, CSP/CORS hardening, refresh token flow, and cross-tab session sync

Changes

  • SWR migration (F-10, F-21): Replaced 13 manual fetch functions in stats.ts with factory pattern using useSWR; dashboard sections load independently with background refresh and tab-aware polling (stats.ts, dashboard.ts, [id]/page.tsx)
  • Stable React keys (F-9): Dashboard lists use data-derived keys instead of array indices (ContentStats, Locations, TechSpecs, TopReferrers)
  • Settings page: Expandable sidebar, security activity log, trusted devices, email notification preferences (SettingsPageClient.tsx, SecurityActivityCard.tsx, TrustedDevicesCard.tsx)
  • Auth improvements: Refresh token flow (auth.ts, refresh/route.ts), cross-tab sign-out sync, session expiration warning, auth context effect deps stabilized (context.tsx)
  • CSP & service worker: Added ciphera.net, *.gstatic.com, google.com to img-src and connect-src; ESM import for next-pwa (F-5); removed invalid icon files (next.config.ts)
  • Security: Full CSP directives, CORS restricted to known origins, environment secret validation at startup, admin input sanitization, error messages no longer expose internals
  • Testing: Unit tests for middleware, online status, visibility polling, logger, request ID; CI workflow (.github/workflows/test.yml, vitest.config.ts)
  • Request ID tracing: X-Request-ID propagated across frontend and backend (requestId.ts, client.ts)
  • Cookie dedup (F-11, F-18): Shared getCookieDomain utility (cookies.ts); login page loading overlay (login/page.tsx)
  • Funnel date fix: Standardized to YYYY-MM-DD across all analytics (funnels.ts)
  • Dead code cleanup: Deleted PasswordInput, WebsiteFooter, WorkspaceSwitcher, Countries, TopPages, errorHandler; removed invalid PWA icon files
  • Deps: Bumped @ciphera-net/ui to ^0.0.79, @types/react to v19
  • Version: Bumped to 0.13.0-alpha; changelog reorganized with correct categories

Test Plan

  • Dashboard loads all sections independently (pages, referrers, locations, devices, campaigns, goals)
  • App switcher icons and site favicons display after clearing cache
  • Real-time visitor count polls without 429 errors
  • Funnel detail pages load with correct date handling
  • Settings: security activity, trusted devices, notification preferences functional
  • Cross-tab sign-out and session expiration warning work
  • CSP headers present on all responses (DevTools → Network)
  • npm run test and npm run build pass
## Summary - SWR dashboard migration, security hardening, expanded settings, and 15+ bug fixes - Migrated dashboard data-fetching to SWR with factory pattern, eliminating 13 duplicate endpoint functions and all `any[]` state - Added automated test suite, CSP/CORS hardening, refresh token flow, and cross-tab session sync ## Changes - **SWR migration (F-10, F-21):** Replaced 13 manual fetch functions in `stats.ts` with factory pattern using `useSWR`; dashboard sections load independently with background refresh and tab-aware polling (`stats.ts`, `dashboard.ts`, `[id]/page.tsx`) - **Stable React keys (F-9):** Dashboard lists use data-derived keys instead of array indices (`ContentStats`, `Locations`, `TechSpecs`, `TopReferrers`) - **Settings page:** Expandable sidebar, security activity log, trusted devices, email notification preferences (`SettingsPageClient.tsx`, `SecurityActivityCard.tsx`, `TrustedDevicesCard.tsx`) - **Auth improvements:** Refresh token flow (`auth.ts`, `refresh/route.ts`), cross-tab sign-out sync, session expiration warning, auth context effect deps stabilized (`context.tsx`) - **CSP & service worker:** Added `ciphera.net`, `*.gstatic.com`, `google.com` to `img-src` and `connect-src`; ESM import for next-pwa (F-5); removed invalid icon files (`next.config.ts`) - **Security:** Full CSP directives, CORS restricted to known origins, environment secret validation at startup, admin input sanitization, error messages no longer expose internals - **Testing:** Unit tests for middleware, online status, visibility polling, logger, request ID; CI workflow (`.github/workflows/test.yml`, `vitest.config.ts`) - **Request ID tracing:** `X-Request-ID` propagated across frontend and backend (`requestId.ts`, `client.ts`) - **Cookie dedup (F-11, F-18):** Shared `getCookieDomain` utility (`cookies.ts`); login page loading overlay (`login/page.tsx`) - **Funnel date fix:** Standardized to `YYYY-MM-DD` across all analytics (`funnels.ts`) - **Dead code cleanup:** Deleted `PasswordInput`, `WebsiteFooter`, `WorkspaceSwitcher`, `Countries`, `TopPages`, `errorHandler`; removed invalid PWA icon files - **Deps:** Bumped `@ciphera-net/ui` to `^0.0.79`, `@types/react` to v19 - **Version:** Bumped to `0.13.0-alpha`; changelog reorganized with correct categories ## Test Plan - [x] Dashboard loads all sections independently (pages, referrers, locations, devices, campaigns, goals) - [x] App switcher icons and site favicons display after clearing cache - [x] Real-time visitor count polls without 429 errors - [x] Funnel detail pages load with correct date handling - [x] Settings: security activity, trusted devices, notification preferences functional - [x] Cross-tab sign-out and session expiration warning work - [x] CSP headers present on all responses (DevTools → Network) - [x] `npm run test` and `npm run build` pass
greptile-apps[bot] commented 2026-03-02 22:55:38 +00:00 (Migrated from github.com)

Greptile Summary

This is a large quality-of-life release that migrates dashboard data-fetching from manual useState/useEffect/setInterval patterns to SWR, collapses ~13 near-identical fetch functions into a factory pattern, and adds CSP/CORS hardening, a shared cookie-domain utility, and refresh token plumbing.

Key changes and findings:

  • SWR migration (app/sites/[id]/page.tsx, lib/swr/dashboard.ts): Dashboard sections now fetch independently with SWR, eliminating any[] state and manual polling. The lazy-initializer pattern for dateRange avoids the previous double-fetch on settings restore. Looks correct overall, with one minor prop-type concern on ExportModal (topPages/topReferrers can be undefined before the first fetch).
  • lib/api/funnels.ts — potentially breaking: getFunnelStats was changed from sending from/to RFC3339 timestamps to start_date/end_date YYYY-MM-DD strings. The original conversion logic was added deliberately and the funnel stats endpoint may have a different API contract than the dashboard/stats endpoints. If the backend hasn't been updated simultaneously, funnel date-range filtering will silently break.
  • lib/api/stats.ts: The createListFetcher factory and buildQuery helper are a clean, correct refactor with no behavioral changes to callers.
  • CSP (next.config.ts): 'unsafe-inline' is present unconditionally in script-src. While this is a practical necessity for the current Next.js setup without nonce support, it should be noted that this effectively nullifies the script-src allowlist for XSS mitigation in production.
  • React keys: All dashboard list components now use data-derived keys (path, referrer, name, composite location) — a genuine improvement. The funnel step key uses step.step.name which could collide for user-named steps with identical names; step.step.order would be safer.
  • Auth/session improvements: Effect dep stabilization in context.tsx, cookie-domain deduplication, and removal of client-side SHA-256 session identification are all clean improvements.

Confidence Score: 3/5

  • Mostly safe to merge, but the funnel stats API parameter change should be verified against the backend before shipping.
  • The SWR migration, factory refactor, and dead-code cleanup are high-quality changes with no obvious correctness issues. The score is held back by the lib/api/funnels.ts change, which silently alters both the parameter names and format sent to the funnel stats backend endpoint — if the backend hasn't been updated in lockstep, funnel date filtering will stop working in production without any visible error.
  • Pay close attention to lib/api/funnels.ts — confirm the backend funnel stats endpoint now accepts start_date/end_date (YYYY-MM-DD) rather than from/to (RFC3339).

Important Files Changed

Filename Overview
lib/api/funnels.ts Changed funnel stats API params from from/to (RFC3339) to start_date/end_date (YYYY-MM-DD) — potentially breaking if the backend endpoint hasn't been updated to match.
lib/api/stats.ts Excellent refactor: 13 near-identical fetch functions collapsed into a createListFetcher factory and a shared buildQuery helper, with no behavioral changes to the public API surface.
app/sites/[id]/page.tsx SWR migration replaces complex manual polling and useState<any[]> with typed SWR hooks; minor issue: topPages/topReferrers passed to ExportModal can be undefined before first fetch.
lib/swr/dashboard.ts Adds useCampaigns hook and propagates interval through useDashboardOverview; clean SWR config with appropriate dedup/refresh intervals.
next.config.ts CSP headers added with sensible defaults; 'unsafe-inline' in script-src is required by Next.js but renders that directive largely ineffective for XSS prevention in production.
app/sites/[id]/funnels/[funnelId]/page.tsx React key for funnel step rows changed from array index to step.step.name; minor risk of duplicate keys if user creates two steps with the same name.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant SWR
    participant Dashboard as app/sites/[id]/page.tsx
    participant API as lib/api/stats.ts
    participant Backend

    Browser->>Dashboard: Mount (reads localStorage → initial dateRange)
    par Independent SWR fetches
        Dashboard->>SWR: useDashboardOverview(siteId, start, end, interval)
        Dashboard->>SWR: useDashboardPages(siteId, start, end)
        Dashboard->>SWR: useDashboardLocations(siteId, start, end)
        Dashboard->>SWR: useDashboardDevices(siteId, start, end)
        Dashboard->>SWR: useDashboardReferrers(siteId, start, end)
        Dashboard->>SWR: useDashboardPerformance(siteId, start, end)
        Dashboard->>SWR: useDashboardGoals(siteId, start, end)
        Dashboard->>SWR: useRealtime(siteId) [5s refresh]
        Dashboard->>SWR: useCampaigns(siteId, start, end)
        Dashboard->>SWR: useStats(prevStart, prevEnd) [comparison]
    end
    SWR->>API: buildQuery() → GET /dashboard/overview?...
    API->>Backend: HTTP request (cookies: include)
    Backend-->>API: DashboardOverviewData
    API-->>SWR: Typed response
    SWR-->>Dashboard: data / isLoading / error
    Dashboard->>Browser: Render sections independently

    Note over SWR,Backend: Each hook auto-refreshes per dashboardSWRConfig<br/>(dedupingInterval: 10s, refreshInterval: 60s)

Last reviewed commit: 564c853

<h3>Greptile Summary</h3> This is a large quality-of-life release that migrates dashboard data-fetching from manual `useState`/`useEffect`/`setInterval` patterns to SWR, collapses ~13 near-identical fetch functions into a factory pattern, and adds CSP/CORS hardening, a shared cookie-domain utility, and refresh token plumbing. Key changes and findings: - **SWR migration (`app/sites/[id]/page.tsx`, `lib/swr/dashboard.ts`):** Dashboard sections now fetch independently with SWR, eliminating `any[]` state and manual polling. The lazy-initializer pattern for `dateRange` avoids the previous double-fetch on settings restore. Looks correct overall, with one minor prop-type concern on `ExportModal` (`topPages`/`topReferrers` can be `undefined` before the first fetch). - **`lib/api/funnels.ts` — potentially breaking:** `getFunnelStats` was changed from sending `from`/`to` RFC3339 timestamps to `start_date`/`end_date` YYYY-MM-DD strings. The original conversion logic was added deliberately and the funnel stats endpoint may have a different API contract than the dashboard/stats endpoints. If the backend hasn't been updated simultaneously, funnel date-range filtering will silently break. - **`lib/api/stats.ts`:** The `createListFetcher` factory and `buildQuery` helper are a clean, correct refactor with no behavioral changes to callers. - **CSP (`next.config.ts`):** `'unsafe-inline'` is present unconditionally in `script-src`. While this is a practical necessity for the current Next.js setup without nonce support, it should be noted that this effectively nullifies the `script-src` allowlist for XSS mitigation in production. - **React keys:** All dashboard list components now use data-derived keys (path, referrer, name, composite location) — a genuine improvement. The funnel step key uses `step.step.name` which could collide for user-named steps with identical names; `step.step.order` would be safer. - **Auth/session improvements:** Effect dep stabilization in `context.tsx`, cookie-domain deduplication, and removal of client-side SHA-256 session identification are all clean improvements. <h3>Confidence Score: 3/5</h3> - Mostly safe to merge, but the funnel stats API parameter change should be verified against the backend before shipping. - The SWR migration, factory refactor, and dead-code cleanup are high-quality changes with no obvious correctness issues. The score is held back by the `lib/api/funnels.ts` change, which silently alters both the parameter names and format sent to the funnel stats backend endpoint — if the backend hasn't been updated in lockstep, funnel date filtering will stop working in production without any visible error. - Pay close attention to `lib/api/funnels.ts` — confirm the backend funnel stats endpoint now accepts `start_date`/`end_date` (YYYY-MM-DD) rather than `from`/`to` (RFC3339). <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | lib/api/funnels.ts | Changed funnel stats API params from `from`/`to` (RFC3339) to `start_date`/`end_date` (YYYY-MM-DD) — potentially breaking if the backend endpoint hasn't been updated to match. | | lib/api/stats.ts | Excellent refactor: 13 near-identical fetch functions collapsed into a `createListFetcher` factory and a shared `buildQuery` helper, with no behavioral changes to the public API surface. | | app/sites/[id]/page.tsx | SWR migration replaces complex manual polling and `useState<any[]>` with typed SWR hooks; minor issue: `topPages`/`topReferrers` passed to ExportModal can be `undefined` before first fetch. | | lib/swr/dashboard.ts | Adds `useCampaigns` hook and propagates `interval` through `useDashboardOverview`; clean SWR config with appropriate dedup/refresh intervals. | | next.config.ts | CSP headers added with sensible defaults; `'unsafe-inline'` in `script-src` is required by Next.js but renders that directive largely ineffective for XSS prevention in production. | | app/sites/[id]/funnels/[funnelId]/page.tsx | React key for funnel step rows changed from array index to `step.step.name`; minor risk of duplicate keys if user creates two steps with the same name. | </details> </details> <h3>Sequence Diagram</h3> ```mermaid sequenceDiagram participant Browser participant SWR participant Dashboard as app/sites/[id]/page.tsx participant API as lib/api/stats.ts participant Backend Browser->>Dashboard: Mount (reads localStorage → initial dateRange) par Independent SWR fetches Dashboard->>SWR: useDashboardOverview(siteId, start, end, interval) Dashboard->>SWR: useDashboardPages(siteId, start, end) Dashboard->>SWR: useDashboardLocations(siteId, start, end) Dashboard->>SWR: useDashboardDevices(siteId, start, end) Dashboard->>SWR: useDashboardReferrers(siteId, start, end) Dashboard->>SWR: useDashboardPerformance(siteId, start, end) Dashboard->>SWR: useDashboardGoals(siteId, start, end) Dashboard->>SWR: useRealtime(siteId) [5s refresh] Dashboard->>SWR: useCampaigns(siteId, start, end) Dashboard->>SWR: useStats(prevStart, prevEnd) [comparison] end SWR->>API: buildQuery() → GET /dashboard/overview?... API->>Backend: HTTP request (cookies: include) Backend-->>API: DashboardOverviewData API-->>SWR: Typed response SWR-->>Dashboard: data / isLoading / error Dashboard->>Browser: Render sections independently Note over SWR,Backend: Each hook auto-refreshes per dashboardSWRConfig<br/>(dedupingInterval: 10s, refreshInterval: 60s) ``` <sub>Last reviewed commit: 564c853</sub>
greptile-apps[bot] (Migrated from github.com) reviewed 2026-03-02 22:55:48 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

35 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

<sub>35 files reviewed, 4 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -280,3 +280,3 @@
{stats.steps.map((step, i) => (
<tr key={i} className="hover:bg-neutral-50 dark:hover:bg-neutral-800/30 transition-colors">
<tr key={step.step.name} className="hover:bg-neutral-50 dark:hover:bg-neutral-800/30 transition-colors">
<td className="px-6 py-4">
greptile-apps[bot] (Migrated from github.com) commented 2026-03-02 22:55:46 +00:00

Duplicate-key risk if two funnel steps share a name

step.step.name is user-supplied. If a user creates two funnel steps with the same name (e.g. two "Homepage" steps), React will emit a duplicate-key warning and produce incorrect DOM reconciliation.

A safer key combines the stable order field (which is unique by definition in a funnel) with the name, or simply uses step.step.order alone as the key.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/[funnelId]/page.tsx
Line: 281

Comment:
**Duplicate-key risk if two funnel steps share a name**

`step.step.name` is user-supplied. If a user creates two funnel steps with the same name (e.g. two "Homepage" steps), React will emit a duplicate-key warning and produce incorrect DOM reconciliation.

A safer key combines the stable `order` field (which is unique by definition in a funnel) with the name, or simply uses `step.step.order` alone as the key.

How can I resolve this? If you propose a fix, please make it concise.
**Duplicate-key risk if two funnel steps share a name** `step.step.name` is user-supplied. If a user creates two funnel steps with the same name (e.g. two "Homepage" steps), React will emit a duplicate-key warning and produce incorrect DOM reconciliation. A safer key combines the stable `order` field (which is unique by definition in a funnel) with the name, or simply uses `step.step.order` alone as the key. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/[funnelId]/page.tsx Line: 281 Comment: **Duplicate-key risk if two funnel steps share a name** `step.step.name` is user-supplied. If a user creates two funnel steps with the same name (e.g. two "Homepage" steps), React will emit a duplicate-key warning and produce incorrect DOM reconciliation. A safer key combines the stable `order` field (which is unique by definition in a funnel) with the name, or simply uses `step.step.order` alone as the key. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-03-02 22:55:42 +00:00

Breaking API contract change for funnel date parameters

The old implementation explicitly converted YYYY-MM-DD strings into RFC3339 boundaries (T00:00:00.000Z / T23:59:59.999Z) and sent them as from/to query params. This PR replaces that with start_date/end_date in plain YYYY-MM-DD format — both the parameter names and the format have changed.

If the backend funnel stats endpoint (/sites/:id/funnels/:funnelId/stats) still expects from/to with RFC3339 timestamps (distinct from the dashboard/stats endpoints that use start_date/end_date), date-range filtering on funnel reports will silently fail — the endpoint will either ignore unrecognized params and return all-time data, or return an error.

The original toRFC3339Range helper and DATE_ONLY_REGEX were very deliberately added, suggesting the backend had a different contract for this endpoint. Please confirm the backend was updated in lockstep, or revert to the previous parameter names and format if not.

// Old (correct if backend still expects this):
params.append('from', `${startDate}T00:00:00.000Z`)
params.append('to',   `${endDate}T23:59:59.999Z`)
Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/api/funnels.ts
Line: 67-73

Comment:
**Breaking API contract change for funnel date parameters**

The old implementation explicitly converted `YYYY-MM-DD` strings into RFC3339 boundaries (`T00:00:00.000Z` / `T23:59:59.999Z`) and sent them as `from`/`to` query params. This PR replaces that with `start_date`/`end_date` in plain `YYYY-MM-DD` format — both the parameter names and the format have changed.

If the backend funnel stats endpoint (`/sites/:id/funnels/:funnelId/stats`) still expects `from`/`to` with RFC3339 timestamps (distinct from the dashboard/stats endpoints that use `start_date`/`end_date`), date-range filtering on funnel reports will silently fail — the endpoint will either ignore unrecognized params and return all-time data, or return an error.

The original `toRFC3339Range` helper and `DATE_ONLY_REGEX` were very deliberately added, suggesting the backend had a different contract for this endpoint. Please confirm the backend was updated in lockstep, or revert to the previous parameter names and format if not.

```typescript
// Old (correct if backend still expects this):
params.append('from', `${startDate}T00:00:00.000Z`)
params.append('to',   `${endDate}T23:59:59.999Z`)
```

How can I resolve this? If you propose a fix, please make it concise.
**Breaking API contract change for funnel date parameters** The old implementation explicitly converted `YYYY-MM-DD` strings into RFC3339 boundaries (`T00:00:00.000Z` / `T23:59:59.999Z`) and sent them as `from`/`to` query params. This PR replaces that with `start_date`/`end_date` in plain `YYYY-MM-DD` format — both the parameter names and the format have changed. If the backend funnel stats endpoint (`/sites/:id/funnels/:funnelId/stats`) still expects `from`/`to` with RFC3339 timestamps (distinct from the dashboard/stats endpoints that use `start_date`/`end_date`), date-range filtering on funnel reports will silently fail — the endpoint will either ignore unrecognized params and return all-time data, or return an error. The original `toRFC3339Range` helper and `DATE_ONLY_REGEX` were very deliberately added, suggesting the backend had a different contract for this endpoint. Please confirm the backend was updated in lockstep, or revert to the previous parameter names and format if not. ```typescript // Old (correct if backend still expects this): params.append('from', `${startDate}T00:00:00.000Z`) params.append('to', `${endDate}T23:59:59.999Z`) ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: lib/api/funnels.ts Line: 67-73 Comment: **Breaking API contract change for funnel date parameters** The old implementation explicitly converted `YYYY-MM-DD` strings into RFC3339 boundaries (`T00:00:00.000Z` / `T23:59:59.999Z`) and sent them as `from`/`to` query params. This PR replaces that with `start_date`/`end_date` in plain `YYYY-MM-DD` format — both the parameter names and the format have changed. If the backend funnel stats endpoint (`/sites/:id/funnels/:funnelId/stats`) still expects `from`/`to` with RFC3339 timestamps (distinct from the dashboard/stats endpoints that use `start_date`/`end_date`), date-range filtering on funnel reports will silently fail — the endpoint will either ignore unrecognized params and return all-time data, or return an error. The original `toRFC3339Range` helper and `DATE_ONLY_REGEX` were very deliberately added, suggesting the backend had a different contract for this endpoint. Please confirm the backend was updated in lockstep, or revert to the previous parameter names and format if not. ```typescript // Old (correct if backend still expects this): params.append('from', `${startDate}T00:00:00.000Z`) params.append('to', `${endDate}T23:59:59.999Z`) ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -8,0 +16,4 @@
"img-src 'self' data: blob: https://www.google.com https://*.gstatic.com https://ciphera.net",
"font-src 'self'",
`connect-src 'self' https://*.ciphera.net https://ciphera.net https://www.google.com https://*.gstatic.com https://cdn.jsdelivr.net${process.env.NODE_ENV === 'development' ? ' http://localhost:*' : ''}`,
"worker-src 'self'",
greptile-apps[bot] (Migrated from github.com) commented 2026-03-02 22:55:47 +00:00

'unsafe-inline' in production script-src neutralises XSS protection

The comment acknowledges this is required for Next.js bootstrap scripts, but browsers ignore the entire script-src allowlist when 'unsafe-inline' is present (unless a nonce or hash is also provided). This means the script-src 'self' restriction has no effect in production — any inline script injected by an attacker will execute freely.

The recommended approach for Next.js is to use a per-request nonce (via generateBuildId / middleware) and emit script-src 'nonce-{value}' rather than 'unsafe-inline'. That is non-trivial to set up, but as long as 'unsafe-inline' is present the script-src directive is essentially no-op from a security standpoint. At a minimum, this is worth a code comment so future contributors don't assume XSS protection is in force.

Prompt To Fix With AI
This is a comment left during a code review.
Path: next.config.ts
Line: 16

Comment:
**`'unsafe-inline'` in production `script-src` neutralises XSS protection**

The comment acknowledges this is required for Next.js bootstrap scripts, but browsers ignore the entire `script-src` allowlist when `'unsafe-inline'` is present (unless a nonce or hash is also provided). This means the `script-src 'self'` restriction has no effect in production — any inline script injected by an attacker will execute freely.

The recommended approach for Next.js is to use a per-request nonce (via `generateBuildId` / middleware) and emit `script-src 'nonce-{value}'` rather than `'unsafe-inline'`. That is non-trivial to set up, but as long as `'unsafe-inline'` is present the `script-src` directive is essentially no-op from a security standpoint. At a minimum, this is worth a code comment so future contributors don't assume XSS protection is in force.

How can I resolve this? If you propose a fix, please make it concise.
**`'unsafe-inline'` in production `script-src` neutralises XSS protection** The comment acknowledges this is required for Next.js bootstrap scripts, but browsers ignore the entire `script-src` allowlist when `'unsafe-inline'` is present (unless a nonce or hash is also provided). This means the `script-src 'self'` restriction has no effect in production — any inline script injected by an attacker will execute freely. The recommended approach for Next.js is to use a per-request nonce (via `generateBuildId` / middleware) and emit `script-src 'nonce-{value}'` rather than `'unsafe-inline'`. That is non-trivial to set up, but as long as `'unsafe-inline'` is present the `script-src` directive is essentially no-op from a security standpoint. At a minimum, this is worth a code comment so future contributors don't assume XSS protection is in force. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: next.config.ts Line: 16 Comment: **`'unsafe-inline'` in production `script-src` neutralises XSS protection** The comment acknowledges this is required for Next.js bootstrap scripts, but browsers ignore the entire `script-src` allowlist when `'unsafe-inline'` is present (unless a nonce or hash is also provided). This means the `script-src 'self'` restriction has no effect in production — any inline script injected by an attacker will execute freely. The recommended approach for Next.js is to use a per-request nonce (via `generateBuildId` / middleware) and emit `script-src 'nonce-{value}'` rather than `'unsafe-inline'`. That is non-trivial to set up, but as long as `'unsafe-inline'` is present the `script-src` directive is essentially no-op from a security standpoint. At a minimum, this is worth a code comment so future contributors don't assume XSS protection is in force. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] commented 2026-03-02 22:55:49 +00:00 (Migrated from github.com)
Additional Comments (1)

app/sites/[id]/page.tsx
topPages / topReferrers can be undefined during initial load

Previously these were initialized with useState<any[]>([]) and were therefore always defined arrays. With SWR they are undefined until the first fetch completes. If ExportModal declares those props as TopPage[] / TopReferrer[] (non-optional), TypeScript will accept them today (the optional-chaining returns undefined, which passes through), but the component will receive undefined at runtime instead of an empty array and may call .map() on it, throwing.

Add a fallback to keep the contract consistent:

        topPages={pages?.top_pages ?? []}
        topReferrers={referrers?.top_referrers ?? []}
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/page.tsx
Line: 399-402

Comment:
**`topPages` / `topReferrers` can be `undefined` during initial load**

Previously these were initialized with `useState<any[]>([])` and were therefore always defined arrays. With SWR they are `undefined` until the first fetch completes. If `ExportModal` declares those props as `TopPage[]` / `TopReferrer[]` (non-optional), TypeScript will accept them today (the optional-chaining returns `undefined`, which passes through), but the component will receive `undefined` at runtime instead of an empty array and may call `.map()` on it, throwing.

Add a fallback to keep the contract consistent:

```suggestion
        topPages={pages?.top_pages ?? []}
        topReferrers={referrers?.top_referrers ?? []}
```

How can I resolve this? If you propose a fix, please make it concise.
<details> <summary>Additional Comments (1)</summary> **`app/sites/[id]/page.tsx`** **`topPages` / `topReferrers` can be `undefined` during initial load** Previously these were initialized with `useState<any[]>([])` and were therefore always defined arrays. With SWR they are `undefined` until the first fetch completes. If `ExportModal` declares those props as `TopPage[]` / `TopReferrer[]` (non-optional), TypeScript will accept them today (the optional-chaining returns `undefined`, which passes through), but the component will receive `undefined` at runtime instead of an empty array and may call `.map()` on it, throwing. Add a fallback to keep the contract consistent: ```suggestion topPages={pages?.top_pages ?? []} topReferrers={referrers?.top_referrers ?? []} ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/page.tsx Line: 399-402 Comment: **`topPages` / `topReferrers` can be `undefined` during initial load** Previously these were initialized with `useState<any[]>([])` and were therefore always defined arrays. With SWR they are `undefined` until the first fetch completes. If `ExportModal` declares those props as `TopPage[]` / `TopReferrer[]` (non-optional), TypeScript will accept them today (the optional-chaining returns `undefined`, which passes through), but the component will receive `undefined` at runtime instead of an empty array and may call `.map()` on it, throwing. Add a fallback to keep the contract consistent: ```suggestion topPages={pages?.top_pages ?? []} topReferrers={referrers?.top_referrers ?? []} ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details> </details>
Sign in to join this conversation.
No description provided.