Next.js Integration

The Surfinguard JS SDK provides utilities for protecting Next.js applications, including API route wrappers and server component helpers.

Installation

npm install @surfinguard/sdk

API Route Protection

App Router (Route Handlers)

Wrap your route handlers with withSurfinguard to automatically check actions:

// app/api/execute/route.ts
import { Guard, withSurfinguard } from '@surfinguard/sdk';
import { NextRequest, NextResponse } from 'next/server';
 
const guard = await Guard.create({ mode: 'local' });
 
export const POST = withSurfinguard(guard, {
  extractAction: (req) => ({
    type: 'command',
    value: req.body.command,
  }),
}, async (req: NextRequest) => {
  const { command } = await req.json();
  // If we reach here, the command passed the security check
  return NextResponse.json({ output: 'executed' });
});

Manual Check in API Routes

For more control, call the guard directly in your route handler:

// app/api/chat/route.ts
import { Guard } from '@surfinguard/sdk';
import { NextRequest, NextResponse } from 'next/server';
 
const guard = await Guard.create({ mode: 'local' });
 
export async function POST(req: NextRequest) {
  const { message } = await req.json();
 
  // Check for prompt injection
  const result = guard.checkText(message);
 
  if (result.level === 'DANGER') {
    return NextResponse.json(
      {
        error: 'Message blocked by security policy',
        reasons: result.reasons,
      },
      { status: 403 }
    );
  }
 
  if (result.level === 'CAUTION') {
    // Log but allow
    console.warn('Caution-level message detected:', result.reasons);
  }
 
  // Process the message
  const reply = await generateReply(message);
  return NextResponse.json({ reply });
}

Server Components

Use Surfinguard in React Server Components to validate data before rendering:

// app/preview/page.tsx
import { Guard } from '@surfinguard/sdk';
 
const guard = await Guard.create({ mode: 'local' });
 
export default async function PreviewPage({
  searchParams,
}: {
  searchParams: { url?: string };
}) {
  const url = searchParams.url;
 
  if (!url) {
    return <div>No URL provided</div>;
  }
 
  const result = guard.checkUrl(url);
 
  if (result.level === 'DANGER') {
    return (
      <div className="p-4 bg-red-50 border border-red-200 rounded">
        <h2 className="text-red-800 font-bold">Dangerous URL Detected</h2>
        <p className="text-red-600">This URL has been flagged as dangerous.</p>
        <ul className="list-disc list-inside mt-2">
          {result.reasons.map((reason, i) => (
            <li key={i} className="text-red-600">{reason}</li>
          ))}
        </ul>
      </div>
    );
  }
 
  // Safe to render preview
  return <iframe src={url} className="w-full h-screen" />;
}

Server Actions

Protect Server Actions with Surfinguard checks:

// app/actions.ts
'use server';
 
import { Guard, NotAllowedError } from '@surfinguard/sdk';
 
const guard = await Guard.create({ mode: 'local', policy: 'moderate' });
 
export async function executeQuery(query: string) {
  // Check the query before execution
  const result = guard.checkQuery(query);
 
  if (result.level === 'DANGER') {
    return { error: 'Query blocked', reasons: result.reasons };
  }
 
  // Execute the query
  const data = await db.query(query);
  return { data };
}
 
export async function fetchUrl(url: string) {
  const result = guard.checkUrl(url);
 
  if (result.level !== 'SAFE') {
    return {
      error: 'URL flagged',
      level: result.level,
      reasons: result.reasons,
    };
  }
 
  const response = await fetch(url);
  return { html: await response.text() };
}

Middleware

Use Next.js middleware for request-level protection:

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { Guard } from '@surfinguard/sdk';
 
// Note: Guard initialization in middleware runs on every request.
// For production, consider caching the guard instance.
const guard = await Guard.create({ mode: 'local' });
 
export function middleware(req: NextRequest) {
  // Check URL parameters
  const url = req.nextUrl.searchParams.get('redirect');
  if (url) {
    const result = guard.checkUrl(url);
    if (result.level === 'DANGER') {
      return NextResponse.json(
        { error: 'Dangerous redirect URL blocked' },
        { status: 403 }
      );
    }
  }
 
  return NextResponse.next();
}
 
export const config = {
  matcher: '/api/:path*',
};

Full Example: AI Chat Application

// app/api/chat/route.ts
import { Guard } from '@surfinguard/sdk';
import { NextRequest, NextResponse } from 'next/server';
 
const guard = await Guard.create({ mode: 'local' });
 
export async function POST(req: NextRequest) {
  const { messages } = await req.json();
  const lastMessage = messages[messages.length - 1];
 
  // Check for prompt injection in user message
  const textResult = guard.checkText(lastMessage.content);
  if (textResult.level === 'DANGER') {
    return NextResponse.json({
      role: 'assistant',
      content: 'I detected a potential security issue in your message and cannot process it.',
      blocked: true,
      reasons: textResult.reasons,
    });
  }
 
  // Generate AI response
  const aiResponse = await generateAIResponse(messages);
 
  // Check any URLs in the AI response
  const urlRegex = /https?:\/\/[^\s)]+/g;
  const urls = aiResponse.match(urlRegex) || [];
 
  for (const url of urls) {
    const urlResult = guard.checkUrl(url);
    if (urlResult.level === 'DANGER') {
      return NextResponse.json({
        role: 'assistant',
        content: 'The AI response contained a potentially dangerous URL and has been blocked.',
        blocked: true,
      });
    }
  }
 
  return NextResponse.json({
    role: 'assistant',
    content: aiResponse,
  });
}