@kya-os/agentshield-nextjs 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EDGE_RUNTIME_WASM_SETUP.md +348 -0
- package/bin/setup-edge-wasm.js +525 -0
- package/dist/edge-runtime-loader.d.mts +49 -0
- package/dist/edge-runtime-loader.d.ts +49 -0
- package/dist/edge-runtime-loader.js +198 -0
- package/dist/edge-runtime-loader.js.map +1 -0
- package/dist/edge-runtime-loader.mjs +192 -0
- package/dist/edge-runtime-loader.mjs.map +1 -0
- package/package.json +15 -4
- package/wasm/agentshield_wasm_bg.wasm +0 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# AgentShield WASM Setup for Next.js Edge Runtime
|
|
2
|
+
|
|
3
|
+
## The Challenge
|
|
4
|
+
|
|
5
|
+
Next.js Edge Runtime (used by middleware) has strict requirements for WebAssembly:
|
|
6
|
+
|
|
7
|
+
- ✅ Supports `WebAssembly.instantiate()`
|
|
8
|
+
- ❌ Cannot use `WebAssembly.compile()` with buffers
|
|
9
|
+
- ❌ Cannot dynamically load WASM from node_modules
|
|
10
|
+
- ✅ Requires static imports with `?module` suffix
|
|
11
|
+
|
|
12
|
+
## Solution: Manual WASM Setup
|
|
13
|
+
|
|
14
|
+
Since npm packages cannot reliably provide WASM files that work in Edge Runtime, we provide a setup
|
|
15
|
+
process that copies the necessary files to your Next.js project.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Step 1: Install AgentShield
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @kya-os/agentshield-nextjs
|
|
23
|
+
# or
|
|
24
|
+
pnpm add @kya-os/agentshield-nextjs
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Step 2: Run Setup Script
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx agentshield-setup-edge
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This will:
|
|
34
|
+
|
|
35
|
+
1. Copy the WASM file to your project root
|
|
36
|
+
2. Create TypeScript definitions
|
|
37
|
+
3. Generate an Edge-compatible loader
|
|
38
|
+
|
|
39
|
+
### Step 3: Update your middleware.ts
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// middleware.ts
|
|
43
|
+
import { NextResponse } from 'next/server';
|
|
44
|
+
import type { NextRequest } from 'next/server';
|
|
45
|
+
import { createAgentShieldMiddleware } from './lib/agentshield-edge';
|
|
46
|
+
|
|
47
|
+
const agentShield = createAgentShieldMiddleware({
|
|
48
|
+
enableWasm: true, // Enable WASM for 95%+ confidence
|
|
49
|
+
onAgentDetected: agent => {
|
|
50
|
+
console.log('AI Agent detected:', agent);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export async function middleware(request: NextRequest) {
|
|
55
|
+
// Initialize on first request
|
|
56
|
+
await agentShield.init();
|
|
57
|
+
|
|
58
|
+
// Check for AI agents
|
|
59
|
+
const result = await agentShield.detect(request);
|
|
60
|
+
|
|
61
|
+
if (result.isAgent && result.confidence > 0.85) {
|
|
62
|
+
// Handle AI agent traffic
|
|
63
|
+
console.log(`Detected ${result.agent} with ${result.confidence * 100}% confidence`);
|
|
64
|
+
|
|
65
|
+
// Optional: Block or redirect
|
|
66
|
+
// return NextResponse.redirect(new URL('/api-access-denied', request.url));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return NextResponse.next();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const config = {
|
|
73
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Manual Setup (Alternative)
|
|
78
|
+
|
|
79
|
+
If you prefer to set up manually or the script doesn't work:
|
|
80
|
+
|
|
81
|
+
### 1. Copy WASM File
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Create wasm directory in your project root
|
|
85
|
+
mkdir -p wasm
|
|
86
|
+
|
|
87
|
+
# Copy the WASM file from node_modules
|
|
88
|
+
cp node_modules/@kya-os/agentshield-nextjs/dist/wasm/agentshield_wasm_bg.wasm ./wasm/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Create TypeScript Definitions
|
|
92
|
+
|
|
93
|
+
Create `wasm/agentshield.d.ts`:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// wasm/agentshield.d.ts
|
|
97
|
+
declare module '*.wasm?module' {
|
|
98
|
+
const wasmModule: WebAssembly.Module;
|
|
99
|
+
export default wasmModule;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface AgentShieldWasm {
|
|
103
|
+
detect_agent(metadata: string): string;
|
|
104
|
+
get_version(): string;
|
|
105
|
+
Memory: WebAssembly.Memory;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3. Create Edge Loader
|
|
110
|
+
|
|
111
|
+
Create `lib/agentshield-edge.ts`:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// lib/agentshield-edge.ts
|
|
115
|
+
import type { NextRequest } from 'next/server';
|
|
116
|
+
import wasmModule from '../wasm/agentshield_wasm_bg.wasm?module';
|
|
117
|
+
|
|
118
|
+
let wasmInstance: WebAssembly.Instance | null = null;
|
|
119
|
+
let wasmMemory: WebAssembly.Memory | null = null;
|
|
120
|
+
|
|
121
|
+
interface WasmExports {
|
|
122
|
+
detect_agent(metadataPtr: number, metadataLen: number): number;
|
|
123
|
+
get_version(): number;
|
|
124
|
+
__wbindgen_malloc(size: number): number;
|
|
125
|
+
__wbindgen_free(ptr: number, size: number): void;
|
|
126
|
+
__wbindgen_realloc(ptr: number, oldSize: number, newSize: number): number;
|
|
127
|
+
memory: WebAssembly.Memory;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function initWasm(): Promise<void> {
|
|
131
|
+
if (wasmInstance) return;
|
|
132
|
+
|
|
133
|
+
// Import the WASM module (Edge Runtime compatible)
|
|
134
|
+
wasmMemory = new WebAssembly.Memory({ initial: 17, maximum: 256 });
|
|
135
|
+
|
|
136
|
+
const imports = {
|
|
137
|
+
wbg: {
|
|
138
|
+
__wbindgen_throw: (ptr: number, len: number) => {
|
|
139
|
+
throw new Error(readString(ptr, len));
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
env: {
|
|
143
|
+
memory: wasmMemory,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
wasmInstance = await WebAssembly.instantiate(wasmModule, imports);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function readString(ptr: number, len: number): string {
|
|
151
|
+
if (!wasmInstance || !wasmMemory) throw new Error('WASM not initialized');
|
|
152
|
+
const memory = new Uint8Array(wasmMemory.buffer);
|
|
153
|
+
const bytes = memory.slice(ptr, ptr + len);
|
|
154
|
+
return new TextDecoder().decode(bytes);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function writeString(str: string): [number, number] {
|
|
158
|
+
if (!wasmInstance) throw new Error('WASM not initialized');
|
|
159
|
+
const exports = wasmInstance.exports as unknown as WasmExports;
|
|
160
|
+
|
|
161
|
+
const encoded = new TextEncoder().encode(str);
|
|
162
|
+
const ptr = exports.__wbindgen_malloc(encoded.length);
|
|
163
|
+
|
|
164
|
+
const memory = new Uint8Array(exports.memory.buffer);
|
|
165
|
+
memory.set(encoded, ptr);
|
|
166
|
+
|
|
167
|
+
return [ptr, encoded.length];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface DetectionResult {
|
|
171
|
+
isAgent: boolean;
|
|
172
|
+
confidence: number;
|
|
173
|
+
agent?: string;
|
|
174
|
+
verificationMethod: 'cryptographic' | 'pattern';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function createAgentShieldMiddleware(options: {
|
|
178
|
+
enableWasm?: boolean;
|
|
179
|
+
onAgentDetected?: (result: DetectionResult) => void;
|
|
180
|
+
}) {
|
|
181
|
+
let initialized = false;
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
async init(): Promise<void> {
|
|
185
|
+
if (initialized) return;
|
|
186
|
+
|
|
187
|
+
if (options.enableWasm !== false) {
|
|
188
|
+
try {
|
|
189
|
+
await initWasm();
|
|
190
|
+
initialized = true;
|
|
191
|
+
console.log('✅ AgentShield WASM initialized in Edge Runtime');
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.warn('⚠️ WASM initialization failed, falling back to pattern detection:', error);
|
|
194
|
+
initialized = true;
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
initialized = true;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
async detect(request: NextRequest): Promise<DetectionResult> {
|
|
202
|
+
const metadata = {
|
|
203
|
+
userAgent: request.headers.get('user-agent') || '',
|
|
204
|
+
ipAddress: request.ip || request.headers.get('x-forwarded-for') || '',
|
|
205
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if (wasmInstance && options.enableWasm !== false) {
|
|
209
|
+
try {
|
|
210
|
+
const exports = wasmInstance.exports as unknown as WasmExports;
|
|
211
|
+
const [ptr, len] = writeString(JSON.stringify(metadata));
|
|
212
|
+
|
|
213
|
+
const resultPtr = exports.detect_agent(ptr, len);
|
|
214
|
+
const resultLen = 1024; // Assume max result size
|
|
215
|
+
const resultStr = readString(resultPtr, resultLen);
|
|
216
|
+
|
|
217
|
+
exports.__wbindgen_free(ptr, len);
|
|
218
|
+
exports.__wbindgen_free(resultPtr, resultLen);
|
|
219
|
+
|
|
220
|
+
const result = JSON.parse(resultStr);
|
|
221
|
+
|
|
222
|
+
if (options.onAgentDetected && result.isAgent) {
|
|
223
|
+
options.onAgentDetected(result);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
...result,
|
|
228
|
+
verificationMethod: 'cryptographic',
|
|
229
|
+
};
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error('WASM detection failed:', error);
|
|
232
|
+
// Fall through to pattern detection
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Fallback: Pattern-based detection (85% confidence)
|
|
237
|
+
const userAgent = metadata.userAgent.toLowerCase();
|
|
238
|
+
const aiAgentPatterns = [
|
|
239
|
+
{ pattern: /chatgpt/i, name: 'ChatGPT' },
|
|
240
|
+
{ pattern: /claude/i, name: 'Claude' },
|
|
241
|
+
{ pattern: /anthropic/i, name: 'Anthropic' },
|
|
242
|
+
{ pattern: /openai/i, name: 'OpenAI' },
|
|
243
|
+
{ pattern: /gpt-/i, name: 'GPT' },
|
|
244
|
+
{ pattern: /copilot/i, name: 'GitHub Copilot' },
|
|
245
|
+
{ pattern: /bard/i, name: 'Google Bard' },
|
|
246
|
+
{ pattern: /gemini/i, name: 'Google Gemini' },
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
for (const { pattern, name } of aiAgentPatterns) {
|
|
250
|
+
if (pattern.test(userAgent)) {
|
|
251
|
+
const result: DetectionResult = {
|
|
252
|
+
isAgent: true,
|
|
253
|
+
confidence: 0.85,
|
|
254
|
+
agent: name,
|
|
255
|
+
verificationMethod: 'pattern',
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
if (options.onAgentDetected) {
|
|
259
|
+
options.onAgentDetected(result);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
isAgent: false,
|
|
268
|
+
confidence: 0.95,
|
|
269
|
+
verificationMethod: 'pattern',
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## How It Works
|
|
277
|
+
|
|
278
|
+
1. **Static Import**: The WASM file is imported at build time using `?module` suffix
|
|
279
|
+
2. **Edge Compatible**: Uses `WebAssembly.instantiate()` with the imported module
|
|
280
|
+
3. **Graceful Fallback**: Falls back to pattern detection if WASM fails
|
|
281
|
+
4. **Type Safety**: Full TypeScript support with proper typing
|
|
282
|
+
|
|
283
|
+
## Verification Methods
|
|
284
|
+
|
|
285
|
+
- **Cryptographic (95-100% confidence)**: Uses WASM for cryptographic verification
|
|
286
|
+
- **Pattern (85% confidence)**: Fallback pattern matching for known AI agents
|
|
287
|
+
|
|
288
|
+
## Troubleshooting
|
|
289
|
+
|
|
290
|
+
### WASM not loading?
|
|
291
|
+
|
|
292
|
+
1. Ensure the WASM file exists at `wasm/agentshield_wasm_bg.wasm`
|
|
293
|
+
2. Check Next.js config doesn't exclude `.wasm` files
|
|
294
|
+
3. Verify the import path is correct
|
|
295
|
+
|
|
296
|
+
### TypeScript errors?
|
|
297
|
+
|
|
298
|
+
Add to your `tsconfig.json`:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"compilerOptions": {
|
|
303
|
+
"types": ["@types/node"],
|
|
304
|
+
"moduleResolution": "bundler"
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Build errors?
|
|
310
|
+
|
|
311
|
+
Update your `next.config.js`:
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
/** @type {import('next').NextConfig} */
|
|
315
|
+
const nextConfig = {
|
|
316
|
+
webpack: config => {
|
|
317
|
+
// Handle WASM imports
|
|
318
|
+
config.module.rules.push({
|
|
319
|
+
test: /\.wasm$/,
|
|
320
|
+
type: 'asset/resource',
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return config;
|
|
324
|
+
},
|
|
325
|
+
experimental: {
|
|
326
|
+
// Ensure Edge Runtime can access WASM
|
|
327
|
+
serverComponentsExternalPackages: ['@kya-os/agentshield-nextjs'],
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
module.exports = nextConfig;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Why Manual Setup?
|
|
335
|
+
|
|
336
|
+
The Edge Runtime's security model prevents dynamic code evaluation. While this adds a setup step, it
|
|
337
|
+
ensures:
|
|
338
|
+
|
|
339
|
+
1. **Reliability**: Works consistently across all deployment platforms
|
|
340
|
+
2. **Security**: No dynamic code evaluation
|
|
341
|
+
3. **Performance**: WASM is loaded at build time, not runtime
|
|
342
|
+
4. **Vercel Compatibility**: Works perfectly with Vercel's Edge Runtime
|
|
343
|
+
|
|
344
|
+
## Support
|
|
345
|
+
|
|
346
|
+
- GitHub Issues:
|
|
347
|
+
[github.com/kya-os/agentshield/issues](https://github.com/kya-os/agentshield/issues)
|
|
348
|
+
- Documentation: [agentshield.ai/docs](https://agentshield.ai/docs)
|