@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.
Files changed (42) hide show
  1. package/LICENSE +47 -0
  2. package/README.md +178 -0
  3. package/bin/setup.js +119 -0
  4. package/dist/agent-guard.d.ts +28 -0
  5. package/dist/agent-guard.js +206 -0
  6. package/dist/config/security-limits.d.ts +42 -0
  7. package/dist/config/security-limits.js +52 -0
  8. package/dist/detectors/base.d.ts +22 -0
  9. package/dist/detectors/base.js +51 -0
  10. package/dist/detectors/pattern.d.ts +7 -0
  11. package/dist/detectors/pattern.js +76 -0
  12. package/dist/detectors/semantic-detector.d.ts +16 -0
  13. package/dist/detectors/semantic-detector.js +96 -0
  14. package/dist/detectors/sequence.d.ts +11 -0
  15. package/dist/detectors/sequence.js +182 -0
  16. package/dist/detectors/state.d.ts +8 -0
  17. package/dist/detectors/state.js +86 -0
  18. package/dist/index.d.ts +15 -0
  19. package/dist/index.js +31 -0
  20. package/dist/integrations/vercel.d.ts +7 -0
  21. package/dist/integrations/vercel.js +34 -0
  22. package/dist/middleware/express.d.ts +36 -0
  23. package/dist/middleware/express.js +54 -0
  24. package/dist/middleware/nextjs.d.ts +3 -0
  25. package/dist/middleware/nextjs.js +40 -0
  26. package/dist/state/session-manager.d.ts +13 -0
  27. package/dist/state/session-manager.js +22 -0
  28. package/dist/telemetry/reporter.d.ts +32 -0
  29. package/dist/telemetry/reporter.js +86 -0
  30. package/dist/types.d.ts +206 -0
  31. package/dist/types.js +14 -0
  32. package/dist/update-checker.d.ts +21 -0
  33. package/dist/update-checker.js +218 -0
  34. package/dist/utils/logger.d.ts +40 -0
  35. package/dist/utils/logger.js +79 -0
  36. package/dist/utils/rule-validator.d.ts +15 -0
  37. package/dist/utils/rule-validator.js +143 -0
  38. package/dist/utils/safe-regex.d.ts +53 -0
  39. package/dist/utils/safe-regex.js +220 -0
  40. package/dist/wardn.d.ts +67 -0
  41. package/dist/wardn.js +443 -0
  42. package/package.json +47 -0
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ /**
3
+ * Safe Regex Utilities - ReDoS Prevention
4
+ *
5
+ * Provides regex validation and safe execution with timeout protection.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.convertPCREModifiers = convertPCREModifiers;
9
+ exports.validateRegexPattern = validateRegexPattern;
10
+ exports.getPatternErrorMetrics = getPatternErrorMetrics;
11
+ exports.resetPatternErrorMetrics = resetPatternErrorMetrics;
12
+ exports.getCachedRegex = getCachedRegex;
13
+ exports.safeRegexExec = safeRegexExec;
14
+ exports.safeRegexTest = safeRegexTest;
15
+ const logger_1 = require("./logger");
16
+ // Patterns that are known to cause catastrophic backtracking
17
+ const DANGEROUS_PATTERNS = [
18
+ /\(\?[^)]*\+[^)]*\)\+/, // Nested quantifiers with +
19
+ /\(\?[^)]*\*[^)]*\)\+/, // Nested quantifiers with *
20
+ /\(\?[^)]*\+[^)]*\)\*/, // Nested quantifiers
21
+ /\(\?[^)]*\*[^)]*\)\*/, // Nested quantifiers
22
+ /\([^)]+\)\{[0-9]+,\}/, // Unbounded repetition of groups
23
+ /\.\*\.\*/, // Multiple greedy wildcards
24
+ /\.\+\.\+/, // Multiple greedy wildcards
25
+ /\([^)]*\|[^)]*\)\+/, // Alternation with quantifier
26
+ /\([^)]*\|[^)]*\)\*/, // Alternation with quantifier
27
+ ];
28
+ // Maximum allowed regex pattern length
29
+ const MAX_PATTERN_LENGTH = 1000;
30
+ // Maximum allowed quantifier value
31
+ const MAX_QUANTIFIER = 100;
32
+ /**
33
+ * Convert PCRE inline modifiers to JavaScript flags
34
+ * Handles: (?i), (?m), (?s), (?x), and combinations like (?im)
35
+ */
36
+ function convertPCREModifiers(pattern) {
37
+ let convertedPattern = pattern;
38
+ let flags = "";
39
+ let converted = false;
40
+ // Match PCRE inline modifiers: (?i), (?m), (?s), (?im), etc.
41
+ const pcreModifierRegex = /\(\?([imsx]+)\)/g;
42
+ const matches = [...pattern.matchAll(pcreModifierRegex)];
43
+ if (matches.length === 0) {
44
+ return { pattern, flags: "", converted: false };
45
+ }
46
+ const unsupportedModifiers = [];
47
+ const flagSet = new Set();
48
+ for (const match of matches) {
49
+ const modifiers = match[1];
50
+ for (const modifier of modifiers) {
51
+ switch (modifier) {
52
+ case "i": // Case insensitive - supported
53
+ flagSet.add("i");
54
+ break;
55
+ case "m": // Multiline - supported
56
+ flagSet.add("m");
57
+ break;
58
+ case "s": // Dotall - supported in ES2018+
59
+ flagSet.add("s");
60
+ break;
61
+ case "x": // Extended/verbose - NOT supported in JavaScript
62
+ unsupportedModifiers.push("x (extended/verbose)");
63
+ break;
64
+ default:
65
+ unsupportedModifiers.push(modifier);
66
+ }
67
+ }
68
+ // Remove the PCRE modifier from pattern
69
+ convertedPattern = convertedPattern.replace(match[0], "");
70
+ converted = true;
71
+ }
72
+ if (unsupportedModifiers.length > 0) {
73
+ return {
74
+ pattern: convertedPattern,
75
+ flags: "",
76
+ converted: false,
77
+ error: `Unsupported PCRE modifiers: ${unsupportedModifiers.join(", ")}`,
78
+ };
79
+ }
80
+ flags = Array.from(flagSet).join("");
81
+ return {
82
+ pattern: convertedPattern,
83
+ flags,
84
+ converted: true,
85
+ };
86
+ }
87
+ /**
88
+ * Validate a regex pattern for potential ReDoS vulnerabilities
89
+ */
90
+ function validateRegexPattern(pattern) {
91
+ // Check pattern length
92
+ if (pattern.length > MAX_PATTERN_LENGTH) {
93
+ return {
94
+ valid: false,
95
+ error: `Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`,
96
+ };
97
+ }
98
+ // Check for dangerous patterns
99
+ for (const dangerous of DANGEROUS_PATTERNS) {
100
+ if (dangerous.test(pattern)) {
101
+ return {
102
+ valid: false,
103
+ error: "Pattern contains potentially dangerous nested quantifiers",
104
+ };
105
+ }
106
+ }
107
+ // Check for large quantifiers like {1000,}
108
+ const quantifierMatch = pattern.match(/\{(\d+)(,(\d*))?\}/g);
109
+ if (quantifierMatch) {
110
+ for (const q of quantifierMatch) {
111
+ const nums = q.match(/\d+/g);
112
+ if (nums) {
113
+ for (const num of nums) {
114
+ if (parseInt(num, 10) > MAX_QUANTIFIER) {
115
+ return {
116
+ valid: false,
117
+ error: `Quantifier value ${num} exceeds maximum of ${MAX_QUANTIFIER}`,
118
+ };
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ // Try to compile the regex to catch syntax errors
125
+ try {
126
+ new RegExp(pattern);
127
+ }
128
+ catch (e) {
129
+ return {
130
+ valid: false,
131
+ error: `Invalid regex syntax: ${e instanceof Error ? e.message : "Unknown error"}`,
132
+ };
133
+ }
134
+ return { valid: true };
135
+ }
136
+ /**
137
+ * Cached regex instances to avoid recompilation
138
+ */
139
+ const regexCache = new Map();
140
+ const MAX_CACHE_SIZE = 100;
141
+ const errorMetrics = {
142
+ pcreConversionFailures: 0,
143
+ validationFailures: 0,
144
+ regexCompilationFailures: 0,
145
+ totalPatternsProcessed: 0,
146
+ };
147
+ /**
148
+ * Get current pattern error metrics
149
+ */
150
+ function getPatternErrorMetrics() {
151
+ return { ...errorMetrics };
152
+ }
153
+ /**
154
+ * Reset pattern error metrics (useful for testing)
155
+ */
156
+ function resetPatternErrorMetrics() {
157
+ errorMetrics.pcreConversionFailures = 0;
158
+ errorMetrics.validationFailures = 0;
159
+ errorMetrics.regexCompilationFailures = 0;
160
+ errorMetrics.totalPatternsProcessed = 0;
161
+ }
162
+ /**
163
+ * Get a cached regex instance or create a new one
164
+ */
165
+ function getCachedRegex(pattern, flags = "") {
166
+ errorMetrics.totalPatternsProcessed++;
167
+ // Convert PCRE modifiers first
168
+ const pcreResult = convertPCREModifiers(pattern);
169
+ if (pcreResult.converted && pcreResult.error) {
170
+ // Unsupported PCRE modifiers - skip this pattern
171
+ errorMetrics.pcreConversionFailures++;
172
+ logger_1.logger.warn(`Skipping pattern with unsupported PCRE modifiers: ${pcreResult.error}`);
173
+ return null;
174
+ }
175
+ // Use converted pattern and merge flags
176
+ const finalPattern = pcreResult.pattern;
177
+ const finalFlags = pcreResult.converted
178
+ ? pcreResult.flags + flags // PCRE flags + explicit flags
179
+ : flags;
180
+ const cacheKey = `${finalPattern}:${finalFlags}`;
181
+ if (regexCache.has(cacheKey)) {
182
+ return regexCache.get(cacheKey);
183
+ }
184
+ const validation = validateRegexPattern(finalPattern);
185
+ if (!validation.valid) {
186
+ errorMetrics.validationFailures++;
187
+ logger_1.logger.warn(`Unsafe regex pattern rejected: ${validation.error}`);
188
+ return null;
189
+ }
190
+ try {
191
+ const regex = new RegExp(finalPattern, finalFlags);
192
+ // Evict oldest entries if cache is full
193
+ if (regexCache.size >= MAX_CACHE_SIZE) {
194
+ const firstKey = regexCache.keys().next().value;
195
+ if (firstKey)
196
+ regexCache.delete(firstKey);
197
+ }
198
+ regexCache.set(cacheKey, regex);
199
+ return regex;
200
+ }
201
+ catch {
202
+ errorMetrics.regexCompilationFailures++;
203
+ return null;
204
+ }
205
+ }
206
+ /**
207
+ * Execute regex with content length limit
208
+ */
209
+ function safeRegexExec(regex, content, maxLength = 50000) {
210
+ // Truncate content if too long
211
+ const safeContent = content.length > maxLength ? content.substring(0, maxLength) : content;
212
+ return regex.exec(safeContent);
213
+ }
214
+ /**
215
+ * Safe regex test with content length limit
216
+ */
217
+ function safeRegexTest(regex, content, maxLength = 50000) {
218
+ const safeContent = content.length > maxLength ? content.substring(0, maxLength) : content;
219
+ return regex.test(safeContent);
220
+ }
@@ -0,0 +1,67 @@
1
+ import { WardnConfig, ScanResult, WardnRequest, Rule } from "./types";
2
+ import { PatternErrorMetrics } from "./utils/safe-regex";
3
+ import { SessionStateProvider } from "./state/session-manager";
4
+ export declare class Wardn {
5
+ private static instance;
6
+ private config;
7
+ private stateProvider;
8
+ private telemetry;
9
+ private patternDetector;
10
+ private sequenceDetector;
11
+ private stateDetector;
12
+ private semanticDetector;
13
+ private pollTimer;
14
+ private isShutdown;
15
+ private lastRuleUpdate;
16
+ private updateCount;
17
+ private updateCountResetTime;
18
+ private constructor();
19
+ private initTelemetry;
20
+ static getInstance(): Wardn;
21
+ static init(config: WardnConfig, stateProvider?: SessionStateProvider): Wardn;
22
+ /**
23
+ * Reset the singleton instance (useful for testing)
24
+ */
25
+ static reset(): void;
26
+ /**
27
+ * Shutdown the Wardn instance, cleaning up resources
28
+ */
29
+ shutdown(): void;
30
+ /**
31
+ * Scan a request for security violations
32
+ * @param request The agent request context (prompt, tools, etc.)
33
+ * @returns ScanResult with allowed/blocked status and violations
34
+ */
35
+ scan(request: WardnRequest): Promise<ScanResult>;
36
+ /** Normalize request to ToolData format */
37
+ private normalizeRequest;
38
+ /** Run detector for a single rule */
39
+ private detectViolation;
40
+ /** Handle semantic detection */
41
+ private detectSemanticViolation;
42
+ /** Report violation to telemetry */
43
+ private reportViolation;
44
+ /** Report scan completion to telemetry */
45
+ private reportScanComplete;
46
+ /**
47
+ * Update rules dynamically
48
+ * This allows external systems (like MCP server with Supabase threat rules)
49
+ * to update the rule set without restarting
50
+ *
51
+ * @param rules New rule set to use
52
+ * @returns void
53
+ */
54
+ updateRules(rules: Rule[]): void;
55
+ /**
56
+ * Get current rules
57
+ * @returns Current rule set
58
+ */
59
+ getRules(): readonly Rule[];
60
+ /**
61
+ * Get pattern error metrics for monitoring
62
+ * @returns Pattern error statistics
63
+ */
64
+ getPatternErrorMetrics(): Readonly<PatternErrorMetrics>;
65
+ /** Poll remote rules for updates with validation */
66
+ private pollRemoteRules;
67
+ }