Skip to main content

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:
app/[...site]/route.ts
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

1

Implement URL Whitelisting

Restrict the server to only fetch from approved documentation domains:
app/[...site]/route.ts
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...
};
2

Block Private IP Ranges

Prevent requests to internal network addresses:
lib/downloadFile.ts
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');
  }
}
3

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

1

Implement Rate Limiting

Add rate limiting using Vercel’s Edge Config or an external service:
middleware.ts
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*',
};
2

Set Resource Limits

Configure timeout and size limits in the MCP handler:
app/[...site]/route.ts
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
3

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);
  }
}
4

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

1

Strict Domain Whitelisting (Required)

This is mandatory - only fetch from trusted domains:
app/[...site]/route.ts
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.
2

Isolate Execution Environment

Use Vercel’s Edge Runtime or isolate code execution:
app/[...site]/route.ts
export const runtime = 'edge'; // Use Edge Runtime for better isolation
Consider using a sandboxed VM or separate worker process for executing WebHelp search code.
3

Content Security Policy

Implement strict CSP headers in next.config.ts:
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'"
          },
        ],
      },
    ];
  },
};
4

Regular Security Audits

  • Review allowed domains regularly
  • Monitor for unexpected behavior
  • Keep dependencies updated
  • Audit execution logs for suspicious activity
5

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

Access Controls

  • Implemented domain whitelisting
  • Blocked private IP ranges
  • Validated all input URLs
  • Restricted to HTTPS only
2

Rate Limiting

  • Configured rate limiting per IP
  • Set appropriate timeout values
  • Limited result set sizes
  • Implemented caching strategy
3

Execution Security

  • Restricted to trusted domains only
  • Used Edge Runtime or isolation
  • Implemented CSP headers
  • Removed sensitive environment variables
4

Monitoring

  • Enabled logging and monitoring
  • Set up alerts for anomalies
  • Regular security audit schedule
  • Incident response plan

Production Deployment Example

Here’s a secure configuration for production:
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

Build docs developers (and LLMs) love