codeflash-internal/js/cf-webapp/next.config.mjs
Kevin Turcios 91b692c1a0 fix: harden cf-webapp security across auth, XSS, and headers
- Add auth0.getSession() to unauthenticated observability endpoints
  (llm-call-debug, llm-export, observability chat)
- Remove hardcoded JWT_SECRET fallback; require env var
- Sanitize markdown HTML with DOMPurify before innerHTML assignment
- Escape user data in Intercom boot snippet via JSON.stringify
- Add security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options,
  Referrer-Policy, Permissions-Policy) via next.config.mjs
- Move OAuth params from sessionStorage to signed HttpOnly cookie
- Add input validation: clamp page/pageSize bounds, allowlist sort fields
- Stop leaking error.message to clients in API responses
- Remove ~40 console.log/error statements that logged user IDs, org IDs,
  PKCE params, and OAuth flow details
- Delete unused api-client.ts (NEXT_PUBLIC_CF_API_KEY never imported)
2026-04-13 19:25:19 -05:00

212 lines
7.3 KiB
JavaScript

import bundleAnalyzer from "@next/bundle-analyzer"
import { dirname, resolve } from "path"
import { fileURLToPath } from "url"
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === "true",
})
const __dirname = dirname(fileURLToPath(import.meta.url))
/** @type {import("next").NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://widget.intercom.io https://js.intercomcdn.com https://client.crisp.chat https://settings.crisp.chat",
"style-src 'self' 'unsafe-inline' https://client.crisp.chat",
"img-src 'self' data: blob: https://avatars.githubusercontent.com https://github.com https://*.intercomcdn.com https://*.crisp.chat https://image.crisp.chat",
"font-src 'self' data: https://client.crisp.chat",
"connect-src 'self' https://*.intercom.io https://api-iam.intercom.io wss://*.intercom.io https://*.crisp.chat wss://*.crisp.chat https://*.sentry.io https://*.ingest.us.sentry.io https://us.i.posthog.com https://us.posthog.com",
"frame-src 'self' https://intercom-sheets.com https://game.crisp.chat",
"media-src 'self' https://*.intercomcdn.com",
"worker-src 'self' blob:",
].join("; "),
},
],
},
]
},
cacheComponents: true,
cacheLife: {
dashboard: {
stale: 60, // 1 minute — serve stale while revalidating
revalidate: 300, // 5 minutes — background revalidation interval
expire: 3600, // 1 hour — hard expiry
},
frequent: {
stale: 30, // 30 seconds
revalidate: 60, // 1 minute
expire: 600, // 10 minutes
},
},
transpilePackages: ["@codeflash-ai/common"],
webpack: (config, { isServer }) => {
config.watchOptions = {
poll: 1000,
aggregateTimeout: 300,
}
// Suppress known-harmless "Critical dependency" warnings from OpenTelemetry
// and require-in-the-middle. These packages use dynamic require() for runtime
// monkey-patching — webpack can't statically analyze them but they work fine.
// Root cause: @sentry/nextjs → @sentry/node → @opentelemetry/instrumentation.
config.ignoreWarnings = [
...(config.ignoreWarnings || []),
{ module: /@opentelemetry\/instrumentation/ },
{ module: /require-in-the-middle/ },
]
// Handle web-tree-sitter's Node.js module imports in browser.
// fallback handles static require(); alias handles dynamic import()
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
"fs/promises": false,
path: false,
module: false,
}
config.resolve.alias = {
...config.resolve.alias,
module: false,
}
}
return config
},
turbopack: {
root: __dirname,
resolveAlias: {
// Stub Node.js built-ins that web-tree-sitter tries to import in the browser.
// Uses { browser: ... } so aliases only apply to client bundles, not SSR.
'fs': { browser: './src/lib/empty-shim.js' },
'fs/promises': { browser: './src/lib/empty-shim.js' },
'path': { browser: './src/lib/empty-shim.js' },
'module': { browser: './src/lib/empty-shim.js' },
},
},
serverExternalPackages: [
"@anthropic-ai/sdk",
"sharp",
"posthog-node",
"@opentelemetry/api",
"@opentelemetry/sdk-node",
"@opentelemetry/auto-instrumentations-node",
"@opentelemetry/instrumentation",
"@prisma/instrumentation",
"@sentry/opentelemetry",
"@sentry/node",
"require-in-the-middle",
"@fastify/otel",
],
experimental: {
// Tree-shake barrel exports for these heavy packages. Without this,
// importing a single icon from lucide-react or a single component from
// chart.js pulls the entire library into the bundle.
optimizePackageImports: [
"lucide-react",
"date-fns",
"react-syntax-highlighter",
"chart.js",
"react-chartjs-2",
"motion",
"zod",
"react-hook-form",
"@hookform/resolvers",
"react-markdown",
"remark-gfm",
"sonner",
"react-resizable-panels",
"@radix-ui/react-dialog",
"@radix-ui/react-select",
"@radix-ui/react-tabs",
"@radix-ui/react-tooltip",
"@radix-ui/react-toast",
"chartjs-plugin-datalabels",
"marked",
"prism-react-renderer",
],
serverActions: {
allowedOrigins: ["app.codeflash.ai", "localhost:3000"],
bodySizeLimit: '5mb', // Increased from default 1mb to handle large PR creation payloads
},
// NOTE: turbopackRemoveUnused{Imports,Exports} are NOT enabled — they
// break @opentelemetry/api barrel re-exports and Next.js internal ESM
// modules (same class of bug as turbopackTreeShaking + @sentry/core below).
// turbopackRemoveUnusedImports requires turbopackRemoveUnusedExports.
turbopackInferModuleSideEffects: true,
// Scope hoisting: collapses module wrappers for smaller output
turbopackScopeHoisting: true,
// NOTE: turbopackTreeShaking is NOT enabled — it fragments modules into
// "internal parts" which breaks @sentry/core's ESM cross-references
// (withScope, withErrorInstrumentation exports disappear). Re-test when
// Turbopack or Sentry fixes the incompatibility.
// Persist compiled artifacts between CI builds
turbopackFileSystemCacheForBuild: true,
// Client-side router cache: avoid refetching on back-navigation
staleTimes: {
dynamic: 30,
static: 180,
},
},
typescript: {
// Type-checking is split into a separate `npm run type-check` step.
// This cuts ~16s off `next build` (was 60% of build time).
ignoreBuildErrors: true,
},
// Optimize for production stability
poweredByHeader: false,
compress: true,
images: {
remotePatterns: [
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
{
protocol: "https",
hostname: "github.com",
},
],
formats: ['image/avif', 'image/webp'],
},
}
import { withSentryConfig } from "@sentry/nextjs"
// Only upload source maps when SENTRY_AUTH_TOKEN is set (CI/deploy).
// Skipping this shaves significant time off local builds.
const withSentry = process.env.SENTRY_AUTH_TOKEN
? (config) => withSentryConfig(
config,
{
silent: true,
org: "codeflash-ai",
project: "webapp",
},
{
widenClientFileUpload: true,
tunnelRoute: "/monitoring",
hideSourceMaps: true,
disableLogger: true,
automaticVercelMonitors: false,
},
)
: (config) => config
export default withBundleAnalyzer(withSentry(nextConfig))