@kya-os/agentshield-nextjs
Version:
Next.js middleware for AgentShield AI agent detection
349 lines (268 loc) • 9.46 kB
Markdown
# AgentShield WASM Setup for Next.js Edge Runtime
## The Challenge
Next.js Edge Runtime (used by middleware) has strict requirements for WebAssembly:
- ✅ Supports `WebAssembly.instantiate()`
- ❌ Cannot use `WebAssembly.compile()` with buffers
- ❌ Cannot dynamically load WASM from node_modules
- ✅ Requires static imports with `?module` suffix
## Solution: Manual WASM Setup
Since npm packages cannot reliably provide WASM files that work in Edge Runtime, we provide a setup
process that copies the necessary files to your Next.js project.
## Quick Start
### Step 1: Install AgentShield
```bash
npm install @kya-os/agentshield-nextjs
# or
pnpm add @kya-os/agentshield-nextjs
```
### Step 2: Run Setup Script
```bash
npx agentshield-setup-edge
```
This will:
1. Copy the WASM file to your project root
2. Create TypeScript definitions
3. Generate an Edge-compatible loader
### Step 3: Update your middleware.ts
```typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { createAgentShieldMiddleware } from './lib/agentshield-edge';
const agentShield = createAgentShieldMiddleware({
enableWasm: true, // Enable WASM for 95%+ confidence
onAgentDetected: agent => {
console.log('AI Agent detected:', agent);
},
});
export async function middleware(request: NextRequest) {
// Initialize on first request
await agentShield.init();
// Check for AI agents
const result = await agentShield.detect(request);
if (result.isAgent && result.confidence > 0.85) {
// Handle AI agent traffic
console.log(`Detected ${result.agent} with ${result.confidence * 100}% confidence`);
// Optional: Block or redirect
// return NextResponse.redirect(new URL('/api-access-denied', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
```
## Manual Setup (Alternative)
If you prefer to set up manually or the script doesn't work:
### 1. Copy WASM File
```bash
# Create wasm directory in your project root
mkdir -p wasm
# Copy the WASM file from node_modules
cp node_modules/@kya-os/agentshield-nextjs/dist/wasm/agentshield_wasm_bg.wasm ./wasm/
```
### 2. Create TypeScript Definitions
Create `wasm/agentshield.d.ts`:
```typescript
// wasm/agentshield.d.ts
declare module '*.wasm?module' {
const wasmModule: WebAssembly.Module;
export default wasmModule;
}
export interface AgentShieldWasm {
detect_agent(metadata: string): string;
get_version(): string;
Memory: WebAssembly.Memory;
}
```
### 3. Create Edge Loader
Create `lib/agentshield-edge.ts`:
```typescript
// lib/agentshield-edge.ts
import type { NextRequest } from 'next/server';
import wasmModule from '../wasm/agentshield_wasm_bg.wasm?module';
let wasmInstance: WebAssembly.Instance | null = null;
let wasmMemory: WebAssembly.Memory | null = null;
interface WasmExports {
detect_agent(metadataPtr: number, metadataLen: number): number;
get_version(): number;
__wbindgen_malloc(size: number): number;
__wbindgen_free(ptr: number, size: number): void;
__wbindgen_realloc(ptr: number, oldSize: number, newSize: number): number;
memory: WebAssembly.Memory;
}
async function initWasm(): Promise<void> {
if (wasmInstance) return;
// Import the WASM module (Edge Runtime compatible)
wasmMemory = new WebAssembly.Memory({ initial: 17, maximum: 256 });
const imports = {
wbg: {
__wbindgen_throw: (ptr: number, len: number) => {
throw new Error(readString(ptr, len));
},
},
env: {
memory: wasmMemory,
},
};
wasmInstance = await WebAssembly.instantiate(wasmModule, imports);
}
function readString(ptr: number, len: number): string {
if (!wasmInstance || !wasmMemory) throw new Error('WASM not initialized');
const memory = new Uint8Array(wasmMemory.buffer);
const bytes = memory.slice(ptr, ptr + len);
return new TextDecoder().decode(bytes);
}
function writeString(str: string): [number, number] {
if (!wasmInstance) throw new Error('WASM not initialized');
const exports = wasmInstance.exports as unknown as WasmExports;
const encoded = new TextEncoder().encode(str);
const ptr = exports.__wbindgen_malloc(encoded.length);
const memory = new Uint8Array(exports.memory.buffer);
memory.set(encoded, ptr);
return [ptr, encoded.length];
}
export interface DetectionResult {
isAgent: boolean;
confidence: number;
agent?: string;
verificationMethod: 'cryptographic' | 'pattern';
}
export function createAgentShieldMiddleware(options: {
enableWasm?: boolean;
onAgentDetected?: (result: DetectionResult) => void;
}) {
let initialized = false;
return {
async init(): Promise<void> {
if (initialized) return;
if (options.enableWasm !== false) {
try {
await initWasm();
initialized = true;
console.log('✅ AgentShield WASM initialized in Edge Runtime');
} catch (error) {
console.warn('⚠️ WASM initialization failed, falling back to pattern detection:', error);
initialized = true;
}
} else {
initialized = true;
}
},
async detect(request: NextRequest): Promise<DetectionResult> {
const metadata = {
userAgent: request.headers.get('user-agent') || '',
ipAddress: request.ip || request.headers.get('x-forwarded-for') || '',
headers: Object.fromEntries(request.headers.entries()),
};
if (wasmInstance && options.enableWasm !== false) {
try {
const exports = wasmInstance.exports as unknown as WasmExports;
const [ptr, len] = writeString(JSON.stringify(metadata));
const resultPtr = exports.detect_agent(ptr, len);
const resultLen = 1024; // Assume max result size
const resultStr = readString(resultPtr, resultLen);
exports.__wbindgen_free(ptr, len);
exports.__wbindgen_free(resultPtr, resultLen);
const result = JSON.parse(resultStr);
if (options.onAgentDetected && result.isAgent) {
options.onAgentDetected(result);
}
return {
...result,
verificationMethod: 'cryptographic',
};
} catch (error) {
console.error('WASM detection failed:', error);
// Fall through to pattern detection
}
}
// Fallback: Pattern-based detection (85% confidence)
const userAgent = metadata.userAgent.toLowerCase();
const aiAgentPatterns = [
{ pattern: /chatgpt/i, name: 'ChatGPT' },
{ pattern: /claude/i, name: 'Claude' },
{ pattern: /anthropic/i, name: 'Anthropic' },
{ pattern: /openai/i, name: 'OpenAI' },
{ pattern: /gpt-/i, name: 'GPT' },
{ pattern: /copilot/i, name: 'GitHub Copilot' },
{ pattern: /bard/i, name: 'Google Bard' },
{ pattern: /gemini/i, name: 'Google Gemini' },
];
for (const { pattern, name } of aiAgentPatterns) {
if (pattern.test(userAgent)) {
const result: DetectionResult = {
isAgent: true,
confidence: 0.85,
agent: name,
verificationMethod: 'pattern',
};
if (options.onAgentDetected) {
options.onAgentDetected(result);
}
return result;
}
}
return {
isAgent: false,
confidence: 0.95,
verificationMethod: 'pattern',
};
},
};
}
```
## How It Works
1. **Static Import**: The WASM file is imported at build time using `?module` suffix
2. **Edge Compatible**: Uses `WebAssembly.instantiate()` with the imported module
3. **Graceful Fallback**: Falls back to pattern detection if WASM fails
4. **Type Safety**: Full TypeScript support with proper typing
## Verification Methods
- **Cryptographic (95-100% confidence)**: Uses WASM for cryptographic verification
- **Pattern (85% confidence)**: Fallback pattern matching for known AI agents
## Troubleshooting
### WASM not loading?
1. Ensure the WASM file exists at `wasm/agentshield_wasm_bg.wasm`
2. Check Next.js config doesn't exclude `.wasm` files
3. Verify the import path is correct
### TypeScript errors?
Add to your `tsconfig.json`:
```json
{
"compilerOptions": {
"types": ["@types/node"],
"moduleResolution": "bundler"
}
}
```
### Build errors?
Update your `next.config.js`:
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: config => {
// Handle WASM imports
config.module.rules.push({
test: /\.wasm$/,
type: 'asset/resource',
});
return config;
},
experimental: {
// Ensure Edge Runtime can access WASM
serverComponentsExternalPackages: ['@kya-os/agentshield-nextjs'],
},
};
module.exports = nextConfig;
```
## Why Manual Setup?
The Edge Runtime's security model prevents dynamic code evaluation. While this adds a setup step, it
ensures:
1. **Reliability**: Works consistently across all deployment platforms
2. **Security**: No dynamic code evaluation
3. **Performance**: WASM is loaded at build time, not runtime
4. **Vercel Compatibility**: Works perfectly with Vercel's Edge Runtime
## Support
- GitHub Issues:
[github.com/kya-os/agentshield/issues](https://github.com/kya-os/agentshield/issues)
- Documentation: [agentshield.ai/docs](https://agentshield.ai/docs)