diff --git a/.trivyignore b/.trivyignore index 6c986f98..7b3b79aa 100644 --- a/.trivyignore +++ b/.trivyignore @@ -9,3 +9,11 @@ # - Uniform bucket-level access (no ACLs) # - Write access restricted to authenticated backend service account only AVD-GCP-0001 + +# PostCSS: Information disclosure and denial of service via crafted CSS input +CVE-2026-45623 +# PostCSS: Path Traversal in Previous Source Map Auto-Loading +GHSA-r28c-9q8g-f849 + +# sharp inherited vulnerabilities in libvips +GHSA-f88m-g3jw-g9cj diff --git a/apps/web/eslint.config.mjs b/apps/web/eslint.config.mjs index cd9286ae..39a8b29e 100644 --- a/apps/web/eslint.config.mjs +++ b/apps/web/eslint.config.mjs @@ -47,6 +47,13 @@ const eslintConfig = [ ...nextTypescript, // Disables conflicting ESLint rules eslintConfigPrettier, + { + settings: { + react: { + version: "19.0", + }, + }, + }, { plugins: { prettier, diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 2faae375..23633f40 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -81,7 +81,9 @@ const nextConfig: NextConfig = { headers: [ { key: "Cache-Control", - value: "public, max-age=31536000, immutable", + value: isDev + ? "no-store, max-age=0" + : "public, max-age=31536000, immutable", }, ], }, @@ -109,16 +111,16 @@ const nextConfig: NextConfig = { value: "max-age=31536000; includeSubDomains", }, - // Content Security Policy - allows Mapbox, Google Analytics, Supabase, fonts + // Content Security Policy - allows MapLibre/CARTO, Google Analytics, Supabase, fonts { key: "Content-Security-Policy", value: [ "default-src 'self'", - "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://api.mapbox.com https://www.googletagmanager.com https://www.google-analytics.com https://www.clarity.ms https://static.cloudflareinsights.com", - "style-src 'self' 'unsafe-inline' https://api.mapbox.com https://fonts.googleapis.com", - "img-src 'self' data: blob: https: https://*.mapbox.com https://*.supabase.co", + `script-src 'self' 'unsafe-inline' ${isDev ? "'unsafe-eval' " : ""}'wasm-unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com https://www.clarity.ms https://static.cloudflareinsights.com`, + "style-src 'self' 'unsafe-inline' https://basemaps.cartocdn.com https://fonts.googleapis.com", + "img-src 'self' data: blob: https: https://*.cartocdn.com https://basemaps.cartocdn.com https://*.supabase.co", "font-src 'self' https://fonts.gstatic.com", - `connect-src 'self' ${isDev ? "http://localhost:8080 " : ""}https://api.nosilha.com https://*.nosilha.com https://api.mapbox.com https://*.mapbox.com https://www.googletagmanager.com https://www.google-analytics.com https://analytics.google.com https://*.supabase.co wss://*.supabase.co https://www.clarity.ms https://*.clarity.ms https://*.r2.cloudflarestorage.com https://cloudflareinsights.com`, + `connect-src 'self' ${isDev ? "http://localhost:8080 " : ""}https://api.nosilha.com https://*.nosilha.com https://*.cartocdn.com https://basemaps.cartocdn.com https://fonts.openmaptiles.org https://s3.amazonaws.com https://www.googletagmanager.com https://www.google-analytics.com https://analytics.google.com https://*.supabase.co wss://*.supabase.co https://www.clarity.ms https://*.clarity.ms https://*.r2.cloudflarestorage.com https://cloudflareinsights.com`, "worker-src 'self' blob:", "frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com", "frame-ancestors 'none'", diff --git a/apps/web/package.json b/apps/web/package.json index ac75314f..94d8dbf3 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -41,7 +41,7 @@ "dependencies": { "@headlessui/react": "^2.2.10", "@hookform/resolvers": "^5.4.0", - "@next/third-parties": "16.2.10", + "@next/third-parties": "16.2.12", "@octokit/rest": "^22.0.1", "@supabase/ssr": "^0.12.0", "@supabase/supabase-js": "^2.110.2", @@ -52,9 +52,8 @@ "exifr": "^7.1.3", "framer-motion": "^12.42.2", "lucide-react": "^1.24.0", - "mapbox-gl": "^3.26.0", - "next": "16.2.10", - "next-router-mock": "^1.0.5", + "maplibre-gl": "^5.1.0", + "next": "16.2.12", "react": "19.2.7", "react-dom": "19.2.7", "react-dropzone": "^17.0.0", @@ -63,10 +62,8 @@ "react-markdown": "^10.1.0", "react-syntax-highlighter": "^16.1.1", "recharts": "^3.9.2", - "rehype-pretty-code": "^0.14.4", "resend": "^6.17.2", "sharp": "^0.35.3", - "shiki": "^4.3.1", "supercluster": "^8.0.1", "use-supercluster": "^1.2.0", "velite": "^0.4.0", @@ -74,9 +71,6 @@ "zustand": "^5.0.14" }, "devDependencies": { - "@eslint/eslintrc": "^3", - "@microsoft/eslint-formatter-sarif": "^3.1.0", - "@next/bundle-analyzer": "16.2.10", "@playwright/test": "^1.61.1", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/postcss": "^4", @@ -85,7 +79,6 @@ "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", "@types/geojson": "^7946.0.16", - "@types/mapbox-gl": "^3.5.0", "@types/mdx": "^2.0.14", "@types/node": "^26.1.1", "@types/react": "19.2.17", @@ -95,7 +88,7 @@ "babel-plugin-react-compiler": "^1.0.0", "dotenv": "^17.4.1", "eslint": "^10", - "eslint-config-next": "16.2.10", + "eslint-config-next": "16.2.12", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.6", "jsdom": "^29.0.2", diff --git a/apps/web/src/app/(admin)/admin/dev-tools/map/page.tsx b/apps/web/src/app/(admin)/admin/dev-tools/map/page.tsx index ab59c735..c7e8d5ee 100644 --- a/apps/web/src/app/(admin)/admin/dev-tools/map/page.tsx +++ b/apps/web/src/app/(admin)/admin/dev-tools/map/page.tsx @@ -13,18 +13,18 @@ interface MapStyleSwitcherProps { const STYLES: { id: MapStyle; label: string; url: string }[] = [ { id: "streets", - label: "Streets", - url: "mapbox://styles/mapbox/streets-v12", + label: "Voyager", + url: "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json", }, { id: "outdoors", - label: "Outdoors", - url: "mapbox://styles/mapbox/outdoors-v12", + label: "Positron", + url: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json", }, { id: "satellite", - label: "Satellite", - url: "mapbox://styles/mapbox/satellite-streets-v12", + label: "Dark Matter", + url: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json", }, ]; @@ -105,7 +105,7 @@ export default function TestMapPage() {
- Mapbox: Provides interactive maps and - location services. + CARTO / OpenStreetMap: Provides interactive + map tiles and location rendering services via MapLibre GL.
- View Mapbox Privacy Policy + View CARTO Privacy Policy diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 9e26e1ea..ff1003f0 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -481,11 +481,11 @@ * - Dark mode: lighter surface on hover (elevation = closer to light source) */ .hover-surface { - @apply hover:bg-mist-50 dark:hover:bg-basalt-800/50; + @apply dark:hover:bg-basalt-800/50 hover:bg-mist-50; } .hover-surface-strong { - @apply hover:bg-mist-200 dark:hover:bg-basalt-800; + @apply dark:hover:bg-basalt-800 hover:bg-mist-200; } /* Glassmorphism Utilities */ diff --git a/apps/web/src/components/admin/mdx-engine/index.ts b/apps/web/src/components/admin/mdx-engine/index.ts deleted file mode 100644 index c3f047df..00000000 --- a/apps/web/src/components/admin/mdx-engine/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { MdxPreviewModal } from "./mdx-preview-modal"; -export { MdxCommitButton } from "./mdx-commit-button"; diff --git a/apps/web/src/components/admin/mdx-engine/mdx-commit-button.tsx b/apps/web/src/components/admin/mdx-engine/mdx-commit-button.tsx deleted file mode 100644 index 2be419fd..00000000 --- a/apps/web/src/components/admin/mdx-engine/mdx-commit-button.tsx +++ /dev/null @@ -1,104 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { FileCode, Loader2 } from "lucide-react"; -import { MdxPreviewModal } from "./mdx-preview-modal"; -import { generateMdx, commitMdx } from "@/lib/api"; -import type { MdxContent } from "@/types/admin"; - -interface MdxCommitButtonProps { - storyId: string; - storyTitle: string; -} - -export function MdxCommitButton({ storyId, storyTitle }: MdxCommitButtonProps) { - const [isGenerating, setIsGenerating] = useState(false); - const [isCommitting, setIsCommitting] = useState(false); - const [mdxContent, setMdxContent] = useState