@wardnmesh/sdk-node 0.2.1
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/LICENSE +47 -0
- package/README.md +178 -0
- package/bin/setup.js +119 -0
- package/dist/agent-guard.d.ts +28 -0
- package/dist/agent-guard.js +206 -0
- package/dist/config/security-limits.d.ts +42 -0
- package/dist/config/security-limits.js +52 -0
- package/dist/detectors/base.d.ts +22 -0
- package/dist/detectors/base.js +51 -0
- package/dist/detectors/pattern.d.ts +7 -0
- package/dist/detectors/pattern.js +76 -0
- package/dist/detectors/semantic-detector.d.ts +16 -0
- package/dist/detectors/semantic-detector.js +96 -0
- package/dist/detectors/sequence.d.ts +11 -0
- package/dist/detectors/sequence.js +182 -0
- package/dist/detectors/state.d.ts +8 -0
- package/dist/detectors/state.js +86 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +31 -0
- package/dist/integrations/vercel.d.ts +7 -0
- package/dist/integrations/vercel.js +34 -0
- package/dist/middleware/express.d.ts +36 -0
- package/dist/middleware/express.js +54 -0
- package/dist/middleware/nextjs.d.ts +3 -0
- package/dist/middleware/nextjs.js +40 -0
- package/dist/state/session-manager.d.ts +13 -0
- package/dist/state/session-manager.js +22 -0
- package/dist/telemetry/reporter.d.ts +32 -0
- package/dist/telemetry/reporter.js +86 -0
- package/dist/types.d.ts +206 -0
- package/dist/types.js +14 -0
- package/dist/update-checker.d.ts +21 -0
- package/dist/update-checker.js +218 -0
- package/dist/utils/logger.d.ts +40 -0
- package/dist/utils/logger.js +79 -0
- package/dist/utils/rule-validator.d.ts +15 -0
- package/dist/utils/rule-validator.js +143 -0
- package/dist/utils/safe-regex.d.ts +53 -0
- package/dist/utils/safe-regex.js +220 -0
- package/dist/wardn.d.ts +67 -0
- package/dist/wardn.js +443 -0
- package/package.json +47 -0
package/dist/wardn.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Wardn = void 0;
|
|
4
|
+
const pattern_1 = require("./detectors/pattern");
|
|
5
|
+
const sequence_1 = require("./detectors/sequence");
|
|
6
|
+
const state_1 = require("./detectors/state");
|
|
7
|
+
const safe_regex_1 = require("./utils/safe-regex");
|
|
8
|
+
const update_checker_1 = require("./update-checker");
|
|
9
|
+
const session_manager_1 = require("./state/session-manager");
|
|
10
|
+
const reporter_1 = require("./telemetry/reporter");
|
|
11
|
+
const semantic_detector_1 = require("./detectors/semantic-detector");
|
|
12
|
+
const rule_validator_1 = require("./utils/rule-validator");
|
|
13
|
+
const logger_1 = require("./utils/logger");
|
|
14
|
+
const security_limits_1 = require("./config/security-limits");
|
|
15
|
+
/** Synchronous state adapter for in-request state management */
|
|
16
|
+
class SyncStateAdapter {
|
|
17
|
+
constructor(initialState, maxHistory = 50) {
|
|
18
|
+
this.maxHistory = maxHistory;
|
|
19
|
+
this.state = {
|
|
20
|
+
startTime: initialState.startTime || new Date().toISOString(),
|
|
21
|
+
toolCalls: initialState.toolCalls || [],
|
|
22
|
+
recentTools: initialState.recentTools || [],
|
|
23
|
+
detectedViolations: initialState.detectedViolations || [],
|
|
24
|
+
customState: initialState.customState || {},
|
|
25
|
+
currentFile: initialState.currentFile,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
getRecentTools(count) {
|
|
29
|
+
const tools = this.state.recentTools || [];
|
|
30
|
+
return count ? tools.slice(-count) : tools;
|
|
31
|
+
}
|
|
32
|
+
setCustomState(key, value) {
|
|
33
|
+
this.state.customState[key] = value;
|
|
34
|
+
}
|
|
35
|
+
getCustomState(key) {
|
|
36
|
+
return this.state.customState[key];
|
|
37
|
+
}
|
|
38
|
+
addToolCall(tool) {
|
|
39
|
+
if (!this.state.recentTools)
|
|
40
|
+
this.state.recentTools = [];
|
|
41
|
+
this.state.recentTools.push(tool);
|
|
42
|
+
if (this.state.recentTools.length > this.maxHistory) {
|
|
43
|
+
this.state.recentTools.shift();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exportState() {
|
|
47
|
+
return this.state;
|
|
48
|
+
}
|
|
49
|
+
addViolation(violation) {
|
|
50
|
+
if (!this.state.detectedViolations)
|
|
51
|
+
this.state.detectedViolations = [];
|
|
52
|
+
this.state.detectedViolations.push(violation);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
class Wardn {
|
|
56
|
+
constructor(config, stateProvider) {
|
|
57
|
+
this.pollTimer = null;
|
|
58
|
+
this.isShutdown = false;
|
|
59
|
+
// Rate limiting for rule updates
|
|
60
|
+
this.lastRuleUpdate = 0;
|
|
61
|
+
this.updateCount = 0;
|
|
62
|
+
this.updateCountResetTime = Date.now();
|
|
63
|
+
this.config = config;
|
|
64
|
+
this.stateProvider = stateProvider || new session_manager_1.InMemorySessionStateProvider();
|
|
65
|
+
this.telemetry = this.initTelemetry();
|
|
66
|
+
this.patternDetector = new pattern_1.PatternDetector();
|
|
67
|
+
this.sequenceDetector = new sequence_1.SequenceDetector();
|
|
68
|
+
this.stateDetector = new state_1.StateDetector();
|
|
69
|
+
this.semanticDetector = semantic_detector_1.SemanticDetector.getInstance();
|
|
70
|
+
// Emit startup event
|
|
71
|
+
this.telemetry.emit({
|
|
72
|
+
eventType: "agent_started",
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
data: {
|
|
75
|
+
config: {
|
|
76
|
+
appName: this.config.telemetry?.serviceName,
|
|
77
|
+
ruleCount: this.config.rules.length,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
// Start polling if configured (with URL validation)
|
|
82
|
+
if (this.config.remoteRulesUrl) {
|
|
83
|
+
const urlValidation = (0, rule_validator_1.validateRemoteUrl)(this.config.remoteRulesUrl);
|
|
84
|
+
if (urlValidation.valid) {
|
|
85
|
+
this.pollRemoteRules();
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
logger_1.logger.error("Invalid remote rules URL:", urlValidation.errors.join(", "));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
initTelemetry() {
|
|
93
|
+
if (this.config.telemetry?.enabled && process.env.WARDN_API_URL) {
|
|
94
|
+
const apiKey = this.config.apiKey;
|
|
95
|
+
return new reporter_1.WardnReporter(process.env.WARDN_API_URL, apiKey || "", this.config.telemetry.serviceName || "unknown-service");
|
|
96
|
+
}
|
|
97
|
+
return new reporter_1.ConsoleReporter();
|
|
98
|
+
}
|
|
99
|
+
static getInstance() {
|
|
100
|
+
if (!Wardn.instance) {
|
|
101
|
+
throw new Error("Wardn not initialized. Call Wardn.init() first.");
|
|
102
|
+
}
|
|
103
|
+
return Wardn.instance;
|
|
104
|
+
}
|
|
105
|
+
static init(config, stateProvider) {
|
|
106
|
+
const apiKey = config.apiKey || process.env.WARDN_API_KEY;
|
|
107
|
+
if (!apiKey) {
|
|
108
|
+
const errorMsg = "\n\n" +
|
|
109
|
+
"[Wardn] ACTIVE DEFENSE NOT INITIALIZED\n" +
|
|
110
|
+
"===================================================\n" +
|
|
111
|
+
"The immune system requires an identity to function.\n" +
|
|
112
|
+
"\n" +
|
|
113
|
+
"1. Get your API Key: https://wardnmesh.ai (Free for Devs)\n" +
|
|
114
|
+
"2. Set Variable: WARDNMESH_API_KEY=wk_...\n" +
|
|
115
|
+
"\n" +
|
|
116
|
+
"Your agent is currently UNPROTECTED from prompt injection.\n" +
|
|
117
|
+
"===================================================\n";
|
|
118
|
+
logger_1.logger.error(errorMsg);
|
|
119
|
+
throw new Error("[Wardn] Missing API Key. Registration required.");
|
|
120
|
+
}
|
|
121
|
+
// Ensure config has the key
|
|
122
|
+
const finalConfig = { ...config, apiKey };
|
|
123
|
+
Wardn.instance = new Wardn(finalConfig, stateProvider);
|
|
124
|
+
// Check for updates (non-blocking, don't await)
|
|
125
|
+
// Skip in test environment to avoid async warnings
|
|
126
|
+
if (security_limits_1.FEATURE_FLAGS.ENABLE_UPDATE_CHECK && process.env.NODE_ENV !== "test") {
|
|
127
|
+
(0, update_checker_1.notifyIfUpdateAvailable)().catch(() => {
|
|
128
|
+
// Silently fail - don't disrupt initialization
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return Wardn.instance;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Reset the singleton instance (useful for testing)
|
|
135
|
+
*/
|
|
136
|
+
static reset() {
|
|
137
|
+
if (Wardn.instance) {
|
|
138
|
+
Wardn.instance.shutdown();
|
|
139
|
+
Wardn.instance = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Shutdown the Wardn instance, cleaning up resources
|
|
144
|
+
*/
|
|
145
|
+
shutdown() {
|
|
146
|
+
if (this.isShutdown)
|
|
147
|
+
return;
|
|
148
|
+
this.isShutdown = true;
|
|
149
|
+
// Clear polling timer
|
|
150
|
+
if (this.pollTimer) {
|
|
151
|
+
clearTimeout(this.pollTimer);
|
|
152
|
+
this.pollTimer = null;
|
|
153
|
+
}
|
|
154
|
+
// Emit shutdown event with pattern error metrics
|
|
155
|
+
const patternMetrics = (0, safe_regex_1.getPatternErrorMetrics)();
|
|
156
|
+
this.telemetry.emit({
|
|
157
|
+
eventType: "agent_shutdown",
|
|
158
|
+
timestamp: new Date().toISOString(),
|
|
159
|
+
data: {
|
|
160
|
+
uptime: Date.now() - new Date(this.stateProvider ? Date.now() : 0).getTime(),
|
|
161
|
+
patternErrorMetrics: patternMetrics,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
// Stop telemetry auto-flush timer
|
|
165
|
+
if (this.telemetry.stop) {
|
|
166
|
+
this.telemetry.stop();
|
|
167
|
+
}
|
|
168
|
+
logger_1.logger.info("Shutdown complete");
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Scan a request for security violations
|
|
172
|
+
* @param request The agent request context (prompt, tools, etc.)
|
|
173
|
+
* @returns ScanResult with allowed/blocked status and violations
|
|
174
|
+
*/
|
|
175
|
+
async scan(request) {
|
|
176
|
+
if (this.isShutdown) {
|
|
177
|
+
return {
|
|
178
|
+
allowed: true,
|
|
179
|
+
violations: [],
|
|
180
|
+
latencyMs: 0,
|
|
181
|
+
metadata: { error: true, errorDetails: "Wardn instance is shutdown" },
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const startTime = Date.now();
|
|
185
|
+
const failMode = this.config.failMode || "open";
|
|
186
|
+
try {
|
|
187
|
+
const { toolName = "llm_input", parameters = {}, sessionId, } = request;
|
|
188
|
+
const currentSessionId = sessionId || "default";
|
|
189
|
+
const rawState = await this.stateProvider.getState(currentSessionId);
|
|
190
|
+
const stateAdapter = new SyncStateAdapter(rawState, this.config.maxHistorySize);
|
|
191
|
+
const toolData = this.normalizeRequest(request);
|
|
192
|
+
stateAdapter.addToolCall(toolData);
|
|
193
|
+
// Lazy load semantic model if needed
|
|
194
|
+
if (this.config.rules.some((r) => r.detector.type === "semantic")) {
|
|
195
|
+
await this.semanticDetector.init();
|
|
196
|
+
}
|
|
197
|
+
const violations = [];
|
|
198
|
+
// Run all detectors
|
|
199
|
+
for (const rule of this.config.rules) {
|
|
200
|
+
const violation = await this.detectViolation(rule, toolData, stateAdapter);
|
|
201
|
+
if (violation) {
|
|
202
|
+
violations.push(violation);
|
|
203
|
+
stateAdapter.addViolation(violation);
|
|
204
|
+
this.reportViolation(violation, toolData, currentSessionId);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
await this.stateProvider.setState(currentSessionId, stateAdapter.exportState());
|
|
208
|
+
const result = {
|
|
209
|
+
allowed: !violations.some((v) => v.severity === "critical"),
|
|
210
|
+
violations,
|
|
211
|
+
latencyMs: Date.now() - startTime,
|
|
212
|
+
metadata: {
|
|
213
|
+
analyzedRules: this.config.rules.length,
|
|
214
|
+
sessionId: currentSessionId,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
this.reportScanComplete(result, currentSessionId);
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
222
|
+
logger_1.logger.error(`Scan failed, failing ${failMode}:`, error);
|
|
223
|
+
this.telemetry.emit({
|
|
224
|
+
eventType: "error",
|
|
225
|
+
timestamp: new Date().toISOString(),
|
|
226
|
+
data: {
|
|
227
|
+
error: errorMessage,
|
|
228
|
+
sessionId: request.sessionId || "unknown",
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
// Configurable fail mode
|
|
232
|
+
const allowed = failMode === "open";
|
|
233
|
+
return {
|
|
234
|
+
allowed,
|
|
235
|
+
violations: [],
|
|
236
|
+
latencyMs: Date.now() - startTime,
|
|
237
|
+
metadata: {
|
|
238
|
+
error: true,
|
|
239
|
+
errorDetails: errorMessage,
|
|
240
|
+
failMode,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/** Normalize request to ToolData format */
|
|
246
|
+
normalizeRequest(request) {
|
|
247
|
+
return {
|
|
248
|
+
toolName: request.toolName || "llm_input",
|
|
249
|
+
parameters: request.parameters || { prompt: request.prompt || "" },
|
|
250
|
+
result: { success: true },
|
|
251
|
+
duration: 0,
|
|
252
|
+
timestamp: new Date().toISOString(),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/** Run detector for a single rule */
|
|
256
|
+
async detectViolation(rule, toolData, stateAdapter) {
|
|
257
|
+
switch (rule.detector.type) {
|
|
258
|
+
case "pattern":
|
|
259
|
+
return this.patternDetector.detect(toolData, rule, stateAdapter);
|
|
260
|
+
case "sequence":
|
|
261
|
+
return this.sequenceDetector.detect(toolData, rule, stateAdapter);
|
|
262
|
+
case "state":
|
|
263
|
+
return this.stateDetector.detect(toolData, rule, stateAdapter);
|
|
264
|
+
case "semantic":
|
|
265
|
+
return this.detectSemanticViolation(rule, toolData);
|
|
266
|
+
default:
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Handle semantic detection */
|
|
271
|
+
async detectSemanticViolation(rule, toolData) {
|
|
272
|
+
const prompt = toolData.parameters.prompt ||
|
|
273
|
+
JSON.stringify(toolData.parameters);
|
|
274
|
+
const config = rule.detector.config;
|
|
275
|
+
const { detected, reason, score } = await this.semanticDetector.scan(prompt, config.threshold || 0.75);
|
|
276
|
+
if (!detected)
|
|
277
|
+
return null;
|
|
278
|
+
return {
|
|
279
|
+
id: crypto.randomUUID(),
|
|
280
|
+
ruleId: rule.id,
|
|
281
|
+
ruleName: rule.name,
|
|
282
|
+
severity: rule.severity,
|
|
283
|
+
description: reason || "Semantic Violation",
|
|
284
|
+
context: {
|
|
285
|
+
toolName: toolData.toolName,
|
|
286
|
+
toolData,
|
|
287
|
+
additionalInfo: { score },
|
|
288
|
+
},
|
|
289
|
+
timestamp: new Date().toISOString(),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/** Report violation to telemetry */
|
|
293
|
+
reportViolation(violation, toolData, sessionId) {
|
|
294
|
+
// REDACTION: Create a safe copy for the cloud
|
|
295
|
+
const safeViolation = { ...violation };
|
|
296
|
+
if (safeViolation.context) {
|
|
297
|
+
// Remove raw toolData (prompts) from the cloud payload
|
|
298
|
+
const { toolData: _removed, ...safeContext } = safeViolation.context;
|
|
299
|
+
safeViolation.context = safeContext;
|
|
300
|
+
}
|
|
301
|
+
this.telemetry.emit({
|
|
302
|
+
eventType: "violation_detected",
|
|
303
|
+
timestamp: new Date().toISOString(),
|
|
304
|
+
data: { violation: safeViolation },
|
|
305
|
+
metadata: { sessionId },
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/** Report scan completion to telemetry */
|
|
309
|
+
reportScanComplete(result, sessionId) {
|
|
310
|
+
this.telemetry.emit({
|
|
311
|
+
eventType: "scan_complete",
|
|
312
|
+
timestamp: new Date().toISOString(),
|
|
313
|
+
data: {
|
|
314
|
+
allowed: result.allowed,
|
|
315
|
+
latencyMs: result.latencyMs,
|
|
316
|
+
violationCount: result.violations.length,
|
|
317
|
+
},
|
|
318
|
+
metadata: { sessionId },
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Update rules dynamically
|
|
323
|
+
* This allows external systems (like MCP server with Supabase threat rules)
|
|
324
|
+
* to update the rule set without restarting
|
|
325
|
+
*
|
|
326
|
+
* @param rules New rule set to use
|
|
327
|
+
* @returns void
|
|
328
|
+
*/
|
|
329
|
+
updateRules(rules) {
|
|
330
|
+
if (this.isShutdown) {
|
|
331
|
+
logger_1.logger.warn("Cannot update rules: Wardn instance is shutdown");
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// Rate limiting: Prevent update spam
|
|
335
|
+
const now = Date.now();
|
|
336
|
+
const timeSinceLastUpdate = now - this.lastRuleUpdate;
|
|
337
|
+
if (timeSinceLastUpdate < security_limits_1.SECURITY_LIMITS.MIN_UPDATE_INTERVAL_MS) {
|
|
338
|
+
throw new Error(`Rate limit: Cannot update rules more than once per ${security_limits_1.SECURITY_LIMITS.MIN_UPDATE_INTERVAL_MS}ms. ` +
|
|
339
|
+
`Last update was ${timeSinceLastUpdate}ms ago.`);
|
|
340
|
+
}
|
|
341
|
+
// Reset update counter every minute
|
|
342
|
+
if (now - this.updateCountResetTime > 60000) {
|
|
343
|
+
this.updateCount = 0;
|
|
344
|
+
this.updateCountResetTime = now;
|
|
345
|
+
}
|
|
346
|
+
if (this.updateCount >= security_limits_1.SECURITY_LIMITS.MAX_UPDATES_PER_MINUTE) {
|
|
347
|
+
throw new Error(`Rate limit: Maximum ${security_limits_1.SECURITY_LIMITS.MAX_UPDATES_PER_MINUTE} updates per minute exceeded.`);
|
|
348
|
+
}
|
|
349
|
+
// Validate rule count before validation (fail fast)
|
|
350
|
+
if (rules.length > security_limits_1.SECURITY_LIMITS.MAX_RULES) {
|
|
351
|
+
throw new Error(`Too many rules: ${rules.length}. Maximum allowed: ${security_limits_1.SECURITY_LIMITS.MAX_RULES}`);
|
|
352
|
+
}
|
|
353
|
+
// Validate rules before applying
|
|
354
|
+
const validation = (0, rule_validator_1.validateRules)(rules);
|
|
355
|
+
if (!validation.valid) {
|
|
356
|
+
logger_1.logger.error("Invalid rules provided to updateRules():", validation.errors.slice(0, 5).join("; "));
|
|
357
|
+
throw new Error(`Invalid rules: ${validation.errors[0]}`);
|
|
358
|
+
}
|
|
359
|
+
const oldCount = this.config.rules.length;
|
|
360
|
+
// Atomic update: Create defensive copy then assign atomically
|
|
361
|
+
// This prevents race conditions where scan() might iterate during update
|
|
362
|
+
const newRules = [...rules]; // Defensive copy
|
|
363
|
+
this.config.rules = newRules; // Atomic assignment (V8 guarantees atomicity)
|
|
364
|
+
logger_1.logger.info(`Rules updated: ${oldCount} → ${this.config.rules.length} rules`);
|
|
365
|
+
// Emit telemetry event for rule update
|
|
366
|
+
this.telemetry.emit({
|
|
367
|
+
eventType: "rules_updated",
|
|
368
|
+
timestamp: new Date().toISOString(),
|
|
369
|
+
data: {
|
|
370
|
+
oldCount,
|
|
371
|
+
newCount: this.config.rules.length,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
// Update rate limiting tracking
|
|
375
|
+
this.lastRuleUpdate = now;
|
|
376
|
+
this.updateCount++;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Get current rules
|
|
380
|
+
* @returns Current rule set
|
|
381
|
+
*/
|
|
382
|
+
getRules() {
|
|
383
|
+
return [...this.config.rules];
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Get pattern error metrics for monitoring
|
|
387
|
+
* @returns Pattern error statistics
|
|
388
|
+
*/
|
|
389
|
+
getPatternErrorMetrics() {
|
|
390
|
+
return (0, safe_regex_1.getPatternErrorMetrics)();
|
|
391
|
+
}
|
|
392
|
+
/** Poll remote rules for updates with validation */
|
|
393
|
+
async pollRemoteRules() {
|
|
394
|
+
if (!this.config.remoteRulesUrl || this.isShutdown)
|
|
395
|
+
return;
|
|
396
|
+
const FETCH_TIMEOUT_MS = 10000; // 10 second timeout
|
|
397
|
+
const poll = async () => {
|
|
398
|
+
if (this.isShutdown)
|
|
399
|
+
return;
|
|
400
|
+
try {
|
|
401
|
+
// Create AbortController for timeout
|
|
402
|
+
const controller = new AbortController();
|
|
403
|
+
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
404
|
+
const response = await fetch(this.config.remoteRulesUrl, {
|
|
405
|
+
headers: {
|
|
406
|
+
"Accept": "application/json",
|
|
407
|
+
"X-Wardn-Client": "sdk-node",
|
|
408
|
+
},
|
|
409
|
+
signal: controller.signal,
|
|
410
|
+
});
|
|
411
|
+
clearTimeout(timeoutId);
|
|
412
|
+
if (response.ok) {
|
|
413
|
+
const remoteRules = await response.json();
|
|
414
|
+
// Validate rules before applying
|
|
415
|
+
const validation = (0, rule_validator_1.validateRules)(remoteRules);
|
|
416
|
+
if (validation.valid) {
|
|
417
|
+
this.config.rules = remoteRules;
|
|
418
|
+
logger_1.logger.info(`Rules synced from remote: ${this.config.rules.length} rules`);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
logger_1.logger.error("Invalid remote rules:", validation.errors.slice(0, 5).join("; "));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
logger_1.logger.warn("Failed to fetch rules:", response.statusText);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
logger_1.logger.error("Rule polling error:", error);
|
|
430
|
+
}
|
|
431
|
+
finally {
|
|
432
|
+
// Schedule next poll if not shutdown
|
|
433
|
+
if (!this.isShutdown) {
|
|
434
|
+
this.pollTimer = setTimeout(poll, this.config.pollingIntervalMs || 60000);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
// Initial call
|
|
439
|
+
poll();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
exports.Wardn = Wardn;
|
|
443
|
+
Wardn.instance = null;
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wardnmesh/sdk-node",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "WardnMesh.AI Node.js SDK - Active Defense Middleware for AI Agents",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "jest"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"wardn-init": "./bin/setup.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"bin",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ai-security",
|
|
22
|
+
"prompt-injection",
|
|
23
|
+
"middleware",
|
|
24
|
+
"agent-guard"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/PCIRCLE-AI/wardnmesh.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/PCIRCLE-AI/wardnmesh/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://wardnmesh.ai",
|
|
34
|
+
"author": "KT",
|
|
35
|
+
"license": "Elastic-2.0",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/jest": "^29.5.11",
|
|
38
|
+
"@types/node": "^20.10.0",
|
|
39
|
+
"jest": "^29.7.0",
|
|
40
|
+
"next": "^16.1.1",
|
|
41
|
+
"ts-jest": "^29.1.1",
|
|
42
|
+
"typescript": "^5.0.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@xenova/transformers": "^2.17.2"
|
|
46
|
+
}
|
|
47
|
+
}
|