@kya-os/agentshield-nextjs 0.1.25 → 0.1.28
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/README.md +24 -36
- package/dist/create-middleware.d.mts +1 -1
- package/dist/create-middleware.d.ts +1 -1
- package/dist/create-middleware.js +171 -2
- package/dist/create-middleware.js.map +1 -1
- package/dist/create-middleware.mjs +171 -2
- package/dist/create-middleware.mjs.map +1 -1
- package/dist/edge-detector-wrapper.js +8 -1
- package/dist/edge-detector-wrapper.js.map +1 -1
- package/dist/edge-detector-wrapper.mjs +8 -1
- package/dist/edge-detector-wrapper.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +226 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +225 -3
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +171 -2
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +171 -2
- package/dist/middleware.mjs.map +1 -1
- package/dist/session-tracker.d.mts +55 -0
- package/dist/session-tracker.d.ts +55 -0
- package/dist/session-tracker.js +176 -0
- package/dist/session-tracker.js.map +1 -0
- package/dist/session-tracker.mjs +173 -0
- package/dist/session-tracker.mjs.map +1 -0
- package/dist/{types-Hsgc8Gry.d.mts → types-BJTEUa4T.d.mts} +28 -0
- package/dist/{types-Hsgc8Gry.d.ts → types-BJTEUa4T.d.ts} +28 -0
- package/package.json +2 -2
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/session-tracker.ts
|
|
4
|
+
var EdgeSessionTracker = class {
|
|
5
|
+
config;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = {
|
|
8
|
+
enabled: config.enabled,
|
|
9
|
+
cookieName: config.cookieName || "__agentshield_session",
|
|
10
|
+
cookieMaxAge: config.cookieMaxAge || 3600,
|
|
11
|
+
// 1 hour default
|
|
12
|
+
encryptionKey: config.encryptionKey || process.env.AGENTSHIELD_SECRET || "agentshield-default-key"
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Track a new AI agent session
|
|
17
|
+
*/
|
|
18
|
+
async track(_request, response, result) {
|
|
19
|
+
try {
|
|
20
|
+
if (!this.config.enabled || !result.isAgent) {
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
23
|
+
const sessionData = {
|
|
24
|
+
id: crypto.randomUUID(),
|
|
25
|
+
agent: result.detectedAgent?.name || "unknown",
|
|
26
|
+
confidence: result.confidence,
|
|
27
|
+
detectedAt: Date.now(),
|
|
28
|
+
expires: Date.now() + this.config.cookieMaxAge * 1e3
|
|
29
|
+
};
|
|
30
|
+
const encrypted = await this.encrypt(JSON.stringify(sessionData));
|
|
31
|
+
response.cookies.set(this.config.cookieName, encrypted, {
|
|
32
|
+
httpOnly: true,
|
|
33
|
+
secure: process.env.NODE_ENV === "production",
|
|
34
|
+
sameSite: "lax",
|
|
35
|
+
maxAge: this.config.cookieMaxAge,
|
|
36
|
+
path: "/"
|
|
37
|
+
});
|
|
38
|
+
return response;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (process.env.DEBUG_AGENTSHIELD) {
|
|
41
|
+
console.warn("AgentShield: Failed to track session:", error);
|
|
42
|
+
}
|
|
43
|
+
return response;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Check for existing AI agent session
|
|
48
|
+
*/
|
|
49
|
+
async check(request) {
|
|
50
|
+
try {
|
|
51
|
+
if (!this.config.enabled) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const cookie = request.cookies.get(this.config.cookieName);
|
|
55
|
+
if (!cookie?.value) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const decrypted = await this.decrypt(cookie.value);
|
|
59
|
+
const session = JSON.parse(decrypted);
|
|
60
|
+
if (session.expires < Date.now()) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return session;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (process.env.DEBUG_AGENTSHIELD) {
|
|
66
|
+
console.warn("AgentShield: Failed to check session:", error);
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Clear an existing session
|
|
73
|
+
*/
|
|
74
|
+
clear(response) {
|
|
75
|
+
try {
|
|
76
|
+
response.cookies.delete(this.config.cookieName);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (process.env.DEBUG_AGENTSHIELD) {
|
|
79
|
+
console.warn("AgentShield: Failed to clear session:", error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Simple encryption using Web Crypto API (Edge-compatible)
|
|
86
|
+
*/
|
|
87
|
+
async encrypt(data) {
|
|
88
|
+
try {
|
|
89
|
+
const key = this.config.encryptionKey;
|
|
90
|
+
const encoded = new TextEncoder().encode(data);
|
|
91
|
+
const obfuscated = new Uint8Array(encoded.length);
|
|
92
|
+
for (let i = 0; i < encoded.length; i++) {
|
|
93
|
+
obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
94
|
+
}
|
|
95
|
+
return btoa(
|
|
96
|
+
Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join("")
|
|
97
|
+
);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return btoa(data);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Simple decryption (Edge-compatible)
|
|
104
|
+
*/
|
|
105
|
+
async decrypt(data) {
|
|
106
|
+
try {
|
|
107
|
+
const key = this.config.encryptionKey;
|
|
108
|
+
const decoded = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
|
|
109
|
+
const deobfuscated = new Uint8Array(decoded.length);
|
|
110
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
111
|
+
deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
112
|
+
}
|
|
113
|
+
return new TextDecoder().decode(deobfuscated);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return atob(data);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var StatelessSessionChecker = class {
|
|
120
|
+
static check(headers) {
|
|
121
|
+
try {
|
|
122
|
+
const agent = headers["x-agentshield-session-agent"];
|
|
123
|
+
const confidence = headers["x-agentshield-session-confidence"];
|
|
124
|
+
const sessionId = headers["x-agentshield-session-id"];
|
|
125
|
+
if (agent && confidence && sessionId) {
|
|
126
|
+
return {
|
|
127
|
+
id: sessionId,
|
|
128
|
+
agent,
|
|
129
|
+
confidence: parseFloat(confidence),
|
|
130
|
+
detectedAt: Date.now(),
|
|
131
|
+
expires: Date.now() + 36e5
|
|
132
|
+
// 1 hour
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const cookieHeader = headers["cookie"];
|
|
136
|
+
if (cookieHeader && cookieHeader.includes("__agentshield_session=")) {
|
|
137
|
+
const match = cookieHeader.match(/__agentshield_session=([^;]+)/);
|
|
138
|
+
if (match && match[1]) {
|
|
139
|
+
try {
|
|
140
|
+
const decoded = atob(match[1]);
|
|
141
|
+
return JSON.parse(decoded);
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
static setHeaders(response, session) {
|
|
152
|
+
try {
|
|
153
|
+
if (response.setHeader) {
|
|
154
|
+
response.setHeader("X-AgentShield-Session-Agent", session.agent);
|
|
155
|
+
response.setHeader(
|
|
156
|
+
"X-AgentShield-Session-Confidence",
|
|
157
|
+
session.confidence.toString()
|
|
158
|
+
);
|
|
159
|
+
response.setHeader("X-AgentShield-Session-Id", session.id);
|
|
160
|
+
} else if (response.headers && response.headers.set) {
|
|
161
|
+
response.headers.set("x-agentshield-session-agent", session.agent);
|
|
162
|
+
response.headers.set(
|
|
163
|
+
"x-agentshield-session-confidence",
|
|
164
|
+
session.confidence.toString()
|
|
165
|
+
);
|
|
166
|
+
response.headers.set("x-agentshield-session-id", session.id);
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
exports.EdgeSessionTracker = EdgeSessionTracker;
|
|
174
|
+
exports.StatelessSessionChecker = StatelessSessionChecker;
|
|
175
|
+
//# sourceMappingURL=session-tracker.js.map
|
|
176
|
+
//# sourceMappingURL=session-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/session-tracker.ts"],"names":[],"mappings":"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","file":"session-tracker.js","sourcesContent":["/**\n * Edge-compatible session tracking for AI agents\n * Uses cookie-based storage to work in Edge Runtime\n */\n\nimport type { NextRequest, NextResponse } from 'next/server';\nimport type { DetectionResult } from '@kya-os/agentshield';\n\nexport interface SessionData {\n id: string;\n agent: string;\n confidence: number;\n detectedAt: number;\n expires: number;\n}\n\nexport interface SessionTrackingConfig {\n enabled: boolean;\n cookieName?: string;\n cookieMaxAge?: number; // in seconds\n encryptionKey?: string;\n}\n\nexport class EdgeSessionTracker {\n private readonly config: Required<SessionTrackingConfig>;\n\n constructor(config: SessionTrackingConfig) {\n this.config = {\n enabled: config.enabled,\n cookieName: config.cookieName || '__agentshield_session',\n cookieMaxAge: config.cookieMaxAge || 3600, // 1 hour default\n encryptionKey:\n config.encryptionKey ||\n process.env.AGENTSHIELD_SECRET ||\n 'agentshield-default-key',\n };\n }\n\n /**\n * Track a new AI agent session\n */\n async track(\n _request: NextRequest,\n response: NextResponse,\n result: DetectionResult\n ): Promise<NextResponse> {\n try {\n if (!this.config.enabled || !result.isAgent) {\n return response;\n }\n\n const sessionData: SessionData = {\n id: crypto.randomUUID(),\n agent: result.detectedAgent?.name || 'unknown',\n confidence: result.confidence,\n detectedAt: Date.now(),\n expires: Date.now() + this.config.cookieMaxAge * 1000,\n };\n\n // Encrypt session data for security\n const encrypted = await this.encrypt(JSON.stringify(sessionData));\n\n // Set secure httpOnly cookie\n response.cookies.set(this.config.cookieName, encrypted, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: this.config.cookieMaxAge,\n path: '/',\n });\n\n return response;\n } catch (error) {\n // Fail gracefully - log error but don't break request\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to track session:', error);\n }\n return response;\n }\n }\n\n /**\n * Check for existing AI agent session\n */\n async check(request: NextRequest): Promise<SessionData | null> {\n try {\n if (!this.config.enabled) {\n return null;\n }\n\n const cookie = request.cookies.get(this.config.cookieName);\n if (!cookie?.value) {\n return null;\n }\n\n // Decrypt and parse session data\n const decrypted = await this.decrypt(cookie.value);\n const session: SessionData = JSON.parse(decrypted);\n\n // Check if session is expired\n if (session.expires < Date.now()) {\n return null;\n }\n\n return session;\n } catch (error) {\n // Fail gracefully - invalid or corrupted session\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to check session:', error);\n }\n return null;\n }\n }\n\n /**\n * Clear an existing session\n */\n clear(response: NextResponse): NextResponse {\n try {\n response.cookies.delete(this.config.cookieName);\n } catch (error) {\n // Fail gracefully\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to clear session:', error);\n }\n }\n return response;\n }\n\n /**\n * Simple encryption using Web Crypto API (Edge-compatible)\n */\n private async encrypt(data: string): Promise<string> {\n try {\n // For Edge Runtime, use simple base64 encoding with obfuscation\n // In production, consider using Web Crypto API subtle.encrypt()\n const key = this.config.encryptionKey;\n const encoded = new TextEncoder().encode(data);\n\n // Simple XOR obfuscation\n const obfuscated = new Uint8Array(encoded.length);\n for (let i = 0; i < encoded.length; i++) {\n obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n // Convert to base64\n return btoa(\n Array.from(obfuscated, byte => String.fromCharCode(byte)).join('')\n );\n } catch (error) {\n // Fallback to simple base64 if encryption fails\n return btoa(data);\n }\n }\n\n /**\n * Simple decryption (Edge-compatible)\n */\n private async decrypt(data: string): Promise<string> {\n try {\n const key = this.config.encryptionKey;\n const decoded = Uint8Array.from(atob(data), c => c.charCodeAt(0));\n\n // Reverse XOR obfuscation\n const deobfuscated = new Uint8Array(decoded.length);\n for (let i = 0; i < decoded.length; i++) {\n deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n return new TextDecoder().decode(deobfuscated);\n } catch (error) {\n // Fallback to simple base64 if decryption fails\n return atob(data);\n }\n }\n}\n\n/**\n * Stateless session checker for non-Next.js environments (Express, etc.)\n * Uses a combination of headers to identify continued sessions\n */\nexport class StatelessSessionChecker {\n static check(headers: Record<string, string>): SessionData | null {\n try {\n // Check for session headers (set by previous response)\n const agent = headers['x-agentshield-session-agent'];\n const confidence = headers['x-agentshield-session-confidence'];\n const sessionId = headers['x-agentshield-session-id'];\n\n if (agent && confidence && sessionId) {\n return {\n id: sessionId,\n agent,\n confidence: parseFloat(confidence),\n detectedAt: Date.now(),\n expires: Date.now() + 3600000, // 1 hour\n };\n }\n\n // Check for cookie-based session (if cookies are parsed)\n const cookieHeader = headers['cookie'];\n if (cookieHeader && cookieHeader.includes('__agentshield_session=')) {\n // Simple cookie parsing\n const match = cookieHeader.match(/__agentshield_session=([^;]+)/);\n if (match && match[1]) {\n try {\n const decoded = atob(match[1]);\n return JSON.parse(decoded);\n } catch {\n // Invalid session data\n }\n }\n }\n\n return null;\n } catch {\n return null;\n }\n }\n\n static setHeaders(response: any, session: SessionData): void {\n try {\n // Set session headers for stateless tracking\n if (response.setHeader) {\n response.setHeader('X-AgentShield-Session-Agent', session.agent);\n response.setHeader(\n 'X-AgentShield-Session-Confidence',\n session.confidence.toString()\n );\n response.setHeader('X-AgentShield-Session-Id', session.id);\n } else if (response.headers && response.headers.set) {\n response.headers.set('x-agentshield-session-agent', session.agent);\n response.headers.set(\n 'x-agentshield-session-confidence',\n session.confidence.toString()\n );\n response.headers.set('x-agentshield-session-id', session.id);\n }\n } catch {\n // Fail gracefully\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// src/session-tracker.ts
|
|
2
|
+
var EdgeSessionTracker = class {
|
|
3
|
+
config;
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.config = {
|
|
6
|
+
enabled: config.enabled,
|
|
7
|
+
cookieName: config.cookieName || "__agentshield_session",
|
|
8
|
+
cookieMaxAge: config.cookieMaxAge || 3600,
|
|
9
|
+
// 1 hour default
|
|
10
|
+
encryptionKey: config.encryptionKey || process.env.AGENTSHIELD_SECRET || "agentshield-default-key"
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Track a new AI agent session
|
|
15
|
+
*/
|
|
16
|
+
async track(_request, response, result) {
|
|
17
|
+
try {
|
|
18
|
+
if (!this.config.enabled || !result.isAgent) {
|
|
19
|
+
return response;
|
|
20
|
+
}
|
|
21
|
+
const sessionData = {
|
|
22
|
+
id: crypto.randomUUID(),
|
|
23
|
+
agent: result.detectedAgent?.name || "unknown",
|
|
24
|
+
confidence: result.confidence,
|
|
25
|
+
detectedAt: Date.now(),
|
|
26
|
+
expires: Date.now() + this.config.cookieMaxAge * 1e3
|
|
27
|
+
};
|
|
28
|
+
const encrypted = await this.encrypt(JSON.stringify(sessionData));
|
|
29
|
+
response.cookies.set(this.config.cookieName, encrypted, {
|
|
30
|
+
httpOnly: true,
|
|
31
|
+
secure: process.env.NODE_ENV === "production",
|
|
32
|
+
sameSite: "lax",
|
|
33
|
+
maxAge: this.config.cookieMaxAge,
|
|
34
|
+
path: "/"
|
|
35
|
+
});
|
|
36
|
+
return response;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (process.env.DEBUG_AGENTSHIELD) {
|
|
39
|
+
console.warn("AgentShield: Failed to track session:", error);
|
|
40
|
+
}
|
|
41
|
+
return response;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check for existing AI agent session
|
|
46
|
+
*/
|
|
47
|
+
async check(request) {
|
|
48
|
+
try {
|
|
49
|
+
if (!this.config.enabled) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const cookie = request.cookies.get(this.config.cookieName);
|
|
53
|
+
if (!cookie?.value) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const decrypted = await this.decrypt(cookie.value);
|
|
57
|
+
const session = JSON.parse(decrypted);
|
|
58
|
+
if (session.expires < Date.now()) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return session;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (process.env.DEBUG_AGENTSHIELD) {
|
|
64
|
+
console.warn("AgentShield: Failed to check session:", error);
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Clear an existing session
|
|
71
|
+
*/
|
|
72
|
+
clear(response) {
|
|
73
|
+
try {
|
|
74
|
+
response.cookies.delete(this.config.cookieName);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (process.env.DEBUG_AGENTSHIELD) {
|
|
77
|
+
console.warn("AgentShield: Failed to clear session:", error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return response;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Simple encryption using Web Crypto API (Edge-compatible)
|
|
84
|
+
*/
|
|
85
|
+
async encrypt(data) {
|
|
86
|
+
try {
|
|
87
|
+
const key = this.config.encryptionKey;
|
|
88
|
+
const encoded = new TextEncoder().encode(data);
|
|
89
|
+
const obfuscated = new Uint8Array(encoded.length);
|
|
90
|
+
for (let i = 0; i < encoded.length; i++) {
|
|
91
|
+
obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
92
|
+
}
|
|
93
|
+
return btoa(
|
|
94
|
+
Array.from(obfuscated, (byte) => String.fromCharCode(byte)).join("")
|
|
95
|
+
);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return btoa(data);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Simple decryption (Edge-compatible)
|
|
102
|
+
*/
|
|
103
|
+
async decrypt(data) {
|
|
104
|
+
try {
|
|
105
|
+
const key = this.config.encryptionKey;
|
|
106
|
+
const decoded = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
|
|
107
|
+
const deobfuscated = new Uint8Array(decoded.length);
|
|
108
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
109
|
+
deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);
|
|
110
|
+
}
|
|
111
|
+
return new TextDecoder().decode(deobfuscated);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return atob(data);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var StatelessSessionChecker = class {
|
|
118
|
+
static check(headers) {
|
|
119
|
+
try {
|
|
120
|
+
const agent = headers["x-agentshield-session-agent"];
|
|
121
|
+
const confidence = headers["x-agentshield-session-confidence"];
|
|
122
|
+
const sessionId = headers["x-agentshield-session-id"];
|
|
123
|
+
if (agent && confidence && sessionId) {
|
|
124
|
+
return {
|
|
125
|
+
id: sessionId,
|
|
126
|
+
agent,
|
|
127
|
+
confidence: parseFloat(confidence),
|
|
128
|
+
detectedAt: Date.now(),
|
|
129
|
+
expires: Date.now() + 36e5
|
|
130
|
+
// 1 hour
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const cookieHeader = headers["cookie"];
|
|
134
|
+
if (cookieHeader && cookieHeader.includes("__agentshield_session=")) {
|
|
135
|
+
const match = cookieHeader.match(/__agentshield_session=([^;]+)/);
|
|
136
|
+
if (match && match[1]) {
|
|
137
|
+
try {
|
|
138
|
+
const decoded = atob(match[1]);
|
|
139
|
+
return JSON.parse(decoded);
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
static setHeaders(response, session) {
|
|
150
|
+
try {
|
|
151
|
+
if (response.setHeader) {
|
|
152
|
+
response.setHeader("X-AgentShield-Session-Agent", session.agent);
|
|
153
|
+
response.setHeader(
|
|
154
|
+
"X-AgentShield-Session-Confidence",
|
|
155
|
+
session.confidence.toString()
|
|
156
|
+
);
|
|
157
|
+
response.setHeader("X-AgentShield-Session-Id", session.id);
|
|
158
|
+
} else if (response.headers && response.headers.set) {
|
|
159
|
+
response.headers.set("x-agentshield-session-agent", session.agent);
|
|
160
|
+
response.headers.set(
|
|
161
|
+
"x-agentshield-session-confidence",
|
|
162
|
+
session.confidence.toString()
|
|
163
|
+
);
|
|
164
|
+
response.headers.set("x-agentshield-session-id", session.id);
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export { EdgeSessionTracker, StatelessSessionChecker };
|
|
172
|
+
//# sourceMappingURL=session-tracker.mjs.map
|
|
173
|
+
//# sourceMappingURL=session-tracker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/session-tracker.ts"],"names":[],"mappings":"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","file":"session-tracker.mjs","sourcesContent":["/**\n * Edge-compatible session tracking for AI agents\n * Uses cookie-based storage to work in Edge Runtime\n */\n\nimport type { NextRequest, NextResponse } from 'next/server';\nimport type { DetectionResult } from '@kya-os/agentshield';\n\nexport interface SessionData {\n id: string;\n agent: string;\n confidence: number;\n detectedAt: number;\n expires: number;\n}\n\nexport interface SessionTrackingConfig {\n enabled: boolean;\n cookieName?: string;\n cookieMaxAge?: number; // in seconds\n encryptionKey?: string;\n}\n\nexport class EdgeSessionTracker {\n private readonly config: Required<SessionTrackingConfig>;\n\n constructor(config: SessionTrackingConfig) {\n this.config = {\n enabled: config.enabled,\n cookieName: config.cookieName || '__agentshield_session',\n cookieMaxAge: config.cookieMaxAge || 3600, // 1 hour default\n encryptionKey:\n config.encryptionKey ||\n process.env.AGENTSHIELD_SECRET ||\n 'agentshield-default-key',\n };\n }\n\n /**\n * Track a new AI agent session\n */\n async track(\n _request: NextRequest,\n response: NextResponse,\n result: DetectionResult\n ): Promise<NextResponse> {\n try {\n if (!this.config.enabled || !result.isAgent) {\n return response;\n }\n\n const sessionData: SessionData = {\n id: crypto.randomUUID(),\n agent: result.detectedAgent?.name || 'unknown',\n confidence: result.confidence,\n detectedAt: Date.now(),\n expires: Date.now() + this.config.cookieMaxAge * 1000,\n };\n\n // Encrypt session data for security\n const encrypted = await this.encrypt(JSON.stringify(sessionData));\n\n // Set secure httpOnly cookie\n response.cookies.set(this.config.cookieName, encrypted, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: this.config.cookieMaxAge,\n path: '/',\n });\n\n return response;\n } catch (error) {\n // Fail gracefully - log error but don't break request\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to track session:', error);\n }\n return response;\n }\n }\n\n /**\n * Check for existing AI agent session\n */\n async check(request: NextRequest): Promise<SessionData | null> {\n try {\n if (!this.config.enabled) {\n return null;\n }\n\n const cookie = request.cookies.get(this.config.cookieName);\n if (!cookie?.value) {\n return null;\n }\n\n // Decrypt and parse session data\n const decrypted = await this.decrypt(cookie.value);\n const session: SessionData = JSON.parse(decrypted);\n\n // Check if session is expired\n if (session.expires < Date.now()) {\n return null;\n }\n\n return session;\n } catch (error) {\n // Fail gracefully - invalid or corrupted session\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to check session:', error);\n }\n return null;\n }\n }\n\n /**\n * Clear an existing session\n */\n clear(response: NextResponse): NextResponse {\n try {\n response.cookies.delete(this.config.cookieName);\n } catch (error) {\n // Fail gracefully\n if (process.env.DEBUG_AGENTSHIELD) {\n console.warn('AgentShield: Failed to clear session:', error);\n }\n }\n return response;\n }\n\n /**\n * Simple encryption using Web Crypto API (Edge-compatible)\n */\n private async encrypt(data: string): Promise<string> {\n try {\n // For Edge Runtime, use simple base64 encoding with obfuscation\n // In production, consider using Web Crypto API subtle.encrypt()\n const key = this.config.encryptionKey;\n const encoded = new TextEncoder().encode(data);\n\n // Simple XOR obfuscation\n const obfuscated = new Uint8Array(encoded.length);\n for (let i = 0; i < encoded.length; i++) {\n obfuscated[i] = (encoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n // Convert to base64\n return btoa(\n Array.from(obfuscated, byte => String.fromCharCode(byte)).join('')\n );\n } catch (error) {\n // Fallback to simple base64 if encryption fails\n return btoa(data);\n }\n }\n\n /**\n * Simple decryption (Edge-compatible)\n */\n private async decrypt(data: string): Promise<string> {\n try {\n const key = this.config.encryptionKey;\n const decoded = Uint8Array.from(atob(data), c => c.charCodeAt(0));\n\n // Reverse XOR obfuscation\n const deobfuscated = new Uint8Array(decoded.length);\n for (let i = 0; i < decoded.length; i++) {\n deobfuscated[i] = (decoded[i] || 0) ^ key.charCodeAt(i % key.length);\n }\n\n return new TextDecoder().decode(deobfuscated);\n } catch (error) {\n // Fallback to simple base64 if decryption fails\n return atob(data);\n }\n }\n}\n\n/**\n * Stateless session checker for non-Next.js environments (Express, etc.)\n * Uses a combination of headers to identify continued sessions\n */\nexport class StatelessSessionChecker {\n static check(headers: Record<string, string>): SessionData | null {\n try {\n // Check for session headers (set by previous response)\n const agent = headers['x-agentshield-session-agent'];\n const confidence = headers['x-agentshield-session-confidence'];\n const sessionId = headers['x-agentshield-session-id'];\n\n if (agent && confidence && sessionId) {\n return {\n id: sessionId,\n agent,\n confidence: parseFloat(confidence),\n detectedAt: Date.now(),\n expires: Date.now() + 3600000, // 1 hour\n };\n }\n\n // Check for cookie-based session (if cookies are parsed)\n const cookieHeader = headers['cookie'];\n if (cookieHeader && cookieHeader.includes('__agentshield_session=')) {\n // Simple cookie parsing\n const match = cookieHeader.match(/__agentshield_session=([^;]+)/);\n if (match && match[1]) {\n try {\n const decoded = atob(match[1]);\n return JSON.parse(decoded);\n } catch {\n // Invalid session data\n }\n }\n }\n\n return null;\n } catch {\n return null;\n }\n }\n\n static setHeaders(response: any, session: SessionData): void {\n try {\n // Set session headers for stateless tracking\n if (response.setHeader) {\n response.setHeader('X-AgentShield-Session-Agent', session.agent);\n response.setHeader(\n 'X-AgentShield-Session-Confidence',\n session.confidence.toString()\n );\n response.setHeader('X-AgentShield-Session-Id', session.id);\n } else if (response.headers && response.headers.set) {\n response.headers.set('x-agentshield-session-agent', session.agent);\n response.headers.set(\n 'x-agentshield-session-confidence',\n session.confidence.toString()\n );\n response.headers.set('x-agentshield-session-id', session.id);\n }\n } catch {\n // Fail gracefully\n }\n }\n}\n"]}
|
|
@@ -46,6 +46,34 @@ interface NextJSMiddlewareConfig extends Partial<AgentShieldConfig> {
|
|
|
46
46
|
* Confidence threshold for agent detection
|
|
47
47
|
*/
|
|
48
48
|
confidenceThreshold?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Enable WASM for enhanced detection
|
|
51
|
+
*/
|
|
52
|
+
enableWasm?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Session tracking configuration
|
|
55
|
+
*/
|
|
56
|
+
sessionTracking?: {
|
|
57
|
+
/**
|
|
58
|
+
* Enable session tracking
|
|
59
|
+
*/
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Cookie name for session storage
|
|
63
|
+
* Default: '__agentshield_session'
|
|
64
|
+
*/
|
|
65
|
+
cookieName?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Cookie max age in seconds
|
|
68
|
+
* Default: 3600 (1 hour)
|
|
69
|
+
*/
|
|
70
|
+
cookieMaxAge?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Encryption key for session data
|
|
73
|
+
* Default: Uses AGENTSHIELD_SECRET env var or default key
|
|
74
|
+
*/
|
|
75
|
+
encryptionKey?: string;
|
|
76
|
+
};
|
|
49
77
|
}
|
|
50
78
|
/**
|
|
51
79
|
* Detection context for hooks
|
|
@@ -46,6 +46,34 @@ interface NextJSMiddlewareConfig extends Partial<AgentShieldConfig> {
|
|
|
46
46
|
* Confidence threshold for agent detection
|
|
47
47
|
*/
|
|
48
48
|
confidenceThreshold?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Enable WASM for enhanced detection
|
|
51
|
+
*/
|
|
52
|
+
enableWasm?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Session tracking configuration
|
|
55
|
+
*/
|
|
56
|
+
sessionTracking?: {
|
|
57
|
+
/**
|
|
58
|
+
* Enable session tracking
|
|
59
|
+
*/
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Cookie name for session storage
|
|
63
|
+
* Default: '__agentshield_session'
|
|
64
|
+
*/
|
|
65
|
+
cookieName?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Cookie max age in seconds
|
|
68
|
+
* Default: 3600 (1 hour)
|
|
69
|
+
*/
|
|
70
|
+
cookieMaxAge?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Encryption key for session data
|
|
73
|
+
* Default: Uses AGENTSHIELD_SECRET env var or default key
|
|
74
|
+
*/
|
|
75
|
+
encryptionKey?: string;
|
|
76
|
+
};
|
|
49
77
|
}
|
|
50
78
|
/**
|
|
51
79
|
* Detection context for hooks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kya-os/agentshield-nextjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Next.js middleware for AgentShield AI agent detection",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -108,6 +108,6 @@
|
|
|
108
108
|
},
|
|
109
109
|
"sideEffects": false,
|
|
110
110
|
"dependencies": {
|
|
111
|
-
"@kya-os/agentshield": "^0.1.
|
|
111
|
+
"@kya-os/agentshield": "^0.1.28"
|
|
112
112
|
}
|
|
113
113
|
}
|