@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 +147 -0
- package/README.md +3 -3
- package/dist/detectors/base.d.ts +18 -20
- package/dist/detectors/base.js +50 -49
- package/dist/detectors/sequence.js +20 -3
- package/dist/detectors/state.d.ts +1 -0
- package/dist/detectors/state.js +39 -66
- package/dist/utils/safe-regex.d.ts +9 -0
- package/dist/utils/safe-regex.js +43 -10
- package/dist/wardn.d.ts +1 -13
- package/dist/wardn.js +11 -45
- package/package.json +11 -5
- package/dist/agent-guard.d.ts +0 -28
- package/dist/agent-guard.js +0 -206
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.
|
|
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@
|
|
29
|
+
npm install @wardnmesh/sdk-node@latest
|
|
30
30
|
# or
|
|
31
|
-
yarn add @wardnmesh/sdk-node@
|
|
31
|
+
yarn add @wardnmesh/sdk-node@latest
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## v0.4.0 API Overview
|
package/dist/detectors/base.d.ts
CHANGED
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
import { ToolData, Violation, Rule, Detector, StateProvider, DetectorType, ThreatAction } from '../types';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
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
|
-
*
|
|
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
|
/**
|
package/dist/detectors/base.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
|
|
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
|
|
70
|
-
additionalInfo
|
|
71
|
+
filePath,
|
|
72
|
+
additionalInfo,
|
|
71
73
|
},
|
|
72
74
|
timestamp: new Date().toISOString(),
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/detectors/state.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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;
|
package/dist/utils/safe-regex.js
CHANGED
|
@@ -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
|
-
/\(
|
|
19
|
-
/\(
|
|
20
|
-
/\(
|
|
21
|
-
/\(
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
356
|
-
recommendedAction: this.getRecommendedActionForSeverity(rule),
|
|
356
|
+
recommendedAction: (0, base_1.mapSeverityToAction)(rule),
|
|
357
357
|
scope: 'Semantic analysis',
|
|
358
358
|
};
|
|
359
359
|
}
|
|
360
|
-
/**
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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.
|
|
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
|
+
}
|
package/dist/agent-guard.d.ts
DELETED
|
@@ -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
|
-
}
|
package/dist/agent-guard.js
DELETED
|
@@ -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;
|