Skip to content

feat: apply special classes on regex match in pages#20

Merged
calebephrem merged 6 commits into
open-devhub:mainfrom
calebephrem:main
Jul 8, 2026
Merged

feat: apply special classes on regex match in pages#20
calebephrem merged 6 commits into
open-devhub:mainfrom
calebephrem:main

Conversation

@calebephrem

Copy link
Copy Markdown
Member
  • Apply special class on regex match in pages, such as markdown links, codeblocks and channel names
  • Add link preview card component to show overlay on link hover
  • Organize component files

@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

@calebephrem is attempting to deploy a commit to the aditya ojha's projects Team on Vercel.

A member of the Team first needs to authorize it.

@devhub-bot devhub-bot Bot added chore maintenance, configs, formatting, dependencies docs Improvements or additions to documentation feat New feature labels Jul 8, 2026
@beetle-ai

beetle-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary by Beetle

This PR refactors the project structure and enhances the pages system with improved text rendering capabilities. The main focus is on moving configuration files to a more appropriate location (content/ directory), implementing a sophisticated inline text parser that supports code snippets, links, and channel mentions, and adding a link preview feature with a custom API endpoint. The changes also include component organization improvements and documentation updates.

📁 File Changes Summary (Consolidated across all commits):

File Status Changes Description
content/pages.ts Added +3/-3 Moved from lib/pages.config.ts to better organize content configuration; updated URLs to use markdown link syntax for better rendering
app/pages/[slug]/PageClient.tsx Modified +78/-42 Replaced simple Highlight component with comprehensive ApplySpecialClass component that parses and renders inline code (backticks), markdown links, and Discord channel mentions with proper styling; integrated LinkPreviewCard for interactive link previews
app/pages/[slug]/page.tsx
app/pages/[slug]/[subslug]/page.tsx
app/pages/layout.tsx
Modified +5/-5 Updated imports to reference new content/pages location instead of lib/pages.config
app/api/link-preview/route.ts Added +99/-0 New API endpoint that fetches and parses Open Graph metadata from external URLs; includes HTML entity decoding, timeout handling, and caching headers
components/LinkPreviewCard.tsx Added +218/-0 Interactive hover card component that displays link previews with title, description, and image; features smooth animations, portal rendering, client-side caching, and scroll-aware dismissal
app/globals.css Modified +4/-0 Added .font-mono utility class for consistent monospace font rendering
components/home/ShowcaseSection.tsx Modified +2/-2 Updated devhub-bot project metadata: changed language from "Discord.js" to "TypeScript" with corresponding color update
app/partners/page.tsx
app/resources/page.tsx
app/rules/page.tsx
components/home/BelongSection.tsx
components/home/FeaturesSection.tsx
components/home/HeroSection.tsx
components/home/StatsSection.tsx
Modified +7/-7 Reorganized component imports to reference new components/bits/ subdirectory for better file organization
README.md Modified +24/-16 Updated project structure documentation to reflect new content/ directory and reorganized component structure; fixed Discord invite link

Total Changes: 13 files changed, +461 additions, -74 deletions

🗺️ Walkthrough:

sequenceDiagram
participant User
participant PageClient
participant ApplySpecialClass
participant LinkPreviewCard
participant API as /api/link-preview
participant External as External Site
User->>PageClient: Hover over link in page content
PageClient->>ApplySpecialClass: Parse text with regex
ApplySpecialClass->>ApplySpecialClass: Match code, links, channels
alt Markdown Link Found
ApplySpecialClass->>LinkPreviewCard: Render with href
User->>LinkPreviewCard: Mouse enter (150ms delay)
LinkPreviewCard->>LinkPreviewCard: Check cache
alt Cache Miss
LinkPreviewCard->>API: GET /api/link-preview?url=...
API->>External: Fetch HTML (5s timeout)
External-->>API: Return HTML
API->>API: Extract og:title, og:description, og:image
API->>API: Decode HTML entities
API-->>LinkPreviewCard: Return metadata + cache header
LinkPreviewCard->>LinkPreviewCard: Store in cache
end
LinkPreviewCard->>User: Display preview card with animation
User->>LinkPreviewCard: Mouse leave
LinkPreviewCard->>LinkPreviewCard: 200ms delay
LinkPreviewCard->>User: Hide card with animation
end
alt Inline Code Found
ApplySpecialClass->>User: Render styled code element
end
alt Channel Mention Found
ApplySpecialClass->>User: Render styled channel link
end
Loading

🎯 Key Changes:

  • Content Organization: Moved pages.config.ts from lib/ to content/pages.ts, establishing a clearer separation between configuration and content management
  • Advanced Text Parsing: Implemented regex-based parser that simultaneously handles inline code (backticks), markdown links, and Discord channel mentions (#channel-name) with appropriate styling for each
  • Interactive Link Previews: Added hover-activated preview cards that fetch and display Open Graph metadata (title, description, image) with smooth animations and intelligent caching
  • Link Preview API: Created serverless endpoint with HTML parsing, meta tag extraction, URL resolution, timeout protection, and HTTP caching headers
  • Component Architecture: Reorganized shared UI components into components/bits/ subdirectory for better maintainability
  • Documentation Sync: Updated README to reflect new project structure and fixed community links

📊 Impact Assessment:

  • Security: ✅ Link preview API includes URL validation, timeout protection (5s), and uses safe HTML parsing via regex rather than DOM manipulation. External fetches are isolated in serverless function. No XSS vulnerabilities introduced as content is rendered as text, not HTML.
  • Performance: ⚠️ Link preview feature adds network overhead but is mitigated by: (1) 150ms hover delay before fetching, (2) client-side Map-based caching to prevent duplicate requests, (3) server-side cache headers (1 hour), (4) 5-second timeout on external fetches. Regex parsing in ApplySpecialClass runs on every render but operates on small text chunks.
  • Maintainability: ✅ Significant improvement. Content is now properly separated from library code. The ApplySpecialClass component uses a single regex pattern for all parsing, making it easier to extend. Component organization into bits/ subdirectory improves discoverability. API route follows Next.js conventions with clear error handling.
  • Testing: ⚠️ No tests added for new functionality. Critical areas needing coverage: (1) regex parsing edge cases in ApplySpecialClass, (2) link preview API error handling and timeout behavior, (3) LinkPreviewCard caching logic and scroll dismissal, (4) HTML entity decoding in API route.
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only.Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

Follow us: Beetle · X · LinkedIn

@devhub-bot

devhub-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

Note

Linting checks passed successfully 🎉

All formatting and code quality checks are clean.

You're good to merge 🚀

Comment on lines +54 to +67
} else if (link) {
const linkMatch = link.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
if (linkMatch) {
parts.push(
<LinkPreviewCard key={key++} href={linkMatch[2]}>
{linkMatch[1]}
</LinkPreviewCard>,
);
} else {
parts.push(full);
}
} else if (channel) {
parts.push(
<span

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.

The link parsing logic doesn't validate URL schemes, which creates an XSS vulnerability. Malicious content could inject javascript: URLs that execute arbitrary code when clicked.
For example, a malicious page content like [Click me](javascript:alert('XSS')) would be rendered as a clickable link that executes JavaScript.

Confidence: 5/5

Suggested Fix
Suggested change
} else if (link) {
const linkMatch = link.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
if (linkMatch) {
parts.push(
<LinkPreviewCard key={key++} href={linkMatch[2]}>
{linkMatch[1]}
</LinkPreviewCard>,
);
} else {
parts.push(full);
}
} else if (channel) {
parts.push(
<span
const linkMatch = link.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
if (linkMatch) {
const url = linkMatch[2];
const isValidUrl = /^(https?:\/\/|\/|#)/.test(url);
if (isValidUrl) {
parts.push(
<a
key={key++}
href={linkMatch[2]}
target="_blank"
rel="noopener noreferrer"
className="hover:underline cursor-pointer hover:opacity-80 transition-opacity"
style={{ color: "#a5b4fc" }}
>
{linkMatch[1]}
</a>,
);
} else {
parts.push(full);
}

Add URL scheme validation to only allow safe protocols (https://, http://, relative paths /, or anchors #). This prevents javascript:, data:, and other potentially dangerous URL schemes from being rendered as clickable links.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In app/pages/[slug]/PageClient.tsx around line 54-67, the link parsing logic in the ApplySpecialClass component doesn't validate URL schemes before rendering them in href attributes, creating an XSS vulnerability where malicious content could inject javascript: URLs; add URL scheme validation using a regex like /^(https?:\/\/|\/|#)/.test(url) to only allow http://, https://, relative paths, and anchor links, and render the original text if the URL scheme is invalid.

📍 This suggestion applies to lines 54-67

Comment thread app/api/link-preview/route.ts Outdated
Comment on lines +45 to +46
try {
new URL(url);

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.

The URL validation only checks if the URL is parseable, but doesn't prevent Server-Side Request Forgery (SSRF) attacks. Attackers can use this endpoint to access internal network resources, cloud metadata endpoints (e.g., http://169.254.169.254/latest/meta-data/), or perform port scanning of internal infrastructure.
For example:

  • ?url=http://localhost:6379 - Access internal Redis
  • ?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ - AWS metadata
  • ?url=http://192.168.1.1/admin - Internal network resources

Confidence: 5/5

Suggested Fix
Suggested change
try {
new URL(url);
try {
const parsedUrl = new URL(url);
// Block dangerous protocols
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
return NextResponse.json({ error: "invalid protocol" }, { status: 400 });
}
// Block private IP ranges and localhost
const hostname = parsedUrl.hostname.toLowerCase();
const privatePatterns = [
/^localhost$/i,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
/^192\.168\./,
/^169\.254\./, // AWS metadata
/^::1$/, // IPv6 localhost
/^fc00:/, // IPv6 private
/^fe80:/, // IPv6 link-local
];
if (privatePatterns.some(pattern => pattern.test(hostname))) {
return NextResponse.json({ error: "private urls not allowed" }, { status: 400 });
}
} catch {
return NextResponse.json({ error: "invalid url" }, { status: 400 });
}

Add comprehensive SSRF protection by validating the URL protocol and blocking private IP ranges, localhost, and cloud metadata endpoints. This prevents attackers from using your server to access internal resources.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In app/api/link-preview/route.ts around line 45-46, the URL validation only checks if the URL is parseable but doesn't prevent SSRF attacks where attackers can access internal network resources, cloud metadata endpoints, or perform port scanning; add comprehensive validation to block dangerous protocols (only allow http/https), private IP ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16), localhost, and IPv6 private addresses before allowing the fetch operation.

📍 This suggestion applies to lines 45-46

return NextResponse.json({ error: "failed to fetch" }, { status: 502 });
}

const html = await res.text();

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.

The res.text() call has no size limit, allowing attackers to exhaust server memory by providing URLs that return extremely large responses (e.g., multi-gigabyte files). This can cause out-of-memory crashes and denial of service.

Confidence: 5/5

Suggested Fix
Suggested change
const html = await res.text();
const contentLength = res.headers.get('content-length');
if (contentLength && parseInt(contentLength) > 1024 * 1024) {
return NextResponse.json({ error: "response too large" }, { status: 502 });
}
const html = await res.text();
if (html.length > 1024 * 1024) {
return NextResponse.json({ error: "response too large" }, { status: 502 });
}

Add response size validation before and after reading the body. Check the Content-Length header if available, and verify the actual text length doesn't exceed 1MB. This prevents memory exhaustion attacks.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In app/api/link-preview/route.ts around line 69, the res.text() call has no size limit which allows attackers to exhaust server memory by providing URLs that return extremely large responses; add validation to check the Content-Length header before reading (if available) and verify the actual text length after reading, rejecting responses larger than 1MB to prevent memory exhaustion and DoS attacks.

Comment on lines +55 to +61
const res = await fetch(url, {
signal: controller.signal,
headers: {
"User-Agent":
"Mozilla/5.0 (compatible; LinkPreviewBot/1.0; +https://example.com/bot)",
},
});

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.

The fetch request doesn't follow redirects safely and could be exploited for SSRF bypass. An attacker could use a public URL that redirects to an internal resource (e.g., a URL shortener pointing to http://localhost), bypassing initial URL validation.

Confidence: 5/5

Suggested Fix

Add redirect: 'manual' to the fetch options and validate redirect URLs before following them, or use redirect: 'error' to block all redirects:

Suggested change
const res = await fetch(url, {
signal: controller.signal,
headers: {
"User-Agent":
"Mozilla/5.0 (compatible; LinkPreviewBot/1.0; +https://example.com/bot)",
},
});
const res = await fetch(url, {
signal: controller.signal,
redirect: 'error',
headers: {
"User-Agent":
"Mozilla/5.0 (compatible; LinkPreviewBot/1.0; +https://example.com/bot)",
},
});

Setting redirect: 'error' prevents the fetch from following redirects, which blocks SSRF bypass attacks where an attacker uses a public URL that redirects to internal resources. If you need to support redirects, use redirect: 'manual' and validate each redirect URL against the same SSRF protection rules.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In app/api/link-preview/route.ts around line 55-61, the fetch request doesn't prevent redirect-based SSRF bypass where attackers use public URLs that redirect to internal resources; add redirect: 'error' to the fetch options to block all redirects, or if redirects are needed, use redirect: 'manual' and validate each redirect URL against the same SSRF protection rules before following it.

📍 This suggestion applies to lines 55-61

url: string;
}

const previewCache = new Map<string, PreviewData | null>();

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.

The global previewCache Map is never cleared and will grow indefinitely as users hover over different links throughout their session. In a long-running application, this can cause memory leaks as the cache accumulates preview data for every unique URL encountered.

Confidence: 5/5

Suggested Fix

Implement a cache size limit or LRU (Least Recently Used) eviction strategy:

Suggested change
const previewCache = new Map<string, PreviewData | null>();
const MAX_CACHE_SIZE = 100;
const previewCache = new Map<string, PreviewData | null>();
function addToCache(key: string, value: PreviewData | null) {
if (previewCache.size >= MAX_CACHE_SIZE) {
const firstKey = previewCache.keys().next().value;
previewCache.delete(firstKey);
}
previewCache.set(key, value);
}

Then update line 61 and 65 to use addToCache(href, json) and addToCache(href, null) instead of direct previewCache.set() calls. This prevents unbounded memory growth by limiting the cache to 100 entries.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In components/LinkPreviewCard.tsx at line 12, the global previewCache Map is never cleared and will grow indefinitely causing memory leaks in long-running sessions; implement a cache size limit (e.g., MAX_CACHE_SIZE = 100) with LRU eviction by creating a helper function that removes the oldest entry when the cache exceeds the limit, and update lines 61 and 65 to use this helper instead of direct Map.set() calls.

<div className="relative">
{data?.image && !imgError && (
<img
src={data.image}

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.

The image src attribute uses data.image directly without validating the URL scheme. If the API endpoint is compromised or returns malicious data, this could render javascript: or data: URLs that execute arbitrary code when the image fails to load and triggers error handlers, or through other browser-specific vulnerabilities.

Confidence: 5/5

Suggested Fix
Suggested change
src={data.image}
src={data.image.startsWith('http://') || data.image.startsWith('https://') ? data.image : ''}

Validate that the image URL uses a safe protocol (http:// or https://) before rendering it. This prevents javascript:, data:, and other potentially dangerous URL schemes from being used in the src attribute.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In components/LinkPreviewCard.tsx at line 174, the image src attribute uses data.image directly without URL scheme validation, which could allow javascript: or data: URLs if the API is compromised; add validation to only allow http:// or https:// URLs by checking data.image.startsWith('http://') || data.image.startsWith('https://') before rendering, otherwise use an empty string.

@beetle-ai

beetle-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary by Beetle

This PR enhances the security and robustness of the link preview functionality by implementing comprehensive safeguards against SSRF (Server-Side Request Forgery) attacks and resource exhaustion. The changes prevent the API from being exploited to access internal network resources, enforce response size limits, and improve the overall security posture of the link preview feature. Additionally, the PR includes minor refactoring to reorganize component imports and adds CORS headers for controlled external access.

📁 File Changes Summary (Consolidated across all commits):

File Status Changes Description
app/api/link-preview/route.ts Modified +49/-5 Security hardening: Added SSRF protection with private IP/hostname filtering, URL validation with protocol restrictions, redirect blocking, response size limits (1MB), and safe image URL resolution
app/layout.tsx
app/not-found.tsx
app/page.tsx
app/pages/[slug]/PageClient.tsx
Modified +5/-5 Component reorganization: Updated import paths to reflect new component structure (components/site/*, components/bits/*), and refined inline code styling with improved padding
components/LinkPreviewCard.tsx Modified +2/-0 Image security: Added lazy loading and no-referrer policy to preview images to prevent referrer leakage and improve performance
vercel.json Added +25/-0 CORS configuration: Added strict CORS headers for /api/link-preview endpoint, allowing only https://devhub.vercel.app origin with GET/OPTIONS methods

Total Changes: 7 files changed, +84 additions, -13 deletions

🗺️ Walkthrough:

sequenceDiagram
participant Client
participant API as /api/link-preview
participant Validator as URL Validator
participant External as External Site
Client->>API: GET /api/link-preview?url=...
API->>Validator: isSafeUrl(url)
Note over Validator: Check protocol (http/https only)
Validator->>Validator: Parse hostname
Note over Validator: Block private IPs: localhost, 127.x, 10.x, 172.16-31.x, 192.168.x, 169.254.x, IPv6 private
alt URL is unsafe
Validator-->>API: false
API-->>Client: 400 "invalid or disallowed url"
else URL is safe
Validator-->>API: true
API->>External: fetch(url, {redirect: "error"})
alt Response too large
External-->>API: Content-Length > 1MB
API-->>Client: 502 "response too large"
else Response OK
External-->>API: HTML content
API->>API: Validate HTML size < 1MB
API->>API: Extract metadata
API->>Validator: isSafeUrl(imageUrl)
alt Image URL safe
Validator-->>API: true
API-->>Client: 200 {title, description, image}
else Image URL unsafe
Validator-->>API: false
API-->>Client: 200 {title, description, image: null}
end
Loading

🎯 Key Changes:

  • SSRF Protection: Implemented comprehensive private network filtering to prevent attackers from using the link preview API to scan internal networks or access localhost services
  • Protocol Enforcement: Restricted URL schemes to HTTP/HTTPS only, blocking potentially dangerous protocols like file://, ftp://, or data:
  • Redirect Prevention: Added redirect: "error" to fetch options to prevent redirect-based SSRF bypasses
  • Resource Limits: Enforced 1MB size limits on both Content-Length header and actual response body to prevent memory exhaustion attacks
  • Image URL Validation: Applied the same safety checks to resolved image URLs to prevent metadata-based SSRF
  • Privacy Enhancement: Added referrerPolicy="no-referrer" to preview images to prevent referrer leakage
  • CORS Lockdown: Configured strict CORS policy allowing only the specific DevHub domain to access the API

📊 Impact Assessment:

  • Security: ⭐⭐⭐⭐⭐ Critical security improvements. This PR addresses serious SSRF vulnerabilities that could have allowed attackers to probe internal networks, access cloud metadata endpoints (AWS/GCP/Azure), or perform port scanning. The multi-layered defense (protocol validation, hostname filtering, redirect blocking, size limits) provides robust protection against various attack vectors.
  • Performance: ⭐⭐⭐⭐ Positive impact. The 1MB response size limit prevents resource exhaustion and protects against slowloris-style attacks. Lazy loading on images improves initial page load performance. The validation overhead is minimal (regex pattern matching) and occurs before network requests, making it efficient.
  • Maintainability: ⭐⭐⭐⭐ Good maintainability. The security logic is well-encapsulated in the isSafeUrl() function with clear pattern definitions. The private IP patterns are documented and easy to extend. Component reorganization improves project structure. Consider adding unit tests for the validation logic to ensure patterns work correctly.
  • Testing: ⚠️ Needs attention. No test files were added in this PR. The security-critical validation logic should have comprehensive unit tests covering: valid/invalid protocols, all private IP ranges (IPv4/IPv6), edge cases (malformed URLs, unicode domains), redirect handling, and size limit enforcement. Integration tests should verify the CORS configuration works as expected.
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only.Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

Follow us: Beetle · X · LinkedIn

Comment on lines +37 to +48
const PRIVATE_HOSTNAME_PATTERNS = [
/^localhost$/i,
/^0\.0\.0\.0$/,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
/^192\.168\./,
/^169\.254\./,
/^::1$/,
/^fc00:/i,
/^fe80:/i,
];

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.

The SSRF protection is missing several critical private IP ranges that could be exploited:

  1. Missing 100.64.0.0/10 (Carrier-grade NAT - RFC 6598)
  2. Missing 192.0.0.0/24 (IETF Protocol Assignments)
  3. Missing 192.0.2.0/24 (TEST-NET-1)
  4. Missing 198.18.0.0/15 (Benchmark testing)
  5. Missing 198.51.100.0/24 (TEST-NET-2)
  6. Missing 203.0.113.0/24 (TEST-NET-3)
  7. Missing 224.0.0.0/4 (Multicast)
  8. Missing 240.0.0.0/4 (Reserved)
  9. Missing IPv6 loopback range (::1 is covered, but not the full ::1/128)
  10. Missing IPv6 unique local addresses (fd00::/8)
    An attacker could potentially use these ranges to bypass SSRF protection and access internal services.

Confidence: 5/5

Suggested Fix
Suggested change
const PRIVATE_HOSTNAME_PATTERNS = [
/^localhost$/i,
/^0\.0\.0\.0$/,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
/^192\.168\./,
/^169\.254\./,
/^::1$/,
/^fc00:/i,
/^fe80:/i,
];
const PRIVATE_HOSTNAME_PATTERNS = [
/^localhost$/i,
/^0\.0\.0\.0$/,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
/^192\.168\./,
/^169\.254\./,
/^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\./,
/^192\.0\.0\./,
/^192\.0\.2\./,
/^198\.18\./,
/^198\.19\./,
/^198\.51\.100\./,
/^203\.0\.113\./,
/^22[4-9]\./,
/^23[0-9]\./,
/^24[0-9]\./,
/^25[0-5]\./,
/^::1$/,
/^::ffff:127\./i,
/^fc00:/i,
/^fd00:/i,
/^fe80:/i,
];

Add comprehensive private IP range coverage including carrier-grade NAT (100.64.0.0/10), test networks, multicast ranges (224.0.0.0/4), reserved ranges (240.0.0.0/4), and IPv6 unique local addresses (fd00::/8). This prevents attackers from exploiting gaps in SSRF protection to access internal services.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In app/api/link-preview/route.ts around line 37, the PRIVATE_HOSTNAME_PATTERNS array is missing several critical private IP ranges that could allow SSRF attacks to bypass protection; add patterns for 100.64.0.0/10 (carrier-grade NAT), 192.0.0.0/24, 192.0.2.0/24, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24 (test networks), 224.0.0.0/4 (multicast), 240.0.0.0/4 (reserved), ::ffff:127.0.0.0/104 (IPv4-mapped IPv6), and fd00::/8 (IPv6 unique local addresses) to ensure comprehensive SSRF protection.

📍 This suggestion applies to lines 37-48

@devhub-bot

devhub-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

Note

Linting checks passed successfully 🎉

All formatting and code quality checks are clean.

You're good to merge 🚀

Comment thread vercel.json
Comment on lines +15 to +16
"key": "Access-Control-Allow-Headers",
"value": "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.

The CORS configuration is missing the Access-Control-Allow-Credentials header. Without explicitly setting this header, the browser's default behavior may vary, and if credentials are ever needed in the future, this could lead to security vulnerabilities. More critically, the current configuration doesn't include Access-Control-Max-Age, which means the browser will send a preflight OPTIONS request for every single API call, significantly impacting performance.
Additionally, for a public link preview API that's being called from a specific origin, you should consider whether you actually need CORS at all, or if this should be a server-side only API call to prevent abuse.

Confidence: 5/5

Suggested Fix
Suggested change
"key": "Access-Control-Allow-Headers",
"value": "Content-Type"
{
"key": "Access-Control-Allow-Headers",
"value": "Content-Type"
},
{
"key": "Access-Control-Max-Age",
"value": "86400"
},

Add Access-Control-Max-Age header with a value of 86400 (24 hours) to cache preflight responses and reduce unnecessary OPTIONS requests. This significantly improves performance by preventing the browser from sending a preflight request for every API call. Consider also explicitly setting Access-Control-Allow-Credentials: false if credentials are not needed, making the security posture explicit.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In vercel.json around line 15, the CORS configuration is missing the Access-Control-Max-Age header which causes the browser to send a preflight OPTIONS request for every API call, significantly impacting performance; add a new header object after the Access-Control-Allow-Headers configuration with key "Access-Control-Max-Age" and value "86400" (24 hours) to cache preflight responses, and consider adding "Access-Control-Allow-Credentials" with value "false" to make the security posture explicit.

📍 This suggestion applies to lines 15-16

Comment thread vercel.json
Comment on lines +7 to +8
"key": "Access-Control-Allow-Origin",
"value": "https://devhub.vercel.app"

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.

The Access-Control-Allow-Origin is hardcoded to a single production domain. This configuration will break local development and any staging/preview environments. When developers try to test the link preview functionality locally (e.g., http://localhost:3000), all API calls will fail due to CORS errors. This is a common misconfiguration that blocks development workflow.

Confidence: 4/5

Suggested Fix

Consider using environment-based configuration or allowing multiple origins. Since Vercel doesn't support environment variables in vercel.json, you have a few options:

  1. Handle CORS in the API route itself (recommended): Remove this configuration from vercel.json and handle CORS dynamically in app/api/link-preview/route.ts where you can check the origin against an allowlist that includes localhost for development.
  2. Use multiple Vercel configurations: Create separate vercel.json files for different environments.
  3. Allow multiple origins: If you control all the domains, list them explicitly (though Vercel's headers config doesn't support arrays, so option 1 is better).
    The recommended approach is to remove CORS from vercel.json and handle it in the API route where you have more control and can use environment variables.
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In vercel.json at line 7-8, the Access-Control-Allow-Origin is hardcoded to production domain which will break local development and staging environments; consider removing the CORS configuration from vercel.json and instead handling it dynamically in app/api/link-preview/route.ts where you can check the request origin against an environment-based allowlist that includes localhost for development, production domain for production, and preview URLs for staging.

📍 This suggestion applies to lines 7-8

@calebephrem calebephrem merged commit 94b5bb3 into open-devhub:main Jul 8, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore maintenance, configs, formatting, dependencies docs Improvements or additions to documentation feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant