@kya-os/agentshield-nextjs
Version:
Next.js middleware for AgentShield AI agent detection
253 lines (250 loc) • 9.34 kB
JavaScript
;
var server = require('next/server');
// src/edge/index.ts
async function patternDetect(input) {
const userAgent = input.userAgent?.toLowerCase() || "";
const reasons = [];
let confidence = 0;
let detectedAgent;
const patterns = [
{ pattern: /chatgpt-user/i, name: "ChatGPT", confidence: 95 },
{ pattern: /claude-web/i, name: "Claude", confidence: 95 },
{ pattern: /claude-?bot/i, name: "ClaudeBot", confidence: 95 },
{ pattern: /anthropic/i, name: "Claude", confidence: 90 },
{ pattern: /gptbot/i, name: "GPTBot", confidence: 90 },
{ pattern: /perplexity/i, name: "Perplexity", confidence: 90 },
{ pattern: /openai/i, name: "OpenAI", confidence: 85 },
{ pattern: /copilot/i, name: "Copilot", confidence: 85 },
{ pattern: /gemini/i, name: "Gemini", confidence: 85 },
{ pattern: /bard/i, name: "Bard", confidence: 85 },
// HTTP client libraries (often used by AI agents/scrapers)
{ pattern: /^axios\//i, name: "HTTP Client (axios)", confidence: 75 },
{ pattern: /^node-fetch\//i, name: "HTTP Client (node-fetch)", confidence: 75 },
{ pattern: /^got\//i, name: "HTTP Client (got)", confidence: 75 },
{ pattern: /^python-requests\//i, name: "HTTP Client (requests)", confidence: 75 },
{ pattern: /^python-urllib/i, name: "HTTP Client (urllib)", confidence: 75 },
{ pattern: /^curl\//i, name: "HTTP Client (curl)", confidence: 60 },
{ pattern: /^wget\//i, name: "HTTP Client (wget)", confidence: 60 },
{ pattern: /^httpie\//i, name: "HTTP Client (httpie)", confidence: 70 },
{ pattern: /^undici\//i, name: "HTTP Client (undici)", confidence: 75 }
];
for (const { pattern, name, confidence: patternConfidence } of patterns) {
if (pattern.test(userAgent)) {
confidence = patternConfidence;
detectedAgent = { type: name.toLowerCase(), name };
reasons.push(`known_pattern:${name.toLowerCase()}`);
break;
}
}
const aiHeaders = ["x-openai-", "x-anthropic-", "x-ai-", "signature-agent"];
for (const [key] of Object.entries(input.headers)) {
if (aiHeaders.some((prefix) => key.toLowerCase().startsWith(prefix))) {
confidence = Math.max(confidence, 45);
reasons.push(`ai_headers:${key}`);
break;
}
}
return {
isAgent: confidence > 30,
confidence,
detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
signals: [],
...detectedAgent && { detectedAgent },
reasons,
verificationMethod: "pattern",
forgeabilityRisk: confidence > 80 ? "medium" : "high",
timestamp: /* @__PURE__ */ new Date()
};
}
async function createDetector(config) {
let detector;
if (config.wasmModule) {
try {
const { StaticWasmLoader, WasmDetector, PolicyLoader } = await import('@kya-os/agentshield-wasm-runtime/edge');
const loader = new StaticWasmLoader(config.wasmModule);
const policyLoader = config.apiKey ? new PolicyLoader({
apiUrl: config.policyApiUrl
}) : void 0;
const wasmDetector = new WasmDetector(loader, policyLoader, {
apiKey: config.apiKey,
debug: config.debug
});
await wasmDetector.ensureReady();
if (config.debug) {
console.debug("[AgentShield] WASM detector initialized in Edge Runtime", {
policyEnabled: !!config.apiKey
});
}
detector = {
detect: async (input) => {
const result = await wasmDetector.detect(input);
return {
isAgent: result.isAgent,
confidence: result.confidence,
detectionClass: result.detectionClass,
detectedAgent: result.detectedAgent,
verificationMethod: result.verificationMethod,
forgeabilityRisk: result.forgeabilityRisk,
reasons: result.reasons,
timestamp: result.timestamp,
signals: [],
// Required by DetectionResult, empty for WASM results
shouldBlock: result.shouldBlock,
blockReason: result.blockReason
};
},
isReady: () => wasmDetector.isReady()
};
} catch (error) {
if (config.debug) {
console.warn("[AgentShield] WASM runtime not available, using pattern detection:", error);
}
detector = {
detect: patternDetect,
isReady: () => true
};
}
} else {
if (config.debug) {
console.debug("[AgentShield] No WASM module provided, using pattern detection");
}
detector = {
detect: patternDetect,
isReady: () => true
};
}
return detector;
}
function createEdgeMiddleware(config = {}) {
let detectorPromise = null;
const {
onAgentDetected = "log",
onDetection,
skipPaths = [],
confidenceThreshold = 70,
blockedResponse = {
status: 403,
message: "Access denied: Automated agent detected",
headers: { "Content-Type": "application/json" }
},
redirectUrl = "/blocked",
rewriteUrl = "/blocked",
debug = false
} = config;
return async (request) => {
try {
if (!detectorPromise) {
detectorPromise = createDetector({ ...config, debug });
}
const detector = await detectorPromise;
const shouldSkip = skipPaths.some((pattern) => {
if (typeof pattern === "string") {
return request.nextUrl.pathname.startsWith(pattern);
}
return pattern.test(request.nextUrl.pathname);
});
if (shouldSkip) {
return server.NextResponse.next();
}
const url = new URL(request.url);
const xForwardedFor = request.headers.get("x-forwarded-for");
const clientIp = xForwardedFor?.split(",")[0]?.trim();
const input = {
userAgent: request.headers.get("user-agent") || void 0,
ipAddress: request.ip || clientIp || void 0,
headers: Object.fromEntries(request.headers.entries()),
url: url.pathname + url.search,
method: request.method
};
const result = await detector.detect(input);
if (result.shouldBlock) {
if (debug) {
console.debug("[AgentShield] Blocked by policy", {
reason: result.blockReason,
agent: result.detectedAgent?.name,
confidence: result.confidence,
pathname: request.nextUrl.pathname
});
}
if (onDetection) {
const customResponse = await onDetection(request, result);
if (customResponse) {
return customResponse;
}
}
return server.NextResponse.json(
{
error: "Access denied by policy",
reason: result.blockReason,
detected: true,
confidence: result.confidence,
agent: result.detectedAgent?.name,
timestamp: result.timestamp
},
{
status: 403,
headers: { "Content-Type": "application/json" }
}
);
}
if (result.shouldBlock !== false && result.isAgent && result.confidence >= confidenceThreshold) {
if (onDetection) {
const customResponse = await onDetection(request, result);
if (customResponse) {
return customResponse;
}
}
switch (onAgentDetected) {
case "block": {
const response2 = server.NextResponse.json(
{
error: blockedResponse.message,
detected: true,
confidence: result.confidence,
timestamp: result.timestamp
},
{ status: blockedResponse.status }
);
if (blockedResponse.headers) {
Object.entries(blockedResponse.headers).forEach(([key, value]) => {
response2.headers.set(key, value);
});
}
return response2;
}
case "redirect":
return server.NextResponse.redirect(new URL(redirectUrl, request.url));
case "rewrite":
return server.NextResponse.rewrite(new URL(rewriteUrl, request.url));
case "log":
if (debug || process.env.NODE_ENV !== "production") {
console.debug("AgentShield: Agent detected", {
ipAddress: input.ipAddress,
userAgent: input.userAgent,
confidence: result.confidence,
agent: result.detectedAgent?.name,
pathname: request.nextUrl.pathname
});
}
break;
case "allow":
default:
break;
}
}
const response = server.NextResponse.next();
response.headers.set("x-agentshield-detected", result.isAgent.toString());
response.headers.set("x-agentshield-confidence", result.confidence.toString());
if (result.detectedAgent?.name) {
response.headers.set("x-agentshield-agent", result.detectedAgent.name);
}
return response;
} catch (error) {
console.error("AgentShield middleware error:", error);
return server.NextResponse.next();
}
};
}
exports.createEdgeMiddleware = createEdgeMiddleware;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map