Skip to content

Feat/translate about#228

Closed
TiZorii wants to merge 4 commits into
developfrom
feat/translate-about
Closed

Feat/translate about#228
TiZorii wants to merge 4 commits into
developfrom
feat/translate-about

Conversation

@TiZorii
Copy link
Copy Markdown
Collaborator

@TiZorii TiZorii commented Jan 31, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added multi-language support for the About page and authentication forms in English, Polish, and Ukrainian.
    • Enhanced form validation with real-time error messaging for email, name, and password fields.
    • Improved SEO metadata generation for the About page.
    • AI explanation service now includes rate limiting, detailed error handling, and a health status endpoint.
  • Bug Fixes

    • Better client IP detection across different hosting environments.

✏️ Tip: You can customize this high-level summary in your review settings.

…ages

  - Add about page translations (EN, UK, PL)
  - Add auth.fields.validation translations for form errors
 - Use dynamic import for groq-sdk (Netlify compatibility)                                                    - Bypass rate limiting for unknown IPs (serverless safety)
  - Safe JSON parsing with request.text() + empty body check
  - Fix ReferenceError: remove undefined errorMessage variable
  - Remove sensitive debug info from client responses
  - Add i18n keys: pricing.heading, sponsors.ctaAriaLabel
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Jan 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devlovers Ready Ready Preview, Comment Jan 31, 2026 10:06am
devlovers-net Error Error Jan 31, 2026 10:06am

@netlify
Copy link
Copy Markdown

netlify Bot commented Jan 31, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit 5dc7a57
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/697dd3e2da748400087c4b71
😎 Deploy Preview https://deploy-preview-228--develop-devlovers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 31, 2026

📝 Walkthrough

Walkthrough

The pull request introduces comprehensive internationalization support to the About page and its components using next-intl, adds metadata generation with translations, significantly enhances the AI explanation API route with rate limiting and diagnostics, implements client-side validation for authentication form fields, extends translation files across multiple locales, and updates Netlify and Next.js configurations for groq-sdk support.

Changes

