@wardnmesh/sdk-node 0.4.0 → 0.4.5

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/CHANGELOG.md ADDED
@@ -0,0 +1,147 @@
1
+ # Changelog
2
+
3
+ All notable changes to @wardnmesh/sdk-node will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.4.0] - 2026-01-17
9
+
10
+ ### 🚨 Breaking Changes
11
+
12
+ **Action-Based Architecture**: Replaces boolean `allowed` field with granular action types.
13
+
14
+ - **`ScanResult.allowed` → `ScanResult.action`**
15
+ - Type: `boolean` → `ThreatAction` (`'block'` | `'confirm'` | `'warn'` | `'log'` | `'allow'`)
16
+ - Provides fine-grained control over threat response
17
+ - Enables user confirmation for high-risk operations
18
+
19
+ ### Added
20
+
21
+ - **Confirmation Support** (`action: 'confirm'`)
22
+ - New `confirmationDetails` field in `ScanResult`
23
+ - Pre-formatted confirmation messages with context
24
+ - Configurable timeout and default action
25
+ - Designed for user approval dialogs in CLI/UI
26
+
27
+ - **Action Priority System**
28
+ - `determineAction()` method with priority: `block` > `confirm` > `warn` > `log` > `allow`
29
+ - Defensive fail-closed design: defaults to `'block'` on invalid actions
30
+ - Comprehensive validation with fallback safety
31
+
32
+ - **Enhanced Violation Metadata**
33
+ - Added `recommendedAction: ThreatAction` field to `Violation`
34
+ - Added `scope: string` field for categorization
35
+ - Richer context for decision-making
36
+
37
+ ### Changed
38
+
39
+ - **Core API**: `scan()` now returns `action` instead of `allowed`
40
+ - **Middleware**: Callbacks now receive full `ScanResult` instead of just `violations`
41
+ - **Error Handling**: Invalid `recommendedAction` values trigger warnings and fail-closed
42
+
43
+ ### Fixed
44
+
45
+ - **Defensive Design**: Prevents security bypass from missing/invalid `recommendedAction`
46
+ - **Type Safety**: All violation fields properly validated before action determination
47
+
48
+ ### Migration
49
+
50
+ See [MIGRATION_v0.4.0.md](MIGRATION_v0.4.0.md) for detailed upgrade guide.
51
+
52
+ **Quick Migration**:
53
+ ```typescript
54
+ // Before (v0.2.3)
55
+ if (!result.allowed) { throw new Error('Blocked'); }
56
+
57
+ // After (v0.4.0)
58
+ if (result.action === 'block') { throw new Error('Blocked'); }
59
+ ```
60
+
61
+ ### Testing
62
+
63
+ - ✅ 17 tests passing (4 test suites)
64
+ - ✅ All action types validated
65
+ - ✅ Confirmation flow tested
66
+ - ✅ Fail-closed behavior verified
67
+
68
+ ---
69
+
70
+ ## [0.2.3] - 2026-01-16
71
+
72
+ ### Fixed
73
+
74
+ - Improved severity-to-action mapping in integrations
75
+ - Enhanced error handling with better null checks
76
+
77
+ ### Documentation
78
+
79
+ - Updated examples with current API patterns
80
+ - Clarified rule configuration options
81
+
82
+ ---
83
+
84
+ ## [0.2.0] - 2026-01-15
85
+
86
+ ### Added
87
+
88
+ - **Semantic Analysis**: ML-based threat detection using local models
89
+ - **State Tracking**: Session-aware detection for multi-turn attacks
90
+ - **Pattern Detectors**: Regex-based threat pattern matching
91
+ - **Sequence Detectors**: Multi-step attack pattern recognition
92
+
93
+ ### Changed
94
+
95
+ - Unified detector API for all detection types
96
+ - Improved telemetry with detailed metrics
97
+
98
+ ### Performance
99
+
100
+ - <20ms latency for most scans
101
+ - Local-first processing (no external API calls)
102
+ - Lazy loading for ML models
103
+
104
+ ---
105
+
106
+ ## [0.1.0] - 2026-01-10
107
+
108
+ ### Added
109
+
110
+ - Initial release of WardnMesh SDK for Node.js
111
+ - Core `Wardn` class with singleton pattern
112
+ - Basic threat detection for:
113
+ - PII (Personal Identifiable Information)
114
+ - Prompt injection patterns
115
+ - API key leakage
116
+ - Express middleware support
117
+ - Next.js middleware support
118
+ - Vercel AI SDK integration
119
+ - Local rule management
120
+
121
+ ### Features
122
+
123
+ - Rule-based scanning engine
124
+ - Configurable severity levels
125
+ - Telemetry and reporting
126
+ - Extensible detector system
127
+ - TypeScript support with full type definitions
128
+
129
+ ---
130
+
131
+ ## Version Compatibility
132
+
133
+ | SDK Version | MCP Server | Node.js | Status |
134
+ |-------------|------------|---------|--------|
135
+ | 0.4.0 | 0.2.3+ | 18+ | ✅ Current |
136
+ | 0.2.3 | 0.2.0+ | 18+ | ⚠️ Legacy |
137
+ | 0.2.0 | 0.2.0+ | 18+ | ⚠️ Legacy |
138
+ | 0.1.0 | 0.1.0+ | 18+ | ❌ Deprecated |
139
+
140
+ ---
141
+
142
+ ## Links
143
+
144
+ - [NPM Package](https://www.npmjs.com/package/@wardnmesh/sdk-node)
145
+ - [GitHub Repository](https://github.com/PCIRCLE-AI/wardnmesh)
146
+ - [Documentation](https://wardnmesh.ai/docs/sdk)
147
+ - [Migration Guides](MIGRATION_v0.4.0.md)
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @wardnmesh/sdk-node
2
2
 
3
- > **Latest Version: v0.4.0** (Released 2026-01-17)
3
+ > **Latest Version: v0.4.5** (Released 2026-01-19)
4
4
 
5
5
  **WardnMesh.AI** (formerly AgentGuard) is an active defense middleware for AI Agents. This SDK allows you to verify LLM inputs/outputs, block prompt injections, and prevent data exfiltration in real-time.
6
6
 
@@ -26,9 +26,9 @@
26
26
  ## Installation
27
27
 
28
28
  ```bash
29
- npm install @wardnmesh/sdk-node@0.4.0
29
+ npm install @wardnmesh/sdk-node@latest
30
30
  # or
31
- yarn add @wardnmesh/sdk-node@0.4.0
31
+ yarn add @wardnmesh/sdk-node@latest
32
32
  ```
33
33
 
34
34
  ## v0.4.0 API Overview
@@ -1,35 +1,33 @@
1
1
  import { ToolData, Violation, Rule, Detector, StateProvider, DetectorType, ThreatAction } from '../types';
2
2
  /**
3
- * Abstract base detector class
3
+ * Map rule severity to recommended action, with optional rule action override.
4
4
  *
5
+ * Default mapping:
6
+ * - critical -> block (immediate threat)
7
+ * - high -> confirm (needs user approval)
8
+ * - medium -> warn (notify but allow)
9
+ * - low -> log (record only)
10
+ *
11
+ * Rules can override default mapping via rule.action.
12
+ */
13
+ export declare function mapSeverityToAction(rule: Rule): ThreatAction;
14
+ /**
15
+ * Map rule category to scope description.
16
+ */
17
+ export declare function mapCategoryToScope(category: string): string;
18
+ /**
19
+ * Abstract base detector class.
5
20
  * Provides common functionality for all detectors.
6
21
  */
7
22
  export declare abstract class BaseDetector implements Detector {
8
23
  abstract detect(toolData: ToolData, rule: Rule, sessionState: StateProvider): Violation | null;
9
24
  abstract getType(): DetectorType | string;
10
25
  /**
11
- * Generate unique violation ID
26
+ * Generate unique violation ID using cryptographically secure random.
12
27
  */
13
28
  protected generateViolationId(): string;
14
29
  /**
15
- * v0.4.0: Map severity to recommended action, with optional rule action override
16
- *
17
- * Default mapping:
18
- * - critical → block (immediate threat)
19
- * - high → confirm (needs user approval)
20
- * - medium → warn (notify but allow)
21
- * - low → log (record only)
22
- *
23
- * Rules can override default mapping via rule.action
24
- */
25
- protected getRecommendedActionForSeverity(rule: Rule): ThreatAction;
26
- /**
27
- * v0.3.0: Determine scope description based on rule category
28
- */
29
- protected getScopeForCategory(category: string): string;
30
- /**
31
- * Create violation object
32
- * v0.4.0: Use rule.action override for recommendedAction
30
+ * Create violation object with standardized structure.
33
31
  */
34
32
  protected createViolation(rule: Rule, toolData: ToolData, additionalInfo?: Record<string, unknown>): Violation;
35
33
  /**
@@ -1,62 +1,64 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BaseDetector = void 0;
4
+ exports.mapSeverityToAction = mapSeverityToAction;
5
+ exports.mapCategoryToScope = mapCategoryToScope;
6
+ const crypto_1 = require("crypto");
4
7
  /**
5
- * Abstract base detector class
8
+ * Map rule severity to recommended action, with optional rule action override.
6
9
  *
10
+ * Default mapping:
11
+ * - critical -> block (immediate threat)
12
+ * - high -> confirm (needs user approval)
13
+ * - medium -> warn (notify but allow)
14
+ * - low -> log (record only)
15
+ *
16
+ * Rules can override default mapping via rule.action.
17
+ */
18
+ function mapSeverityToAction(rule) {
19
+ if (rule.action) {
20
+ return rule.action;
21
+ }
22
+ switch (rule.severity) {
23
+ case 'critical': return 'block';
24
+ case 'high': return 'confirm';
25
+ case 'medium': return 'warn';
26
+ case 'low': return 'log';
27
+ default: return 'block';
28
+ }
29
+ }
30
+ /**
31
+ * Map rule category to scope description.
32
+ */
33
+ function mapCategoryToScope(category) {
34
+ switch (category) {
35
+ case 'workflow': return 'Workflow safety';
36
+ case 'quality': return 'Code quality';
37
+ case 'safety': return 'Security';
38
+ case 'network_boundary': return 'Network access';
39
+ case 'supply_chain': return 'Supply chain';
40
+ default: return 'Security violation';
41
+ }
42
+ }
43
+ /**
44
+ * Abstract base detector class.
7
45
  * Provides common functionality for all detectors.
8
46
  */
9
47
  class BaseDetector {
10
48
  /**
11
- * Generate unique violation ID
49
+ * Generate unique violation ID using cryptographically secure random.
12
50
  */
13
51
  generateViolationId() {
14
- // Note: Using substring() instead of deprecated substr()
15
- return `violation_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
16
- }
17
- /**
18
- * v0.4.0: Map severity to recommended action, with optional rule action override
19
- *
20
- * Default mapping:
21
- * - critical → block (immediate threat)
22
- * - high → confirm (needs user approval)
23
- * - medium → warn (notify but allow)
24
- * - low → log (record only)
25
- *
26
- * Rules can override default mapping via rule.action
27
- */
28
- getRecommendedActionForSeverity(rule) {
29
- // Rule action override takes precedence
30
- if (rule.action) {
31
- return rule.action;
32
- }
33
- // Default severity-to-action mapping
34
- switch (rule.severity) {
35
- case 'critical': return 'block';
36
- case 'high': return 'confirm'; // v0.4.0: Changed from 'warn' to 'confirm'
37
- case 'medium': return 'warn';
38
- case 'low': return 'log';
39
- default: return 'block';
40
- }
41
- }
42
- /**
43
- * v0.3.0: Determine scope description based on rule category
44
- */
45
- getScopeForCategory(category) {
46
- switch (category) {
47
- case 'workflow': return 'Workflow safety';
48
- case 'quality': return 'Code quality';
49
- case 'safety': return 'Security';
50
- case 'network_boundary': return 'Network access';
51
- case 'supply_chain': return 'Supply chain';
52
- default: return 'Security violation';
53
- }
52
+ return `violation_${(0, crypto_1.randomUUID)()}`;
54
53
  }
55
54
  /**
56
- * Create violation object
57
- * v0.4.0: Use rule.action override for recommendedAction
55
+ * Create violation object with standardized structure.
58
56
  */
59
57
  createViolation(rule, toolData, additionalInfo) {
58
+ const filePath = (toolData.parameters.file_path ||
59
+ toolData.parameters.TargetFile ||
60
+ toolData.parameters.AbsolutePath ||
61
+ toolData.parameters.path);
60
62
  return {
61
63
  id: this.generateViolationId(),
62
64
  ruleId: rule.id,
@@ -66,13 +68,12 @@ class BaseDetector {
66
68
  context: {
67
69
  toolName: toolData.toolName,
68
70
  toolData,
69
- filePath: (toolData.parameters.file_path || toolData.parameters.TargetFile || toolData.parameters.AbsolutePath || toolData.parameters.path),
70
- additionalInfo
71
+ filePath,
72
+ additionalInfo,
71
73
  },
72
74
  timestamp: new Date().toISOString(),
73
- // v0.4.0: Support rule.action override
74
- recommendedAction: this.getRecommendedActionForSeverity(rule),
75
- scope: this.getScopeForCategory(rule.category)
75
+ recommendedAction: mapSeverityToAction(rule),
76
+ scope: mapCategoryToScope(rule.category),
76
77
  };
77
78
  }
78
79
  /**
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SequenceDetector = void 0;
4
4
  const base_1 = require("./base");
5
+ const safe_regex_1 = require("../utils/safe-regex");
5
6
  class SequenceDetector extends base_1.BaseDetector {
6
7
  getType() {
7
8
  return 'sequence';
@@ -60,7 +61,12 @@ class SequenceDetector extends base_1.BaseDetector {
60
61
  }
61
62
  if (step.matchesPattern) {
62
63
  const currentValue = this.extractValue(currentTool, step.extractPath);
63
- const regex = new RegExp(step.matchesPattern);
64
+ // SECURITY FIX Round 16: Use getCachedRegex for ReDoS protection
65
+ const regex = (0, safe_regex_1.getCachedRegex)(step.matchesPattern);
66
+ if (!regex) {
67
+ // Invalid or unsafe pattern - skip this check
68
+ return { matched: true };
69
+ }
64
70
  if (typeof currentValue === 'string' && !regex.test(currentValue)) {
65
71
  return {
66
72
  matched: false,
@@ -103,7 +109,12 @@ class SequenceDetector extends base_1.BaseDetector {
103
109
  }
104
110
  }
105
111
  if (step.matchesPattern) {
106
- const regex = new RegExp(step.matchesPattern);
112
+ // SECURITY FIX Round 16: Use getCachedRegex for ReDoS protection
113
+ const regex = (0, safe_regex_1.getCachedRegex)(step.matchesPattern);
114
+ if (!regex) {
115
+ // Invalid or unsafe pattern - skip this tool
116
+ continue;
117
+ }
107
118
  if (typeof value === 'string' && !regex.test(value)) {
108
119
  continue;
109
120
  }
@@ -123,7 +134,13 @@ class SequenceDetector extends base_1.BaseDetector {
123
134
  }
124
135
  if (lastStep.matchesPattern) {
125
136
  const val = this.extractValue(toolData, lastStep.extractPath);
126
- if (typeof val === 'string' && !new RegExp(lastStep.matchesPattern).test(val)) {
137
+ // SECURITY FIX Round 16: Use getCachedRegex for ReDoS protection
138
+ const regex = (0, safe_regex_1.getCachedRegex)(lastStep.matchesPattern);
139
+ if (!regex) {
140
+ // Invalid or unsafe pattern - skip this check (allow through)
141
+ return null;
142
+ }
143
+ if (typeof val === 'string' && !regex.test(val)) {
127
144
  return null;
128
145
  }
129
146
  }
@@ -5,4 +5,5 @@ export declare class StateDetector extends BaseDetector {
5
5
  detect(toolData: ToolData, rule: Rule, sessionState: StateProvider): Violation | null;
6
6
  private isTriggerEvent;
7
7
  private updateStateFromTool;
8
+ private setStateWithTimestamp;
8
9
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StateDetector = void 0;
4
4
  const base_1 = require("./base");
5
+ const safe_regex_1 = require("../utils/safe-regex");
5
6
  class StateDetector extends base_1.BaseDetector {
6
7
  getType() {
7
8
  return 'state';
@@ -9,84 +10,56 @@ class StateDetector extends base_1.BaseDetector {
9
10
  detect(toolData, rule, sessionState) {
10
11
  const config = rule.detector.config;
11
12
  this.updateStateFromTool(config, toolData, sessionState);
12
- if (this.isTriggerEvent(config, toolData)) {
13
- const currentState = sessionState.getCustomState(config.requiredState);
14
- if (currentState !== config.targetStateValue) {
15
- return {
16
- id: this.generateViolationId(),
17
- ruleId: rule.id,
18
- ruleName: rule.name,
19
- severity: rule.severity,
20
- timestamp: new Date().toISOString(),
21
- description: rule.description,
22
- context: {
23
- toolName: toolData.toolName,
24
- toolData: toolData,
25
- additionalInfo: {
26
- message: `Required state '${config.requiredState}' is '${currentState || 'undefined'}', expected '${config.targetStateValue}'.`
27
- }
28
- },
29
- // v0.4.0: Support rule.action override
30
- recommendedAction: this.getRecommendedActionForSeverity(rule),
31
- scope: this.getScopeForCategory(rule.category)
32
- };
33
- }
34
- if (config.stateDerivation?.validityDurationMs) {
35
- const lastUpdate = sessionState.getCustomState(`${config.requiredState}_timestamp`);
36
- if (lastUpdate && typeof lastUpdate === 'string') {
37
- const timeDiff = new Date().getTime() - new Date(lastUpdate).getTime();
38
- if (timeDiff > config.stateDerivation.validityDurationMs) {
39
- return {
40
- id: this.generateViolationId(),
41
- ruleId: rule.id,
42
- ruleName: rule.name,
43
- severity: rule.severity,
44
- timestamp: new Date().toISOString(),
45
- description: rule.description,
46
- context: {
47
- toolName: toolData.toolName,
48
- toolData: toolData,
49
- additionalInfo: {
50
- message: `Required state '${config.requiredState}' has expired (last verified ${Math.floor(timeDiff / 1000)}s ago).`
51
- }
52
- },
53
- // v0.4.0: Support rule.action override
54
- recommendedAction: this.getRecommendedActionForSeverity(rule),
55
- scope: this.getScopeForCategory(rule.category)
56
- };
57
- }
13
+ if (!this.isTriggerEvent(config, toolData)) {
14
+ return null;
15
+ }
16
+ const currentState = sessionState.getCustomState(config.requiredState);
17
+ if (currentState !== config.targetStateValue) {
18
+ const message = `Required state '${config.requiredState}' is '${currentState ?? 'undefined'}', expected '${config.targetStateValue}'.`;
19
+ return this.createViolation(rule, toolData, { message });
20
+ }
21
+ if (config.stateDerivation?.validityDurationMs) {
22
+ const lastUpdate = sessionState.getCustomState(`${config.requiredState}_timestamp`);
23
+ if (lastUpdate && typeof lastUpdate === 'string') {
24
+ const timeDiff = Date.now() - new Date(lastUpdate).getTime();
25
+ if (timeDiff > config.stateDerivation.validityDurationMs) {
26
+ const message = `Required state '${config.requiredState}' has expired (last verified ${Math.floor(timeDiff / 1000)}s ago).`;
27
+ return this.createViolation(rule, toolData, { message });
58
28
  }
59
29
  }
60
30
  }
61
31
  return null;
62
32
  }
63
33
  isTriggerEvent(config, toolData) {
64
- if (toolData.toolName !== config.trigger.tool)
34
+ if (toolData.toolName !== config.trigger.tool) {
35
+ return false;
36
+ }
37
+ if (!config.trigger.parameterMatch) {
38
+ return true;
39
+ }
40
+ const paramValue = this.extractValue(toolData.parameters, config.trigger.parameterMatch.key);
41
+ if (!paramValue || typeof paramValue !== 'string') {
65
42
  return false;
66
- if (config.trigger.parameterMatch) {
67
- const paramValue = this.extractValue(toolData.parameters, config.trigger.parameterMatch.key);
68
- if (!paramValue || typeof paramValue !== 'string')
69
- return false;
70
- const regex = new RegExp(config.trigger.parameterMatch.valuePattern, 'i');
71
- return regex.test(paramValue);
72
43
  }
73
- return true;
44
+ const regex = (0, safe_regex_1.getCachedRegex)(config.trigger.parameterMatch.valuePattern, 'i');
45
+ return regex ? regex.test(paramValue) : false;
74
46
  }
75
47
  updateStateFromTool(config, toolData, sessionState) {
76
- if (config.stateDerivation && toolData.toolName === config.stateDerivation.fromTool) {
77
- // Heuristic check for 'test' in command line if it's run_command
78
- if (toolData.toolName === 'run_command' && toolData.parameters.CommandLine) {
79
- const commandLine = toolData.parameters.CommandLine;
80
- if (commandLine.includes('test') || commandLine.includes('vitest')) {
81
- sessionState.setCustomState(config.stateDerivation.setState, config.stateDerivation.setValue);
82
- sessionState.setCustomState(`${config.stateDerivation.setState}_timestamp`, new Date().toISOString());
83
- }
84
- }
85
- else {
86
- sessionState.setCustomState(config.stateDerivation.setState, config.stateDerivation.setValue);
87
- sessionState.setCustomState(`${config.stateDerivation.setState}_timestamp`, new Date().toISOString());
48
+ if (!config.stateDerivation || toolData.toolName !== config.stateDerivation.fromTool) {
49
+ return;
50
+ }
51
+ // For run_command tool, only update state if command contains test keywords
52
+ if (toolData.toolName === 'run_command' && toolData.parameters.CommandLine) {
53
+ const commandLine = toolData.parameters.CommandLine;
54
+ if (!commandLine.includes('test') && !commandLine.includes('vitest')) {
55
+ return;
88
56
  }
89
57
  }
58
+ this.setStateWithTimestamp(sessionState, config.stateDerivation.setState, config.stateDerivation.setValue);
59
+ }
60
+ setStateWithTimestamp(sessionState, key, value) {
61
+ sessionState.setCustomState(key, value);
62
+ sessionState.setCustomState(`${key}_timestamp`, new Date().toISOString());
90
63
  }
91
64
  }
92
65
  exports.StateDetector = StateDetector;
@@ -51,3 +51,12 @@ export declare function safeRegexExec(regex: RegExp, content: string, maxLength?
51
51
  * Safe regex test with content length limit
52
52
  */
53
53
  export declare function safeRegexTest(regex: RegExp, content: string, maxLength?: number): boolean;
54
+ /**
55
+ * Create a safe regex with validation
56
+ * Throws error if pattern is unsafe
57
+ */
58
+ export declare function createSafeRegex(pattern: string, flags?: string): RegExp;
59
+ /**
60
+ * Validate multiple regex patterns
61
+ */
62
+ export declare function validateRegexPatterns(patterns: string[]): RegexValidationResult;
@@ -12,18 +12,23 @@ exports.resetPatternErrorMetrics = resetPatternErrorMetrics;
12
12
  exports.getCachedRegex = getCachedRegex;
13
13
  exports.safeRegexExec = safeRegexExec;
14
14
  exports.safeRegexTest = safeRegexTest;
15
+ exports.createSafeRegex = createSafeRegex;
16
+ exports.validateRegexPatterns = validateRegexPatterns;
15
17
  const logger_1 = require("./logger");
16
18
  // Patterns that are known to cause catastrophic backtracking
19
+ // SECURITY FIX: Detect dangerous patterns in BOTH non-capturing (?...) AND capturing (...) groups
20
+ // VULNERABILITY (v1): Required `\?` which only matched (?...) groups, missing dangerous (...) groups
21
+ // FIX (v2): Remove `\?` requirement to catch all dangerous nested quantifiers
17
22
  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
23
+ /\([^)]*\+[^)]*\)\+/, // Nested quantifiers: (...+...)+ or (?...+...)+
24
+ /\([^)]*\*[^)]*\)\+/, // Nested quantifiers: (...*...)+ or (?...*...)+
25
+ /\([^)]*\+[^)]*\)\*/, // Nested quantifiers: (...+...)* or (?...+...)*
26
+ /\([^)]*\*[^)]*\)\*/, // Nested quantifiers: (...*...)* or (?...*...)*
27
+ /\([^)]+\)\{[0-9]+,\}/, // Unbounded repetition of groups: (...){10,}
28
+ /\.\*\.\*/, // Multiple greedy wildcards: .*.*
29
+ /\.\+\.\+/, // Multiple greedy wildcards: .+.+
30
+ /\([^)]*\|[^)]*\)\+/, // Alternation with quantifier: (a|b)+
31
+ /\([^)]*\|[^)]*\)\*/, // Alternation with quantifier: (a|b)*
27
32
  ];
28
33
  // Maximum allowed regex pattern length
29
34
  const MAX_PATTERN_LENGTH = 1000;
@@ -166,7 +171,12 @@ function getCachedRegex(pattern, flags = "") {
166
171
  errorMetrics.totalPatternsProcessed++;
167
172
  // Convert PCRE modifiers first
168
173
  const pcreResult = convertPCREModifiers(pattern);
169
- if (pcreResult.converted && pcreResult.error) {
174
+ // SECURITY FIX: Check for PCRE conversion errors
175
+ // VULNERABILITY (v1): Condition `converted && error` was always false when error exists
176
+ // - When unsupported modifiers found: converted=false, error=truthy
177
+ // - Condition: false && truthy = false (check skipped!)
178
+ // FIX (v2): Check error first, regardless of converted status
179
+ if (pcreResult.error) {
170
180
  // Unsupported PCRE modifiers - skip this pattern
171
181
  errorMetrics.pcreConversionFailures++;
172
182
  logger_1.logger.warn(`Skipping pattern with unsupported PCRE modifiers: ${pcreResult.error}`);
@@ -218,3 +228,26 @@ function safeRegexTest(regex, content, maxLength = 50000) {
218
228
  const safeContent = content.length > maxLength ? content.substring(0, maxLength) : content;
219
229
  return regex.test(safeContent);
220
230
  }
231
+ /**
232
+ * Create a safe regex with validation
233
+ * Throws error if pattern is unsafe
234
+ */
235
+ function createSafeRegex(pattern, flags = "") {
236
+ const validation = validateRegexPattern(pattern);
237
+ if (!validation.valid) {
238
+ throw new Error(validation.error || "Invalid regex pattern");
239
+ }
240
+ return new RegExp(pattern, flags);
241
+ }
242
+ /**
243
+ * Validate multiple regex patterns
244
+ */
245
+ function validateRegexPatterns(patterns) {
246
+ for (const pattern of patterns) {
247
+ const result = validateRegexPattern(pattern);
248
+ if (!result.valid) {
249
+ return result;
250
+ }
251
+ }
252
+ return { valid: true };
253
+ }
package/dist/wardn.d.ts CHANGED
@@ -47,19 +47,7 @@ export declare class Wardn {
47
47
  private detectViolation;
48
48
  /** Handle semantic detection */
49
49
  private detectSemanticViolation;
50
- /**
51
- * v0.4.0: Map severity to recommended action, with optional rule action override
52
- *
53
- * Default mapping:
54
- * - critical → block (immediate threat)
55
- * - high → confirm (needs user approval)
56
- * - medium → warn (notify but allow)
57
- * - low → log (record only)
58
- *
59
- * Rules can override default mapping via rule.action
60
- */
61
- private getRecommendedActionForSeverity;
62
- /** Report violation to telemetry */
50
+ /** Report violation to telemetry (redacts sensitive toolData) */
63
51
  private reportViolation;
64
52
  /** Report scan completion to telemetry */
65
53
  private reportScanComplete;
package/dist/wardn.js CHANGED
@@ -4,6 +4,7 @@ exports.Wardn = void 0;
4
4
  const pattern_1 = require("./detectors/pattern");
5
5
  const sequence_1 = require("./detectors/sequence");
6
6
  const state_1 = require("./detectors/state");
7
+ const base_1 = require("./detectors/base");
7
8
  const safe_regex_1 = require("./utils/safe-regex");
8
9
  const update_checker_1 = require("./update-checker");
9
10
  const session_manager_1 = require("./state/session-manager");
@@ -201,7 +202,7 @@ class Wardn {
201
202
  if (violation) {
202
203
  violations.push(violation);
203
204
  stateAdapter.addViolation(violation);
204
- this.reportViolation(violation, toolData, currentSessionId);
205
+ this.reportViolation(violation, currentSessionId);
205
206
  }
206
207
  }
207
208
  await this.stateProvider.setState(currentSessionId, stateAdapter.exportState());
@@ -352,50 +353,17 @@ This operation requires your approval to proceed.`;
352
353
  additionalInfo: { score },
353
354
  },
354
355
  timestamp: new Date().toISOString(),
355
- // v0.4.0: Set recommended action (rule.action or default severity mapping)
356
- recommendedAction: this.getRecommendedActionForSeverity(rule),
356
+ recommendedAction: (0, base_1.mapSeverityToAction)(rule),
357
357
  scope: 'Semantic analysis',
358
358
  };
359
359
  }
360
- /**
361
- * v0.4.0: Map severity to recommended action, with optional rule action override
362
- *
363
- * Default mapping:
364
- * - critical → block (immediate threat)
365
- * - high → confirm (needs user approval)
366
- * - medium → warn (notify but allow)
367
- * - low → log (record only)
368
- *
369
- * Rules can override default mapping via rule.action
370
- */
371
- getRecommendedActionForSeverity(rule) {
372
- // Rule action override takes precedence
373
- if (rule.action) {
374
- return rule.action;
375
- }
376
- // Default severity-to-action mapping
377
- switch (rule.severity) {
378
- case 'critical':
379
- return 'block';
380
- case 'high':
381
- return 'confirm'; // v0.4.0: Changed from 'warn' to 'confirm'
382
- case 'medium':
383
- return 'warn';
384
- case 'low':
385
- return 'log';
386
- default:
387
- return 'block'; // Safe default
388
- }
389
- }
390
- /** Report violation to telemetry */
391
- reportViolation(violation, toolData, sessionId) {
392
- // REDACTION: Create a safe copy for the cloud
393
- const safeViolation = { ...violation };
394
- if (safeViolation.context) {
395
- // Remove raw toolData (prompts) from the cloud payload
396
- const { toolData: _removed, ...safeContext } = safeViolation.context;
397
- safeViolation.context = safeContext;
398
- }
360
+ /** Report violation to telemetry (redacts sensitive toolData) */
361
+ reportViolation(violation, sessionId) {
362
+ const { toolData: _redacted, ...safeContext } = violation.context;
363
+ const safeViolation = {
364
+ ...violation,
365
+ context: safeContext,
366
+ };
399
367
  this.telemetry.emit({
400
368
  eventType: "violation_detected",
401
369
  timestamp: new Date().toISOString(),
@@ -491,14 +459,12 @@ This operation requires your approval to proceed.`;
491
459
  async pollRemoteRules() {
492
460
  if (!this.config.remoteRulesUrl || this.isShutdown)
493
461
  return;
494
- const FETCH_TIMEOUT_MS = 10000; // 10 second timeout
495
462
  const poll = async () => {
496
463
  if (this.isShutdown)
497
464
  return;
498
465
  try {
499
- // Create AbortController for timeout
500
466
  const controller = new AbortController();
501
- const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
467
+ const timeoutId = setTimeout(() => controller.abort(), security_limits_1.SECURITY_LIMITS.FETCH_TIMEOUT_MS);
502
468
  const response = await fetch(this.config.remoteRulesUrl, {
503
469
  headers: {
504
470
  "Accept": "application/json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wardnmesh/sdk-node",
3
- "version": "0.4.0",
3
+ "version": "0.4.5",
4
4
  "description": "WardnMesh.AI Node.js SDK - Active Defense Middleware for AI Agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -29,7 +29,8 @@
29
29
  "dist",
30
30
  "bin",
31
31
  "README.md",
32
- "LICENSE"
32
+ "LICENSE",
33
+ "CHANGELOG.md"
33
34
  ],
34
35
  "publishConfig": {
35
36
  "access": "public"
@@ -42,18 +43,23 @@
42
43
  ],
43
44
  "repository": {
44
45
  "type": "git",
45
- "url": "git+https://github.com/PCIRCLE-AI/wardnmesh.git"
46
+ "url": "git+https://github.com/PCIRCLE-AI/wardnmesh-sdk-node.git"
46
47
  },
47
48
  "bugs": {
48
- "url": "https://github.com/PCIRCLE-AI/wardnmesh/issues"
49
+ "url": "https://github.com/PCIRCLE-AI/wardnmesh-sdk-node/issues"
49
50
  },
50
51
  "homepage": "https://wardnmesh.ai",
51
52
  "author": "KT",
52
53
  "license": "Elastic-2.0",
54
+ "engines": {
55
+ "node": ">=18.0.0",
56
+ "npm": ">=9.0.0"
57
+ },
53
58
  "devDependencies": {
54
59
  "@types/jest": "^29.5.11",
55
60
  "@types/node": "^20.10.0",
56
61
  "jest": "^29.7.0",
62
+ "next": "^14.2.35",
57
63
  "ts-jest": "^29.1.1",
58
64
  "typescript": "^5.0.0"
59
65
  },
@@ -68,4 +74,4 @@
68
74
  "dependencies": {
69
75
  "@xenova/transformers": "^2.17.2"
70
76
  }
71
- }
77
+ }
@@ -1,28 +0,0 @@
1
- import { AgentGuardConfig, ScanResult, AgentRequest } from "./types";
2
- import { SessionStateProvider } from "./state/session-manager";
3
- export declare class AgentGuard {
4
- private static instance;
5
- private config;
6
- private stateProvider;
7
- private telemetry;
8
- private patternDetector;
9
- private sequenceDetector;
10
- private stateDetector;
11
- private semanticDetector;
12
- private constructor();
13
- private initTelemetry;
14
- static getInstance(): AgentGuard;
15
- static init(config: AgentGuardConfig, stateProvider?: SessionStateProvider): AgentGuard;
16
- /** Main entry point to scan an agent request */
17
- scan(request: AgentRequest): Promise<ScanResult>;
18
- /** Normalize request to ToolData format */
19
- private normalizeRequest;
20
- /** Run detector for a single rule */
21
- private detectViolation;
22
- /** Handle semantic detection */
23
- private detectSemanticViolation;
24
- /** Report violation to telemetry */
25
- private reportViolation;
26
- /** Report scan completion to telemetry */
27
- private reportScanComplete;
28
- }
@@ -1,206 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AgentGuard = 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 session_manager_1 = require("./state/session-manager");
8
- const reporter_1 = require("./telemetry/reporter");
9
- const semantic_detector_1 = require("./detectors/semantic-detector");
10
- /** Synchronous state adapter for in-request state management */
11
- class SyncStateAdapter {
12
- constructor(initialState, maxHistory = 50) {
13
- this.maxHistory = maxHistory;
14
- this.state = {
15
- startTime: initialState.startTime || new Date().toISOString(),
16
- toolCalls: initialState.toolCalls || [],
17
- recentTools: initialState.recentTools || [],
18
- detectedViolations: initialState.detectedViolations || [],
19
- customState: initialState.customState || {},
20
- currentFile: initialState.currentFile,
21
- };
22
- }
23
- getRecentTools(count) {
24
- const tools = this.state.recentTools || [];
25
- return count ? tools.slice(-count) : tools;
26
- }
27
- setCustomState(key, value) {
28
- this.state.customState[key] = value;
29
- }
30
- getCustomState(key) {
31
- return this.state.customState[key];
32
- }
33
- addToolCall(tool) {
34
- if (!this.state.recentTools)
35
- this.state.recentTools = [];
36
- this.state.recentTools.push(tool);
37
- if (this.state.recentTools.length > this.maxHistory) {
38
- this.state.recentTools.shift();
39
- }
40
- }
41
- exportState() {
42
- return this.state;
43
- }
44
- }
45
- class AgentGuard {
46
- constructor(config, stateProvider) {
47
- this.config = config;
48
- this.stateProvider = stateProvider || new session_manager_1.InMemorySessionStateProvider();
49
- this.telemetry = this.initTelemetry();
50
- this.patternDetector = new pattern_1.PatternDetector();
51
- this.sequenceDetector = new sequence_1.SequenceDetector();
52
- this.stateDetector = new state_1.StateDetector();
53
- this.semanticDetector = semantic_detector_1.SemanticDetector.getInstance();
54
- // Emit startup event
55
- this.telemetry.emit({
56
- eventType: "agent_started",
57
- timestamp: new Date().toISOString(),
58
- data: {
59
- config: {
60
- appName: this.config.telemetry?.serviceName,
61
- ruleCount: this.config.rules.length,
62
- },
63
- },
64
- });
65
- }
66
- initTelemetry() {
67
- if (this.config.telemetry?.enabled && process.env.CCB_ENDPOINT) {
68
- return new reporter_1.CCBReporter(process.env.CCB_ENDPOINT, process.env.CCB_API_KEY || "", this.config.telemetry.serviceName || "unknown-service");
69
- }
70
- return new reporter_1.ConsoleReporter();
71
- }
72
- static getInstance() {
73
- if (!AgentGuard.instance) {
74
- throw new Error("AgentGuard not initialized. Call AgentGuard.init() first.");
75
- }
76
- return AgentGuard.instance;
77
- }
78
- static init(config, stateProvider) {
79
- AgentGuard.instance = new AgentGuard(config, stateProvider);
80
- return AgentGuard.instance;
81
- }
82
- /** Main entry point to scan an agent request */
83
- async scan(request) {
84
- const start = Date.now();
85
- const violations = [];
86
- const sessionId = request.sessionId || "default";
87
- try {
88
- const rawState = await this.stateProvider.getState(sessionId);
89
- const stateAdapter = new SyncStateAdapter(rawState, this.config.maxHistorySize);
90
- const toolData = this.normalizeRequest(request);
91
- stateAdapter.addToolCall(toolData);
92
- // Lazy load semantic model if needed
93
- if (this.config.rules.some((r) => r.detector.type === "semantic")) {
94
- await this.semanticDetector.init();
95
- }
96
- // Run all detectors
97
- for (const rule of this.config.rules) {
98
- const violation = await this.detectViolation(rule, toolData, stateAdapter);
99
- if (violation) {
100
- violations.push(violation);
101
- this.reportViolation(violation, toolData, sessionId);
102
- }
103
- }
104
- await this.stateProvider.setState(sessionId, stateAdapter.exportState());
105
- const result = {
106
- allowed: !violations.some((v) => v.severity === "critical"),
107
- violations,
108
- latencyMs: Date.now() - start,
109
- metadata: { analyzedRules: this.config.rules.length },
110
- };
111
- this.reportScanComplete(result, sessionId);
112
- return result;
113
- }
114
- catch (error) {
115
- // Fail-Open Resilience: Never crash the application
116
- console.error("[AgentGuard] Scan failed, failing open:", error);
117
- this.telemetry.emit({
118
- eventType: "error",
119
- timestamp: new Date().toISOString(),
120
- data: {
121
- error: error instanceof Error ? error.message : "Unknown error",
122
- sessionId,
123
- },
124
- });
125
- return {
126
- allowed: true, // Fail-open
127
- violations: [],
128
- latencyMs: Date.now() - start,
129
- metadata: {
130
- error: true,
131
- errorDetails: error instanceof Error ? error.message : "Unknown",
132
- },
133
- };
134
- }
135
- }
136
- /** Normalize request to ToolData format */
137
- normalizeRequest(request) {
138
- return {
139
- toolName: request.toolName || "llm_input",
140
- parameters: request.parameters || { prompt: request.prompt || "" },
141
- result: { success: true },
142
- duration: 0,
143
- timestamp: new Date().toISOString(),
144
- };
145
- }
146
- /** Run detector for a single rule */
147
- async detectViolation(rule, toolData, stateAdapter) {
148
- switch (rule.detector.type) {
149
- case "pattern":
150
- return this.patternDetector.detect(toolData, rule, stateAdapter);
151
- case "sequence":
152
- return this.sequenceDetector.detect(toolData, rule, stateAdapter);
153
- case "state":
154
- return this.stateDetector.detect(toolData, rule, stateAdapter);
155
- case "semantic":
156
- return this.detectSemanticViolation(rule, toolData);
157
- default:
158
- return null;
159
- }
160
- }
161
- /** Handle semantic detection */
162
- async detectSemanticViolation(rule, toolData) {
163
- const prompt = toolData.parameters.prompt ||
164
- JSON.stringify(toolData.parameters);
165
- const config = rule.detector.config;
166
- const { detected, reason, score } = await this.semanticDetector.scan(prompt, config.threshold || 0.75);
167
- if (!detected)
168
- return null;
169
- return {
170
- id: crypto.randomUUID(),
171
- ruleId: rule.id,
172
- ruleName: rule.name,
173
- severity: rule.severity,
174
- description: reason || "Semantic Violation",
175
- context: {
176
- toolName: toolData.toolName,
177
- toolData,
178
- additionalInfo: { score },
179
- },
180
- timestamp: new Date().toISOString(),
181
- };
182
- }
183
- /** Report violation to telemetry */
184
- reportViolation(violation, toolData, sessionId) {
185
- this.telemetry.emit({
186
- eventType: "violation_detected",
187
- timestamp: new Date().toISOString(),
188
- data: { violation, toolData },
189
- metadata: { sessionId },
190
- });
191
- }
192
- /** Report scan completion to telemetry */
193
- reportScanComplete(result, sessionId) {
194
- this.telemetry.emit({
195
- eventType: "scan_complete",
196
- timestamp: new Date().toISOString(),
197
- data: {
198
- allowed: result.allowed,
199
- latencyMs: result.latencyMs,
200
- violationCount: result.violations.length,
201
- },
202
- metadata: { sessionId },
203
- });
204
- }
205
- }
206
- exports.AgentGuard = AgentGuard;