@kya-os/agentshield-nextjs
Version:
Next.js middleware for AgentShield AI agent detection
185 lines (182 loc) • 6.83 kB
JavaScript
;
var server = require('next/server');
var agentshieldShared = require('@kya-os/agentshield-shared');
// src/policy.ts
function createContextFromDetection(detection, request) {
return agentshieldShared.createEvaluationContext({
agentType: detection.detectedAgent?.type,
agentName: detection.detectedAgent?.name,
agentVendor: detection.detectedAgent?.vendor,
confidence: detection.confidence,
riskLevel: detection.riskLevel,
path: request.nextUrl.pathname,
method: request.method,
signatureVerified: detection.verificationMethod === "signature",
isAuthenticated: false,
// TODO: integrate with auth
userAgent: request.headers.get("user-agent") || void 0
});
}
function evaluatePolicyForDetection(detection, request, policy) {
const context = createContextFromDetection(detection, request);
return agentshieldShared.evaluatePolicy(policy, context);
}
function buildBlockedResponse(decision, config) {
const status = config.blockedResponse?.status ?? 403;
const message = config.blockedResponse?.message ?? decision.message ?? "Access denied";
const response = server.NextResponse.json(
{
error: message,
code: "POLICY_BLOCKED",
reason: decision.reason,
ruleId: decision.ruleId,
matchType: decision.matchType
},
{ status }
);
if (config.blockedResponse?.headers) {
for (const [key, value] of Object.entries(config.blockedResponse.headers)) {
response.headers.set(key, value);
}
}
response.headers.set("X-AgentShield-Action", decision.action);
response.headers.set("X-AgentShield-Reason", decision.reason);
response.headers.set("X-AgentShield-MatchType", decision.matchType);
return response;
}
function buildRedirectResponse(request, decision, config) {
const redirectUrl = decision.redirectUrl || config.redirectUrl || "/blocked";
const url = new URL(redirectUrl, request.url);
url.searchParams.set("reason", decision.reason);
if (decision.ruleId) {
url.searchParams.set("ruleId", decision.ruleId);
}
return server.NextResponse.redirect(url);
}
function buildChallengeResponse(request, decision, config) {
return buildRedirectResponse(request, decision, config);
}
async function handlePolicyDecision(request, decision, config) {
switch (decision.action) {
case agentshieldShared.ENFORCEMENT_ACTIONS.BLOCK:
if (config.customBlockedResponse) {
return await config.customBlockedResponse(request, decision);
}
return buildBlockedResponse(decision, config);
case agentshieldShared.ENFORCEMENT_ACTIONS.REDIRECT:
return buildRedirectResponse(request, decision, config);
case agentshieldShared.ENFORCEMENT_ACTIONS.CHALLENGE:
return buildChallengeResponse(request, decision, config);
case agentshieldShared.ENFORCEMENT_ACTIONS.LOG:
console.log("[AgentShield] Policy decision (log):", {
path: request.nextUrl.pathname,
action: decision.action,
reason: decision.reason,
matchType: decision.matchType,
ruleId: decision.ruleId
});
return null;
// Continue to allow
case agentshieldShared.ENFORCEMENT_ACTIONS.ALLOW:
default:
return null;
}
}
var fetcherCache = /* @__PURE__ */ new Map();
function getFetcherCacheKey(config) {
return `${config.apiUrl ?? "default"}:${config.apiKey ?? ""}:${config.cacheTtlSeconds ?? "default"}`;
}
function getPolicyFetcher(config) {
if (!config) {
throw new Error("fetchPolicy config required");
}
const cacheKey = getFetcherCacheKey(config);
let fetcher = fetcherCache.get(cacheKey);
if (!fetcher) {
const fetcherConfig = {
apiBaseUrl: config.apiUrl || "https://kya.vouched.id",
apiKey: config.apiKey,
cacheTtlSeconds: config.cacheTtlSeconds
};
fetcher = agentshieldShared.createPolicyFetcher(fetcherConfig);
fetcherCache.set(cacheKey, fetcher);
}
return fetcher;
}
async function getPolicy(config) {
if (config.policy) {
return agentshieldShared.PolicyConfigSchema.parse({ ...agentshieldShared.DEFAULT_POLICY, ...config.policy });
}
if (config.fetchPolicy) {
try {
const fetcher = getPolicyFetcher(config.fetchPolicy);
return await fetcher.getPolicy(config.fetchPolicy.projectId);
} catch (error) {
if (config.debug) {
console.warn("[AgentShield] Policy fetch failed, using fallback:", error);
}
return agentshieldShared.PolicyConfigSchema.parse({
...agentshieldShared.DEFAULT_POLICY,
...config.fallbackPolicy || {}
});
}
}
return agentshieldShared.PolicyConfigSchema.parse(agentshieldShared.DEFAULT_POLICY);
}
async function applyPolicy(request, detection, config) {
try {
const path = request.nextUrl.pathname;
if (config.skipPaths?.some((pattern) => agentshieldShared.matchPath(path, pattern))) {
return null;
}
if (config.includePaths && config.includePaths.length > 0) {
if (!config.includePaths.some((pattern) => agentshieldShared.matchPath(path, pattern))) {
return null;
}
}
const policy = await getPolicy(config);
const context = createContextFromDetection(detection, request);
const decision = agentshieldShared.evaluatePolicy(policy, context);
if (config.onPolicyDecision) {
await config.onPolicyDecision(request, decision, context);
}
return await handlePolicyDecision(request, decision, config);
} catch (error) {
if (config.debug) {
console.error("[AgentShield] Policy evaluation error:", error);
}
if (config.failOpen !== false) {
return null;
}
return server.NextResponse.json(
{ error: "Security check failed", code: "POLICY_ERROR" },
{ status: 503 }
);
}
}
Object.defineProperty(exports, "DEFAULT_POLICY", {
enumerable: true,
get: function () { return agentshieldShared.DEFAULT_POLICY; }
});
Object.defineProperty(exports, "ENFORCEMENT_ACTIONS", {
enumerable: true,
get: function () { return agentshieldShared.ENFORCEMENT_ACTIONS; }
});
Object.defineProperty(exports, "createEvaluationContext", {
enumerable: true,
get: function () { return agentshieldShared.createEvaluationContext; }
});
Object.defineProperty(exports, "evaluatePolicy", {
enumerable: true,
get: function () { return agentshieldShared.evaluatePolicy; }
});
exports.applyPolicy = applyPolicy;
exports.buildBlockedResponse = buildBlockedResponse;
exports.buildChallengeResponse = buildChallengeResponse;
exports.buildRedirectResponse = buildRedirectResponse;
exports.createContextFromDetection = createContextFromDetection;
exports.evaluatePolicyForDetection = evaluatePolicyForDetection;
exports.getPolicy = getPolicy;
exports.handlePolicyDecision = handlePolicyDecision;
//# sourceMappingURL=policy.js.map
//# sourceMappingURL=policy.js.map