Cohort / File(s) Summary
About Page Metadata & Internationalization
frontend/app/[locale]/about/page.tsx, frontend/components/about/HeroSection.tsx, frontend/components/about/TopicsSection.tsx, frontend/components/about/FeaturesSection.tsx, frontend/components/about/PricingSection.tsx, frontend/components/about/CommunitySection.tsx, frontend/components/about/SponsorsWall.tsx, frontend/components/about/InteractiveGame.tsx
Adds exported generateMetadata() function using server-side translations; replaces hardcoded UI strings across all About components with i18n lookups via useTranslations("about.*"); refactors FeaturesSection data model from label/desc to labelKey/descKey and updates child component signatures to accept translation function.
AI Explanation API Route
frontend/app/api/ai/explain/route.ts
Adds comprehensive server-side logging, per-IP rate limiting (10 requests/20 minutes), dynamic groq-sdk import for Netlify compatibility, robust body parsing with empty/invalid JSON validation, new GET endpoint exposing service status and diagnostics, proper error categorization (AUTH_ERROR, SDK_INIT_ERROR), exports maxDuration = 25 constant.
Authentication Field Validation
frontend/components/auth/fields/EmailField.tsx, frontend/components/auth/fields/NameField.tsx, frontend/components/auth/fields/PasswordField.tsx
Adds client-side HTML5 validity handling with onInvalid and onInput handlers to set/clear custom validation messages using localized strings for required fields, invalid emails, and password minimum length constraints.
Internationalization Messages
frontend/messages/en.json, frontend/messages/pl.json, frontend/messages/uk.json
Adds extensive "about" translation keys covering metadata, hero, topics, features, pricing, sponsors, community, and arcade sections; adds nested auth.fields.validation object with required/invalidEmail/passwordTooShort messages; updates dashboard title in pl and uk locales.
Build & Server Configuration
frontend/next.config.ts, netlify.toml
Marks groq-sdk as serverExternalPackages in Next.js config; configures Netlify build with Node 20, esbuild bundler, groq-sdk external module handling, publishes from .next, and adds CORS headers for /api/ai/* routes.
Client IP Detection
frontend/lib/security/client-ip.ts
Adds early return for Netlify client connection IP from x-nf-client-connection-ip header with validation, prioritizing it before CF-Connecting-IP header logic.

Sequence Diagram

sequenceDiagram
    actor Client
    participant RateLimit as Rate Limiter
    participant Validation as Request Validator
    participant GroqSDK as Groq SDK
    participant GroqAPI as Groq API
    participant ErrorHandler as Error Handler

    Client->>RateLimit: POST /api/ai/explain + IP
    alt Rate limit exceeded
        RateLimit->>ErrorHandler: Rate limit exceeded
        ErrorHandler->>Client: 429 error
    else Within limit
        RateLimit->>Validation: Proceed with request
        Validation->>Validation: Parse body, validate format
        alt Invalid/empty body
            Validation->>ErrorHandler: Invalid request
            ErrorHandler->>Client: 400 error
        else Valid body
            Validation->>GroqSDK: Initialize Groq client
            alt SDK init fails
                GroqSDK->>ErrorHandler: SDK initialization error
                ErrorHandler->>Client: 503 SDK_INIT_ERROR
            else SDK ready
                GroqSDK->>GroqAPI: Send explain request
                alt API call succeeds
                    GroqAPI->>Client: Return explanation
                else API call fails
                    GroqAPI->>ErrorHandler: Auth or model error
                    ErrorHandler->>Client: 401/503 error
                end
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐰 Strings now dance in tongues so true,
APIs blessed with rate-limit's view,
Forms now validate with care,
About page glows, multilingual and fair!
Netlify smiles—groq-sdk's in place,
What a sprint to embrace this space!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/translate about' clearly describes the main change: adding translation/internationalization support to the About page and related components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/translate-about

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/components/about/CommunitySection.tsx (1)

140-142: ⚠️ Potential issue | 🟡 Minor

Missing translation for "via" prefix.

The word "via" is hardcoded but should be translated for consistency with the rest of the i18n work.

Suggested fix

The TestimonialCard would need access to the translation function. Consider either:

  1. Pass the translation function as a prop, or
  2. Call useTranslations within TestimonialCard:
 function TestimonialCard({
   name,
   role,
   avatar,
   content,
   platform,
   icon: Icon,
   color
 }: Testimonial) {
+  const t = useTranslations("about.community")
   return (
     // ...
       <div className="mt-3 text-[10px] text-gray-400 dark:text-gray-600 font-mono flex items-center gap-1">
-        via {platform} <ExternalLink size={8} />
+        {t("via")} {platform} <ExternalLink size={8} />
       </div>
🤖 Fix all issues with AI agents
In `@frontend/app/`[locale]/about/page.tsx:
- Around line 11-17: The generateMetadata function currently calls
getTranslations() with no locale; update its signature to accept params and pass
the locale to getTranslations (e.g., change to async function generateMetadata({
params }) and call getTranslations(params.locale, "about") or equivalent),
keeping the returned object with title: t("metaTitle") and description:
t("metaDescription"); this mirrors other pages (q&a/page.tsx, blog/page.tsx,
dashboard/page.tsx) and ensures metadata is generated in the correct locale.

In `@frontend/app/api/ai/explain/route.ts`:
- Around line 342-359: The GET health handler exposes sensitive environment
details; update the GET function to stop returning groqKeyLength, nodeEnv,
isNetlify, and context (or move them behind auth) — keep only non-sensitive
status/service/timestamp and a boolean hasGroqKey, or protect the route with
authentication/ACL so only internal users can fetch full diagnostics; locate the
GET export in route.ts and remove the sensitive fields from the
NextResponse.json payload (or wrap the detailed payload behind an auth check)
and ensure the response status logic using apiKey remains unchanged.
- Around line 93-97: The checkRateLimit function currently bypasses limits for
ip === 'unknown', which is exploitable; update checkRateLimit to not silently
allow unlimited requests: when ip === 'unknown' either apply a conservative
fallback rate (e.g., much lower MAX_REQUESTS_PER_WINDOW) or still enforce the
normal limits but keyed to a special "unknown" bucket, and emit a monitored
log/metric indicating the fallback was used (use the existing
logger/processLogger or telemetry). Also update callers (e.g., getClientIp) to
surface when IP extraction failed so the log contains context, and ensure the
returned object sets skipped to true only when a safe monitoring path is
enabled.

In `@frontend/lib/security/client-ip.ts`:
- Around line 25-27: The Netlify client IP header is currently trusted
unconditionally: update the logic in frontend/lib/security/client-ip.ts around
the netlifyIp variable to gate trusting 'x-nf-client-connection-ip' behind a new
environment flag (e.g., TRUST_NETLIFY_CLIENT_IP or similar) consistent with how
TRUST_CF_CONNECTING_IP and TRUST_FORWARDED_HEADERS are used; only read and
return netlifyIp when the env flag is truthy and isIP(netlifyIp) passes,
otherwise skip it so header spoofing is prevented and downstream IP resolution
falls back to existing checks.

In `@netlify.toml`:
- Around line 18-23: The CORS headers on the route defined by the [[headers]]
block for = "/api/ai/*" are overly permissive (Access-Control-Allow-Origin =
"*"); change the static header to a specific allowed origin or comma-separated
list of trusted domains instead of "*" or remove the static header and implement
dynamic CORS in the API code (validate Origin against an allowlist and set
Access-Control-Allow-Origin per-request) in your route handler (the API that
serves /api/ai/*), and ensure Access-Control-Allow-Methods and
Access-Control-Allow-Headers remain correct when you switch to the more
restrictive approach.
🧹 Nitpick comments (7)
netlify.toml (1)

13-13: Empty function configuration section.

The [functions."api-ai-explain-*"] section is empty and has no effect. Either add configuration options (e.g., included_files, schedule, timeouts) or remove this line to avoid confusion.

frontend/components/auth/fields/PasswordField.tsx (1)

18-27: Missing else clause may leave stale custom validity message.

If the input becomes invalid for a reason not explicitly handled (e.g., if a pattern attribute is added later), the custom validity message from a previous state could persist. Add an else clause to reset custom validity for unhandled cases:

 const handleInvalid = (e: React.InvalidEvent<HTMLInputElement>) => {
     const input = e.target;
     if (input.validity.valueMissing) {
         input.setCustomValidity(t("validation.required"));
     } else if (input.validity.tooShort && minLength) {
         input.setCustomValidity(
             t("validation.passwordTooShort", { minLength })
         );
+    } else {
+        input.setCustomValidity("");
     }
 };
frontend/components/auth/fields/EmailField.tsx (1)

14-21: Clear custom validity before setting a new message.

If the validity state changes between invocations (e.g., from valueMissing to typeMismatch), the previous custom validity message may persist since setCustomValidity is only called conditionally.

Proposed fix
     const handleInvalid = (e: React.InvalidEvent<HTMLInputElement>) => {
         const input = e.target;
+        input.setCustomValidity(""); // Clear first
         if (input.validity.valueMissing) {
             input.setCustomValidity(t("validation.required"));
         } else if (input.validity.typeMismatch) {
             input.setCustomValidity(t("validation.invalidEmail"));
         }
     };
frontend/app/api/ai/explain/route.ts (1)

15-22: Consider removing API key length from logs in production.

Logging the API key length (apiKey.length) could provide minor information to attackers about the key format. While not critical, consider conditionally logging this only in development.

Optional: Conditional logging
 function logEnvironmentDiagnostics() {
   const apiKey = process.env.GROQ_API_KEY;
   console.log('[ENV] GROQ_API_KEY configured:', !!apiKey);
-  console.log('[ENV] GROQ_API_KEY length:', apiKey ? apiKey.length : 0);
+  if (process.env.NODE_ENV === 'development') {
+    console.log('[ENV] GROQ_API_KEY length:', apiKey ? apiKey.length : 0);
+  }
   console.log('[ENV] NODE_ENV:', process.env.NODE_ENV);
frontend/components/about/HeroSection.tsx (1)

94-123: Consider adding proper TypeScript types for widget props.

Both GlassWidget and MobileStatItem use any for props, which loses type safety. Consider defining a shared interface.

Suggested type definition
+interface StatWidgetProps {
+  icon: React.ComponentType<{ size: number }>;
+  color: string;
+  bg: string;
+  label: string;
+  value: string;
+}
+
-function GlassWidget({ icon: Icon, color, bg, label, value }: any) {
+function GlassWidget({ icon: Icon, color, bg, label, value }: StatWidgetProps) {

Apply the same type to MobileStatItem.

frontend/components/about/PricingSection.tsx (1)

79-87: Avoid using translated strings as React keys.

Using key={item} where item is a translated string can cause issues:

  1. If two features have identical translations, React will produce duplicate key warnings
  2. Keys should be stable identifiers, not content that varies by locale
♻️ Proposed fix: Use index-based or stable keys
 <ul className="space-y-4 mb-8 flex-1">
-    {juniorFeatures.map((item) => (
-        <li key={item} className="flex items-center gap-3 text-sm text-gray-700 dark:text-neutral-300">
+    {juniorFeatures.map((item, index) => (
+        <li key={`junior-feature-${index}`} className="flex items-center gap-3 text-sm text-gray-700 dark:text-neutral-300">
 <ul className="space-y-4 mb-8 flex-1">
-    {heroFeatures.map((item) => (
-        <li key={item} className="flex items-center gap-3 text-sm text-gray-900 dark:text-white font-medium">
+    {heroFeatures.map((item, index) => (
+        <li key={`hero-feature-${index}`} className="flex items-center gap-3 text-sm text-gray-900 dark:text-white font-medium">

Alternatively, consider using translation keys as identifiers (similar to the approach in FeaturesSection.tsx):

const juniorFeatureKeys = ["unlimited", "fullAccess", "noCard", "noGuilt"]
// Then map with: key={key} and t(`junior.features.${key}`)

Also applies to: 129-137

frontend/components/about/FeaturesSection.tsx (1)

305-310: Consider SSR hydration for initial isMobile state.

The initial state uses window.innerWidth which is only available on the client. While the conditional check typeof window !== "undefined" prevents errors, during SSR this will always be false, potentially causing a hydration mismatch if the server renders the desktop version but the client is on mobile.

♻️ Optional: Use consistent initial state

For consistent SSR/CSR behavior, consider defaulting to false unconditionally and letting the useEffect handle the actual value:

-    const [isMobile, setIsMobile] = useState(() =>
-        typeof window !== "undefined" ? window.innerWidth < 768 : false
-    )
+    const [isMobile, setIsMobile] = useState(false)

This ensures the server and client render the same initial state, with the client updating after hydration. The brief flash is typically acceptable, or you could use CSS media queries for the initial render.

Comment on lines +11 to +17
export async function generateMetadata() {
const t = await getTranslations("about")
return {
title: t("metaTitle"),
description: t("metaDescription"),
}
}
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.

⚠️ Potential issue | 🟠 Major

Missing locale parameter in generateMetadata.

Other pages in this codebase (e.g., q&a/page.tsx, blog/page.tsx, dashboard/page.tsx) explicitly extract the locale from params and pass it to getTranslations. Without this, the metadata may not be generated in the correct locale.

Proposed fix to match existing patterns
-export async function generateMetadata() {
-  const t = await getTranslations("about")
+export async function generateMetadata({
+  params,
+}: {
+  params: Promise<{ locale: string }>;
+}) {
+  const { locale } = await params;
+  const t = await getTranslations({ locale, namespace: "about" });
   return {
     title: t("metaTitle"),
     description: t("metaDescription"),
   }
 }
🤖 Prompt for AI Agents
In `@frontend/app/`[locale]/about/page.tsx around lines 11 - 17, The
generateMetadata function currently calls getTranslations() with no locale;
update its signature to accept params and pass the locale to getTranslations
(e.g., change to async function generateMetadata({ params }) and call
getTranslations(params.locale, "about") or equivalent), keeping the returned
object with title: t("metaTitle") and description: t("metaDescription"); this
mirrors other pages (q&a/page.tsx, blog/page.tsx, dashboard/page.tsx) and
ensures metadata is generated in the correct locale.

Comment on lines +93 to +97
function checkRateLimit(ip: string): { allowed: boolean; remaining: number; resetIn: number; skipped: boolean } {
// Bypass rate limiting for unknown IPs (serverless safety)
if (ip === 'unknown') {
return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW, resetIn: RATE_LIMIT_WINDOW_MS, skipped: true };
}
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.

⚠️ Potential issue | 🟡 Minor

Rate limit bypass for unknown IPs may be exploitable.

Bypassing rate limiting when ip === 'unknown' could allow abuse if getClientIp fails to extract IPs in certain environments (e.g., misconfigured proxies). Consider logging when rate limiting is skipped to monitor for abuse, or applying a stricter fallback.

Suggested improvement
 function checkRateLimit(ip: string): { allowed: boolean; remaining: number; resetIn: number; skipped: boolean } {
   // Bypass rate limiting for unknown IPs (serverless safety)
   if (ip === 'unknown') {
+    console.warn('[RATE_LIMIT] Skipped - unknown IP');
     return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW, resetIn: RATE_LIMIT_WINDOW_MS, skipped: true };
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function checkRateLimit(ip: string): { allowed: boolean; remaining: number; resetIn: number; skipped: boolean } {
// Bypass rate limiting for unknown IPs (serverless safety)
if (ip === 'unknown') {
return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW, resetIn: RATE_LIMIT_WINDOW_MS, skipped: true };
}
function checkRateLimit(ip: string): { allowed: boolean; remaining: number; resetIn: number; skipped: boolean } {
// Bypass rate limiting for unknown IPs (serverless safety)
if (ip === 'unknown') {
console.warn('[RATE_LIMIT] Skipped - unknown IP');
return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW, resetIn: RATE_LIMIT_WINDOW_MS, skipped: true };
}
🤖 Prompt for AI Agents
In `@frontend/app/api/ai/explain/route.ts` around lines 93 - 97, The
checkRateLimit function currently bypasses limits for ip === 'unknown', which is
exploitable; update checkRateLimit to not silently allow unlimited requests:
when ip === 'unknown' either apply a conservative fallback rate (e.g., much
lower MAX_REQUESTS_PER_WINDOW) or still enforce the normal limits but keyed to a
special "unknown" bucket, and emit a monitored log/metric indicating the
fallback was used (use the existing logger/processLogger or telemetry). Also
update callers (e.g., getClientIp) to surface when IP extraction failed so the
log contains context, and ensure the returned object sets skipped to true only
when a safe monitoring path is enabled.

Comment on lines +342 to +359
export async function GET() {
const apiKey = process.env.GROQ_API_KEY;
return NextResponse.json(
{
status: apiKey ? 'ok' : 'misconfigured',
service: 'ai-explain',
timestamp: new Date().toISOString(),
env: {
hasGroqKey: !!apiKey,
groqKeyLength: apiKey ? apiKey.length : 0,
nodeEnv: process.env.NODE_ENV,
isNetlify: !!process.env.NETLIFY,
context: process.env.CONTEXT ?? 'unknown',
},
},
{ status: apiKey ? 200 : 503 }
);
}
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.

⚠️ Potential issue | 🟡 Minor

Health endpoint exposes potentially sensitive environment details.

The GET endpoint publicly exposes groqKeyLength, nodeEnv, isNetlify, and deployment context. This information could aid attackers in fingerprinting the environment. Consider restricting access or reducing the information exposed.

Suggested: Reduce public exposure
 export async function GET() {
   const apiKey = process.env.GROQ_API_KEY;
   return NextResponse.json(
     {
       status: apiKey ? 'ok' : 'misconfigured',
       service: 'ai-explain',
       timestamp: new Date().toISOString(),
-      env: {
-        hasGroqKey: !!apiKey,
-        groqKeyLength: apiKey ? apiKey.length : 0,
-        nodeEnv: process.env.NODE_ENV,
-        isNetlify: !!process.env.NETLIFY,
-        context: process.env.CONTEXT ?? 'unknown',
-      },
     },
     { status: apiKey ? 200 : 503 }
   );
 }

If detailed diagnostics are needed, consider protecting this endpoint with authentication or moving it to a separate internal route.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function GET() {
const apiKey = process.env.GROQ_API_KEY;
return NextResponse.json(
{
status: apiKey ? 'ok' : 'misconfigured',
service: 'ai-explain',
timestamp: new Date().toISOString(),
env: {
hasGroqKey: !!apiKey,
groqKeyLength: apiKey ? apiKey.length : 0,
nodeEnv: process.env.NODE_ENV,
isNetlify: !!process.env.NETLIFY,
context: process.env.CONTEXT ?? 'unknown',
},
},
{ status: apiKey ? 200 : 503 }
);
}
export async function GET() {
const apiKey = process.env.GROQ_API_KEY;
return NextResponse.json(
{
status: apiKey ? 'ok' : 'misconfigured',
service: 'ai-explain',
timestamp: new Date().toISOString(),
},
{ status: apiKey ? 200 : 503 }
);
}
🤖 Prompt for AI Agents
In `@frontend/app/api/ai/explain/route.ts` around lines 342 - 359, The GET health
handler exposes sensitive environment details; update the GET function to stop
returning groqKeyLength, nodeEnv, isNetlify, and context (or move them behind
auth) — keep only non-sensitive status/service/timestamp and a boolean
hasGroqKey, or protect the route with authentication/ACL so only internal users
can fetch full diagnostics; locate the GET export in route.ts and remove the
sensitive fields from the NextResponse.json payload (or wrap the detailed
payload behind an auth check) and ensure the response status logic using apiKey
remains unchanged.

Comment on lines +25 to +27
const netlifyIp = (headers.get('x-nf-client-connection-ip') ?? '').trim();
if (netlifyIp && isIP(netlifyIp)) return netlifyIp;

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.

⚠️ Potential issue | 🟠 Major

Unconditional trust of x-nf-client-connection-ip creates a header spoofing risk.

Unlike cf-connecting-ip (gated by TRUST_CF_CONNECTING_IP) and forwarded headers (gated by TRUST_FORWARDED_HEADERS), the Netlify header is trusted unconditionally. If the app is deployed outside Netlify or behind a different proxy, attackers can spoof this header to bypass IP-based rate limiting or security controls.

Consider adding an environment variable gate for consistency:

🔒 Proposed fix to add trust gate for Netlify header
   const trustCf = envBool('TRUST_CF_CONNECTING_IP', false);
+  const trustNetlify = envBool('TRUST_NETLIFY_IP', false);

-  const netlifyIp = (headers.get('x-nf-client-connection-ip') ?? '').trim();
-  if (netlifyIp && isIP(netlifyIp)) return netlifyIp;
+  if (trustNetlify) {
+    const netlifyIp = (headers.get('x-nf-client-connection-ip') ?? '').trim();
+    if (netlifyIp && isIP(netlifyIp)) return netlifyIp;
+  }
🤖 Prompt for AI Agents
In `@frontend/lib/security/client-ip.ts` around lines 25 - 27, The Netlify client
IP header is currently trusted unconditionally: update the logic in
frontend/lib/security/client-ip.ts around the netlifyIp variable to gate
trusting 'x-nf-client-connection-ip' behind a new environment flag (e.g.,
TRUST_NETLIFY_CLIENT_IP or similar) consistent with how TRUST_CF_CONNECTING_IP
and TRUST_FORWARDED_HEADERS are used; only read and return netlifyIp when the
env flag is truthy and isIP(netlifyIp) passes, otherwise skip it so header
spoofing is prevented and downstream IP resolution falls back to existing
checks.

Comment thread netlify.toml
Comment on lines +18 to +23
[[headers]]
for = "/api/ai/*"
[headers.values]
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "POST, OPTIONS"
Access-Control-Allow-Headers = "Content-Type"
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.

⚠️ Potential issue | 🟠 Major

Overly permissive CORS configuration.

Access-Control-Allow-Origin = "*" allows any origin to make requests to your AI API endpoints. Since this API likely processes authenticated user requests and may have rate limiting tied to user identity, consider restricting this to your specific domains:

 [[headers]]
   for = "/api/ai/*"
   [headers.values]
-    Access-Control-Allow-Origin = "*"
+    Access-Control-Allow-Origin = "https://devlovers.net"
     Access-Control-Allow-Methods = "POST, OPTIONS"
     Access-Control-Allow-Headers = "Content-Type"

Alternatively, if you need multiple origins, handle CORS dynamically in the API route itself to validate against an allowlist.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[[headers]]
for = "/api/ai/*"
[headers.values]
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "POST, OPTIONS"
Access-Control-Allow-Headers = "Content-Type"
[[headers]]
for = "/api/ai/*"
[headers.values]
Access-Control-Allow-Origin = "https://devlovers.net"
Access-Control-Allow-Methods = "POST, OPTIONS"
Access-Control-Allow-Headers = "Content-Type"
🤖 Prompt for AI Agents
In `@netlify.toml` around lines 18 - 23, The CORS headers on the route defined by
the [[headers]] block for = "/api/ai/*" are overly permissive
(Access-Control-Allow-Origin = "*"); change the static header to a specific
allowed origin or comma-separated list of trusted domains instead of "*" or
remove the static header and implement dynamic CORS in the API code (validate
Origin against an allowlist and set Access-Control-Allow-Origin per-request) in
your route handler (the API that serves /api/ai/*), and ensure
Access-Control-Allow-Methods and Access-Control-Allow-Headers remain correct
when you switch to the more restrictive approach.

@TiZorii TiZorii deleted the feat/translate-about branch February 6, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants