Overview
Hosting the WebHelp MCP Server publicly exposes your infrastructure to several security risks. This guide outlines the threats and provides mitigation strategies to secure your deployment.
Critical: Do not deploy this server to production without implementing the security measures outlined in this guide. The server fetches and executes external content, which can be exploited by malicious actors.
Security Risks
The WebHelp MCP Server faces three primary security risks:
1. Server-Side Request Forgery (SSRF)
Server-Side Request Forgery (SSRF) is a vulnerability that allows an attacker to make the server fetch arbitrary URLs, potentially accessing internal resources or services that should not be publicly accessible.
Risk Level: 🔴 Critical
How It Works
The MCP server accepts a site URL in the request path and fetches content from that location:
const handler = async (
req : NextRequest ,
{ params } : { params : Promise <{ site : Array < string > }> }
) => {
const { site } = await params ;
const endpoint = site . join ( '/' );
const baseUrls = resolveBaseUrls ( site );
// Server fetches from baseUrls...
}
An attacker could exploit this by requesting:
https://your-server.vercel.app/internal-service.company.local
https://your-server.vercel.app/169.254.169.254/latest/meta-data
https://your-server.vercel.app/localhost:8080/admin
Impact
Access to internal services : Attackers can reach internal APIs, databases, or admin panels
Cloud metadata exposure : Access to AWS/GCP/Azure metadata endpoints containing credentials
Port scanning : Enumerate internal network services and open ports
Bypass firewall rules : Use your server as a proxy to access restricted resources
Mitigation Strategies
Implement URL Whitelisting
Restrict the server to only fetch from approved documentation domains: const ALLOWED_DOMAINS = [
'docs.example.com' ,
'help.mycompany.com' ,
'documentation.trusted.org'
];
function isAllowedDomain ( url : string ) : boolean {
try {
const hostname = new URL ( url ). hostname ;
return ALLOWED_DOMAINS . some ( domain =>
hostname === domain || hostname . endsWith ( '.' + domain )
);
} catch {
return false ;
}
}
const handler = async ( req , { params }) => {
const { site } = await params ;
const baseUrls = resolveBaseUrls ( site );
// Validate all URLs before processing
for ( const url of baseUrls ) {
if ( ! isAllowedDomain ( url )) {
return new Response ( 'Domain not allowed' , { status: 403 });
}
}
// Continue with normal processing...
};
Block Private IP Ranges
Prevent requests to internal network addresses: import { isIP } from 'net' ;
function isPrivateIP ( ip : string ) : boolean {
const parts = ip . split ( '.' ). map ( Number );
return (
ip === 'localhost' ||
ip === '127.0.0.1' ||
ip === '::1' ||
parts [ 0 ] === 10 ||
parts [ 0 ] === 127 ||
( parts [ 0 ] === 172 && parts [ 1 ] >= 16 && parts [ 1 ] <= 31 ) ||
( parts [ 0 ] === 192 && parts [ 1 ] === 168 ) ||
ip . startsWith ( '169.254.' ) // AWS metadata
);
}
async function validateUrl ( url : string ) : Promise < void > {
const hostname = new URL ( url ). hostname ;
// Check if hostname is an IP
if ( isIP ( hostname )) {
if ( isPrivateIP ( hostname )) {
throw new Error ( 'Access to private IP ranges is forbidden' );
}
}
// Resolve DNS and check IP
const { address } = await dns . promises . lookup ( hostname );
if ( isPrivateIP ( address )) {
throw new Error ( 'Domain resolves to private IP' );
}
}
Implement Network-Level Controls
Use Vercel’s network configuration or external firewall rules:
Configure egress filtering to block private IP ranges
Use a VPC or private network for sensitive deployments
Implement DNS filtering to prevent resolution of internal hostnames
2. Resource Exhaustion
Risk Level: 🟡 High
How It Works
Large or repeated queries can consume excessive server resources:
CPU exhaustion : Processing large WebHelp indexes
Memory exhaustion : Loading multiple documentation sites simultaneously
Bandwidth exhaustion : Downloading large HTML files
Disk exhaustion : Caching search indexes (if implemented)
Impact
Service degradation or downtime
Increased hosting costs
Denial of service for legitimate users
Vercel function timeout (default 60s, configured in app/[...site]/route.ts:129)
Mitigation Strategies
Implement Rate Limiting
Add rate limiting using Vercel’s Edge Config or an external service: import { NextResponse } from 'next/server' ;
import type { NextRequest } from 'next/server' ;
import { Ratelimit } from '@upstash/ratelimit' ;
import { Redis } from '@upstash/redis' ;
const ratelimit = new Ratelimit ({
redis: Redis . fromEnv (),
limiter: Ratelimit . slidingWindow ( 10 , '1 m' ), // 10 requests per minute
});
export async function middleware ( request : NextRequest ) {
const ip = request . ip ?? '127.0.0.1' ;
const { success } = await ratelimit . limit ( ip );
if ( ! success ) {
return NextResponse . json (
{ error: 'Too many requests' },
{ status: 429 }
);
}
return NextResponse . next ();
}
export const config = {
matcher: '/:path*' ,
};
Set Resource Limits
Configure timeout and size limits in the MCP handler: createMcpHandler (
// ... server setup
{
streamableHttpEndpoint: `/ ${ endpoint } ` ,
verboseLogs: false ,
maxDuration: 30 , // Reduce from 60s to limit resource usage
}
)
Also limit search results in app/[...site]/route.ts:49: const maxResultsToUse = 5 ; // Reduce from 10 to limit processing
Implement Caching
Cache WebHelp indexes to reduce repeated fetches: lib/webhelp-index-loader.ts
import { Redis } from '@vercel/kv' ;
class CachedIndexLoader {
async loadIndex ( baseUrl : string ) : Promise < void > {
const cacheKey = `index: ${ baseUrl } ` ;
const cached = await Redis . get ( cacheKey );
if ( cached ) {
this . index = cached ;
return ;
}
// Load from remote
await this . fetchIndex ( baseUrl );
// Cache for 1 hour
await Redis . setex ( cacheKey , 3600 , this . index );
}
}
Monitor Resource Usage
Use Vercel Analytics and monitoring to track:
Function execution time
Memory usage
Bandwidth consumption
Error rates
Set up alerts for abnormal usage patterns.
3. Remote Code Execution (RCE)
Risk Level: 🔴 Critical
The server executes JavaScript from external WebHelp deployments to provide search functionality. This is the most severe security risk.
How It Works
The WebHelp index loader fetches and executes JavaScript files from the target documentation site:
lib/webhelp-index-loader.ts
// The server downloads and executes search JavaScript from the WebHelp site
await this . loadIndex ( baseUrl );
// This executes code from: ${baseUrl}/oxygen-webhelp/search/index/...
An attacker controlling a WebHelp deployment could inject malicious code that:
Accesses environment variables and secrets
Makes arbitrary network requests
Reads the filesystem (within Vercel’s constraints)
Exfiltrates data from other requests
Impact
Complete server compromise : Attacker gains code execution on your server
Data exfiltration : Access to all data processed by the server
Lateral movement : Potential access to connected services
Supply chain attack : If serving multiple organizations’ documentation
Mitigation Strategies
Strict Domain Whitelisting (Required)
This is mandatory - only fetch from trusted domains:const TRUSTED_DOMAINS = [
'docs.yourcompany.com' ,
// Add ONLY domains you control or explicitly trust
];
// Reject any request to untrusted domains
Never add domains you do not control or cannot verify. Each domain represents a potential RCE vector.
Isolate Execution Environment
Use Vercel’s Edge Runtime or isolate code execution: export const runtime = 'edge' ; // Use Edge Runtime for better isolation
Consider using a sandboxed VM or separate worker process for executing WebHelp search code.
Content Security Policy
Implement strict CSP headers in next.config.ts: const nextConfig = {
async headers () {
return [
{
source: '/:path*' ,
headers: [
{
key: 'Content-Security-Policy' ,
value: "default-src 'self'; script-src 'self'; connect-src 'self'"
},
],
},
];
},
};
Regular Security Audits
Review allowed domains regularly
Monitor for unexpected behavior
Keep dependencies updated
Audit execution logs for suspicious activity
Principle of Least Privilege
Do not store sensitive secrets in environment variables
Use separate Vercel projects for different trust levels
Limit network access from the deployment
Use service-specific credentials with minimal permissions
Security Checklist
Before deploying to production, ensure you have:
Production Deployment Example
Here’s a secure configuration for production:
app/[...site]/route.ts
middleware.ts
next.config.ts
const ALLOWED_DOMAINS = [
'docs.yourcompany.com' ,
'help.yourproduct.com'
];
function isAllowedDomain ( url : string ) : boolean {
try {
const hostname = new URL ( url ). hostname ;
return ALLOWED_DOMAINS . some ( domain =>
hostname === domain || hostname . endsWith ( '.' + domain )
);
} catch {
return false ;
}
}
const handler = async ( req : NextRequest , { params }) => {
const { site } = await params ;
const baseUrls = resolveBaseUrls ( site );
// Security: Validate all URLs
for ( const url of baseUrls ) {
if ( ! isAllowedDomain ( url )) {
return new Response ( 'Domain not allowed' , { status: 403 });
}
if ( ! url . startsWith ( 'https://' )) {
return new Response ( 'HTTPS required' , { status: 403 });
}
}
// ... continue with MCP handler
};
export const runtime = 'edge' ;
Additional Resources
OWASP SSRF Learn more about SSRF vulnerabilities
Vercel Security Vercel security best practices
Rate Limiting Implement rate limiting with Upstash
CSP Guide Content Security Policy documentation
Next Steps
Vercel Deployment Deploy with security measures in place
Configuration Configure secure production settings