Skip to content

Add i18n check, URL edge-case coverage, and lint fixes#69

Open
CodewithEvilxd wants to merge 2 commits intomrmps:mainfrom
CodewithEvilxd:main
Open

Add i18n check, URL edge-case coverage, and lint fixes#69
CodewithEvilxd wants to merge 2 commits intomrmps:mainfrom
CodewithEvilxd:main

Conversation

@CodewithEvilxd
Copy link

@CodewithEvilxd CodewithEvilxd commented Feb 13, 2026

What

  • Added check-i18n script to verify locale key consistency.
  • Added URL validation edge-case tests (IPv6, IPv4-mapped, metadata).
  • Hardened IPv4-mapped IPv6 private detection.
  • Allowed admin route tests to accept 401 when auth enabled.
  • Silenced known Next.js <img> lint warnings in OG + lightbox.
  • Renamed unused adProvider var in gravity route.

Why

  • Prevent i18n drift and improve SSRF/URL validation coverage.
  • Keep tests stable across auth environments.

Testing

  • bun run check-i18n
  • bun test

Open with Devin

Summary by CodeRabbit

  • New Features

    • Open Graph image generation now supports rendering article images as backgrounds.
  • Improvements

    • Enhanced URL validation to detect a broader range of private IP and IPv4-mapped IPv6 formats.
    • Added an automated i18n shape checker to validate locale message files.
  • Tests

    • Added URL validation edge-case tests and updated several tests to allow 401 responses and include per-test timeouts.
  • Chores

    • Minor lint-related adjustments and noise-suppression edits (no behavior changes).

@vercel
Copy link

vercel bot commented Feb 13, 2026

@CodewithEvilxd is attempting to deploy a commit to the Explainer Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Adds an i18n shape-check script and package script, expands URL private-IP detection to handle IPv4-mapped IPv6 forms with new tests, inserts ESLint disables for img elements, adjusts some tests to accept 401, and renames an unused request field in a gravity route.

Changes

Cohort / File(s) Summary
OG & lightbox images
app/api/og/route.tsx, components/ui/image-lightbox.tsx
Inserted eslint-disable-next-line for Next.js no-img-element before img elements; OG route now optionally layers article image as background via an img element.
URL validation & tests
lib/validation/url.ts, lib/validation/url.test.ts
Extended isPrivateIP to detect IPv4-mapped IPv6 (hex hextet and expanded forms) by parsing and delegating to IPv4 checks. Added edge-case tests covering userinfo, IPv6 literals, mapped IPv4 variants, and cloud metadata endpoints.
i18n validation tooling
scripts/check-i18n.ts, package.json
Added scripts/check-i18n.ts to compare locale JSON shapes against en.json (missing/extra/type mismatches) and added check-i18n script to package.json.
Tests & server route minor edits
server/index.test.ts, server/routes/gravity.ts
Broadened expected statuses to include 401 and added per-test timeouts; renamed destructured adProvider to _adProvider in gravity POST handler to silence unused-variable.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • mrmps

Poem

🐇 I nibble keys in JSON rows,
I chase the IPv6 that grows,
I hush the linter, frame the art,
Tests broaden eyes, scripts do their part,
Hop, hop — this rabbit stamps "All parts compose." 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add i18n check, URL edge-case coverage, and lint fixes' clearly and concisely summarizes the three main changes: i18n validation script, URL validation improvements, and linting fixes across multiple files.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ 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

No actionable comments were generated in the recent review. 🎉


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.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 13, 2026

Greptile Overview

Greptile Summary

Added i18n validation tooling and hardened SSRF protections with comprehensive test coverage.

  • Implemented check-i18n script to prevent locale drift by validating all translation files against en.json
  • Enhanced URL validation to detect IPv4-mapped IPv6 addresses in both hex notation (::ffff:a00:1) and expanded forms (0:0:0:0:0:ffff:10.0.0.1)
  • Added edge-case tests for IPv6 literals, userinfo URLs, and cloud metadata endpoints
  • Updated admin route tests to accommodate 401 responses when authentication is enabled
  • Silenced known Next.js <img> lint warnings in OG generation and lightbox (using native <img> is intentional for these use cases)
  • Cleaned up unused adProvider parameter in gravity route

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • All changes are defensive improvements with comprehensive test coverage. The i18n script prevents configuration drift, URL validation changes close SSRF attack vectors, test updates ensure stability across auth environments, and lint suppressions are justified for intentional use of native <img> elements.
  • No files require special attention

Important Files Changed

Filename Overview
scripts/check-i18n.ts New i18n validation script that compares locale files against en.json for key consistency
lib/validation/url.ts Enhanced SSRF protection with IPv4-mapped IPv6 detection for hex notation and expanded forms
lib/validation/url.test.ts Added comprehensive edge-case tests for IPv6, IPv4-mapped addresses, userinfo, and metadata endpoints
server/index.test.ts Updated admin route tests to accept 401 status when authentication is enabled
server/routes/gravity.ts Renamed unused adProvider parameter to _adProvider to silence lint warnings

Last reviewed commit: 02a7adc

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

Copy link

@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: 1

Caution

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

⚠️ Outside diff range comments (2)
server/routes/gravity.ts (1)

153-159: ⚠️ Potential issue | 🟡 Minor

Stale comment: _adProvider is not actually logged to ClickHouse.

Line 158 states "adProvider from client is used only for ClickHouse logging", but _adProvider is never passed to trackAdEvent (lines 172–192). The comment is misleading — either remove it or, if the intent is to log it, actually pass it as a field.

Suggested comment fix
       const { type, sessionId, hostname, brandName, adTitle, adText, clickUrl, impUrl, cta, favicon, deviceType, os, browser, adProvider: _adProvider } = body;

       // Derive provider from impUrl prefix only — never trust client-sent adProvider
       // for forwarding decisions (prevents spoofing to skip Gravity billing)
       const isZeroClick = impUrl?.startsWith("zeroclick://") ?? false;
-      // adProvider from client is used only for ClickHouse logging, not forwarding logic
+      // adProvider from client is accepted but intentionally unused
       const provider = isZeroClick ? "zeroclick" : "gravity";
app/api/og/route.tsx (1)

124-142: ⚠️ Potential issue | 🟡 Minor

ESLint suppression is correct — next/image is unsupported in ImageResponse (Satori).

However, there's an unaddressed SSRF vulnerability: articleImage originates from unvalidated user input (?image=... query parameter or fetched article metadata) and Satori will fetch it server-side during image generation. An attacker could pass ?image=http://169.254.169.254/latest/meta-data/ or other private IP addresses. Apply the existing isPrivateIP() validation from lib/validation/url.ts to articleImage before rendering to prevent this attack vector.

🤖 Fix all issues with AI agents
In `@lib/validation/url.test.ts`:
- Around line 80-89: Add unit tests to cover the new IPv4-mapped hex-hextet and
expanded IPv6 parsing branches that currently lack coverage: add tests invoking
normalizeUrl with "[::ffff:a00:1]" (hex-hextet private) and "[::ffff:808:808]"
(hex-hextet public) and with the expanded form "[0:0:0:0:0:ffff:10.0.0.1]" to
assert the private addresses throw "Cannot access internal or private network
addresses." and the public hex-hextet returns the normalized URL; place these in
lib/validation/url.test.ts alongside the existing IPv4-mapped tests so the code
paths in url.ts (the hex-hextet branch and the expanded form branch) are
exercised.
🧹 Nitpick comments (3)
scripts/check-i18n.ts (2)

86-97: Static analysis: forEach callbacks should not return a value.

Biome flags lines 88, 92, and 96 because the arrow-function shorthand implicitly returns the result of console.log(). Use block bodies to silence the lint errors.

Proposed fix
     if (missing.length) {
       console.log(`  Missing (${missing.length})`);
-      missing.forEach((key) => console.log(`    - ${key}`));
+      missing.forEach((key) => { console.log(`    - ${key}`); });
     }
     if (extra.length) {
       console.log(`  Extra (${extra.length})`);
-      extra.forEach((key) => console.log(`    - ${key}`));
+      extra.forEach((key) => { console.log(`    - ${key}`); });
     }
     if (typeMismatches.length) {
       console.log(`  Type mismatches (${typeMismatches.length})`);
-      typeMismatches.forEach((key) => console.log(`    - ${key}`));
+      typeMismatches.forEach((key) => { console.log(`    - ${key}`); });
     }

60-66: Consider wrapping the base file read in a try/catch with a user-friendly error.

If messages/en.json doesn't exist (e.g., running from wrong directory), readFileSync will throw a raw ENOENT error. A small guard would improve DX.

Proposed improvement
 const projectRoot = process.cwd();
 const messagesDir = join(projectRoot, "messages");
 const baseFile = join(messagesDir, "en.json");

+import { existsSync } from "fs";
+if (!existsSync(baseFile)) {
+  console.error(`Base locale file not found: ${baseFile}`);
+  process.exit(1);
+}
+
 const baseJson = readJson(baseFile);
lib/validation/url.ts (1)

103-110: Overly broad includes("ffff:") match may false-positive on non-mapped IPv6.

Any IPv6 address containing the substring "ffff:" will enter this branch — e.g. 2001:db8:0:0:0:ffff:192.168.1.1 is a legitimate (non-mapped) IPv6 address whose embedded IPv4 tail happens to be private. This fails safely (over-blocks rather than under-blocks), but it could be tightened to only match the canonical mapped prefix pattern:

Tighter match for expanded IPv4-mapped forms
-    // Handle expanded IPv4-mapped IPv6 forms (e.g., 0:0:0:0:0:ffff:10.0.0.1)
-    if (lower.includes("ffff:")) {
-      const lastColon = lower.lastIndexOf(":");
-      const tail = lastColon >= 0 ? lower.slice(lastColon + 1) : "";
-      if (tail.includes(".") && /^\d{1,3}(?:\.\d{1,3}){3}$/.test(tail)) {
-        return isPrivateIP(tail);
-      }
-    }
+    // Handle expanded IPv4-mapped IPv6 forms (e.g., 0:0:0:0:0:ffff:10.0.0.1)
+    const expandedMapped = lower.match(
+      /^(?:0+:){5}(?:0*f{4}):(\d{1,3}(?:\.\d{1,3}){3})$/
+    );
+    if (expandedMapped) {
+      return isPrivateIP(expandedMapped[1]);
+    }

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.

1 participant