agentshield-sdk 7.2.1 → 7.4.0

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 (57) hide show
  1. package/CHANGELOG.md +125 -1
  2. package/README.md +68 -7
  3. package/bin/agent-shield.js +19 -0
  4. package/package.json +10 -3
  5. package/src/agent-protocol.js +4 -0
  6. package/src/allowlist.js +605 -603
  7. package/src/attack-genome.js +536 -0
  8. package/src/attack-replay.js +246 -0
  9. package/src/audit-streaming.js +486 -469
  10. package/src/audit.js +619 -0
  11. package/src/behavior-profiling.js +299 -289
  12. package/src/behavioral-dna.js +757 -0
  13. package/src/canary.js +273 -271
  14. package/src/compliance-authority.js +803 -0
  15. package/src/compliance.js +619 -617
  16. package/src/confidence-tuning.js +328 -324
  17. package/src/context-scoring.js +362 -360
  18. package/src/cost-optimizer.js +1024 -1024
  19. package/src/detector-core.js +186 -0
  20. package/src/distributed.js +7 -2
  21. package/src/embedding.js +310 -307
  22. package/src/errors.js +9 -0
  23. package/src/evolution-simulator.js +650 -0
  24. package/src/flight-recorder.js +379 -0
  25. package/src/herd-immunity.js +521 -0
  26. package/src/honeypot.js +332 -328
  27. package/src/index.js +6 -5
  28. package/src/integrations.js +1 -2
  29. package/src/intent-firewall.js +775 -0
  30. package/src/llm-redteam.js +678 -670
  31. package/src/main.js +139 -0
  32. package/src/mcp-security-runtime.js +6 -5
  33. package/src/middleware.js +11 -5
  34. package/src/model-fingerprint.js +1059 -1042
  35. package/src/multi-agent-trust.js +459 -453
  36. package/src/multi-agent.js +1 -1
  37. package/src/normalizer.js +734 -0
  38. package/src/pii.js +8 -1
  39. package/src/policy-dsl.js +775 -775
  40. package/src/presets.js +409 -409
  41. package/src/production.js +22 -9
  42. package/src/real-attack-datasets.js +246 -0
  43. package/src/redteam.js +475 -475
  44. package/src/report-generator.js +640 -0
  45. package/src/response-handler.js +436 -429
  46. package/src/scanners.js +358 -357
  47. package/src/self-healing.js +368 -363
  48. package/src/semantic.js +339 -339
  49. package/src/shield-score.js +250 -250
  50. package/src/soc-dashboard.js +394 -0
  51. package/src/sso-saml.js +8 -4
  52. package/src/supply-chain.js +667 -0
  53. package/src/testing.js +24 -2
  54. package/src/threat-intel-federation.js +343 -0
  55. package/src/tool-guard.js +412 -412
  56. package/src/watermark.js +242 -235
  57. package/src/worker-scanner.js +608 -601
@@ -1,289 +1,299 @@
1
- 'use strict';
2
-
3
- /**
4
- * Agent Shield — Agent Behavior Profiling (v3.0)
5
- *
6
- * Establishes baselines for normal agent behavior and detects anomalies
7
- * that may indicate compromise, drift, or attack influence.
8
- *
9
- * Tracks: response patterns, tool usage, topic distribution, timing,
10
- * output length, sentiment shifts, and more.
11
- *
12
- * All processing runs locally — no data ever leaves your environment.
13
- */
14
-
15
- // =========================================================================
16
- // STATISTICAL HELPERS
17
- // =========================================================================
18
-
19
- /**
20
- * Calculate mean of an array.
21
- * @param {number[]} arr
22
- * @returns {number}
23
- */
24
- function mean(arr) {
25
- return arr.length > 0 ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;
26
- }
27
-
28
- /**
29
- * Calculate standard deviation.
30
- * @param {number[]} arr
31
- * @returns {number}
32
- */
33
- function stdDev(arr) {
34
- if (arr.length < 2) return 0;
35
- const m = mean(arr);
36
- const squaredDiffs = arr.map(x => (x - m) ** 2);
37
- return Math.sqrt(squaredDiffs.reduce((a, b) => a + b, 0) / (arr.length - 1));
38
- }
39
-
40
- /**
41
- * Check if a value is anomalous (beyond N standard deviations from mean).
42
- * @param {number} value
43
- * @param {number} m - Mean.
44
- * @param {number} sd - Standard deviation.
45
- * @param {number} [threshold=2] - Number of standard deviations.
46
- * @returns {boolean}
47
- */
48
- function isAnomaly(value, m, sd, threshold = 2) {
49
- if (sd === 0) return value !== m;
50
- return Math.abs(value - m) > threshold * sd;
51
- }
52
-
53
- // =========================================================================
54
- // BEHAVIOR PROFILE
55
- // =========================================================================
56
-
57
- /**
58
- * Maintains a statistical profile of agent behavior.
59
- */
60
- class BehaviorProfile {
61
- /**
62
- * @param {object} [options]
63
- * @param {number} [options.windowSize=200] - Number of observations to maintain.
64
- * @param {number} [options.learningPeriod=20] - Observations before anomaly detection activates.
65
- * @param {number} [options.anomalyThreshold=2.5] - Standard deviations for anomaly detection.
66
- */
67
- constructor(options = {}) {
68
- this.windowSize = options.windowSize || 200;
69
- this.learningPeriod = options.learningPeriod || 20;
70
- this.anomalyThreshold = options.anomalyThreshold || 2.5;
71
-
72
- this._metrics = {
73
- responseLength: [],
74
- responseTime: [],
75
- toolCallCount: [],
76
- threatScore: [],
77
- topicEntropy: []
78
- };
79
-
80
- this._toolUsage = {};
81
- this._topicDistribution = {};
82
- this._totalObservations = 0;
83
- this._anomalies = [];
84
-
85
- console.log('[Agent Shield] BehaviorProfile initialized (windowSize: %d, learningPeriod: %d)', this.windowSize, this.learningPeriod);
86
- }
87
-
88
- /**
89
- * Record an observation of agent behavior.
90
- *
91
- * @param {object} observation
92
- * @param {number} [observation.responseLength] - Length of agent response.
93
- * @param {number} [observation.responseTimeMs] - Time taken to respond.
94
- * @param {string[]} [observation.toolsCalled] - Tools used in this turn.
95
- * @param {number} [observation.threatScore] - Threat score from scanning.
96
- * @param {string} [observation.topic] - Detected topic/category.
97
- * @returns {object} { anomalies: Array, isLearning: boolean }
98
- */
99
- record(observation) {
100
- this._totalObservations++;
101
-
102
- // Record metrics
103
- if (observation.responseLength !== undefined) {
104
- this._addMetric('responseLength', observation.responseLength);
105
- }
106
- if (observation.responseTimeMs !== undefined) {
107
- this._addMetric('responseTime', observation.responseTimeMs);
108
- }
109
- if (observation.toolsCalled) {
110
- this._addMetric('toolCallCount', observation.toolsCalled.length);
111
- for (const tool of observation.toolsCalled) {
112
- this._toolUsage[tool] = (this._toolUsage[tool] || 0) + 1;
113
- }
114
- }
115
- if (observation.threatScore !== undefined) {
116
- this._addMetric('threatScore', observation.threatScore);
117
- }
118
- if (observation.topic) {
119
- this._topicDistribution[observation.topic] = (this._topicDistribution[observation.topic] || 0) + 1;
120
- }
121
-
122
- // Check for anomalies (only after learning period)
123
- const isLearning = this._totalObservations < this.learningPeriod;
124
- const anomalies = isLearning ? [] : this._detectAnomalies(observation);
125
-
126
- if (anomalies.length > 0) {
127
- this._anomalies.push({
128
- timestamp: Date.now(),
129
- observation: this._totalObservations,
130
- anomalies
131
- });
132
- }
133
-
134
- return { anomalies, isLearning };
135
- }
136
-
137
- /**
138
- * Get the current behavior baseline.
139
- * @returns {object}
140
- */
141
- getBaseline() {
142
- const baseline = {};
143
- for (const [metric, values] of Object.entries(this._metrics)) {
144
- if (values.length > 0) {
145
- baseline[metric] = {
146
- mean: Math.round(mean(values) * 100) / 100,
147
- stdDev: Math.round(stdDev(values) * 100) / 100,
148
- min: Math.min(...values),
149
- max: Math.max(...values),
150
- samples: values.length
151
- };
152
- }
153
- }
154
- return baseline;
155
- }
156
-
157
- /**
158
- * Get a full behavior report.
159
- * @returns {object}
160
- */
161
- getReport() {
162
- return {
163
- totalObservations: this._totalObservations,
164
- isLearning: this._totalObservations < this.learningPeriod,
165
- baseline: this.getBaseline(),
166
- toolUsage: { ...this._toolUsage },
167
- topicDistribution: { ...this._topicDistribution },
168
- anomalyCount: this._anomalies.length,
169
- recentAnomalies: this._anomalies.slice(-10),
170
- riskLevel: this._calculateRiskLevel()
171
- };
172
- }
173
-
174
- /**
175
- * Check if the agent appears to be behaving normally.
176
- * @returns {object} { normal: boolean, riskLevel, concerns: string[] }
177
- */
178
- healthCheck() {
179
- const concerns = [];
180
- const report = this.getReport();
181
-
182
- if (report.isLearning) {
183
- return { normal: true, riskLevel: 'unknown', concerns: ['Still in learning period.'] };
184
- }
185
-
186
- // Check for sudden tool usage changes
187
- const totalToolCalls = Object.values(this._toolUsage).reduce((a, b) => a + b, 0);
188
- if (totalToolCalls > 0) {
189
- for (const [tool, count] of Object.entries(this._toolUsage)) {
190
- const ratio = count / totalToolCalls;
191
- if (ratio > 0.7 && totalToolCalls > 10) {
192
- concerns.push(`Tool "${tool}" dominates usage at ${(ratio * 100).toFixed(0)}%.`);
193
- }
194
- }
195
- }
196
-
197
- // Check for high threat score trend
198
- const recentThreats = this._metrics.threatScore.slice(-20);
199
- if (recentThreats.length >= 5 && mean(recentThreats) > 0.5) {
200
- concerns.push('Recent threat scores are elevated.');
201
- }
202
-
203
- // Check for anomaly frequency
204
- const recentAnomalies = this._anomalies.filter(a => Date.now() - a.timestamp < 300000);
205
- if (recentAnomalies.length > 5) {
206
- concerns.push(`${recentAnomalies.length} anomalies in the last 5 minutes.`);
207
- }
208
-
209
- return {
210
- normal: concerns.length === 0,
211
- riskLevel: report.riskLevel,
212
- concerns
213
- };
214
- }
215
-
216
- /** Reset the profile. */
217
- reset() {
218
- for (const key of Object.keys(this._metrics)) {
219
- this._metrics[key] = [];
220
- }
221
- this._toolUsage = {};
222
- this._topicDistribution = {};
223
- this._totalObservations = 0;
224
- this._anomalies = [];
225
- }
226
-
227
- /** @private */
228
- _addMetric(name, value) {
229
- if (!this._metrics[name]) this._metrics[name] = [];
230
- this._metrics[name].push(value);
231
- if (this._metrics[name].length > this.windowSize) {
232
- this._metrics[name].shift();
233
- }
234
- }
235
-
236
- /** @private */
237
- _detectAnomalies(observation) {
238
- const anomalies = [];
239
-
240
- const checks = [
241
- { metric: 'responseLength', value: observation.responseLength, label: 'Response length' },
242
- { metric: 'responseTime', value: observation.responseTimeMs, label: 'Response time' },
243
- { metric: 'toolCallCount', value: observation.toolsCalled ? observation.toolsCalled.length : undefined, label: 'Tool call count' },
244
- { metric: 'threatScore', value: observation.threatScore, label: 'Threat score' }
245
- ];
246
-
247
- for (const check of checks) {
248
- if (check.value === undefined) continue;
249
-
250
- const values = this._metrics[check.metric];
251
- if (values.length < this.learningPeriod) continue;
252
-
253
- const m = mean(values.slice(0, -1)); // Exclude current observation
254
- const sd = stdDev(values.slice(0, -1));
255
-
256
- if (isAnomaly(check.value, m, sd, this.anomalyThreshold)) {
257
- const zScore = sd > 0 ? Math.round(((check.value - m) / sd) * 100) / 100 : 0;
258
- anomalies.push({
259
- metric: check.metric,
260
- label: check.label,
261
- value: check.value,
262
- expected: { mean: Math.round(m * 100) / 100, stdDev: Math.round(sd * 100) / 100 },
263
- zScore,
264
- direction: check.value > m ? 'above' : 'below'
265
- });
266
- }
267
- }
268
-
269
- return anomalies;
270
- }
271
-
272
- /** @private */
273
- _calculateRiskLevel() {
274
- if (this._totalObservations < this.learningPeriod) return 'unknown';
275
-
276
- const recentAnomalies = this._anomalies.filter(a => Date.now() - a.timestamp < 600000);
277
- if (recentAnomalies.length > 10) return 'critical';
278
- if (recentAnomalies.length > 5) return 'high';
279
- if (recentAnomalies.length > 2) return 'medium';
280
- if (recentAnomalies.length > 0) return 'low';
281
- return 'normal';
282
- }
283
- }
284
-
285
- // =========================================================================
286
- // EXPORTS
287
- // =========================================================================
288
-
289
- module.exports = { BehaviorProfile, mean, stdDev, isAnomaly };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Agent Behavior Profiling (v3.0)
5
+ *
6
+ * Establishes baselines for normal agent behavior and detects anomalies
7
+ * that may indicate compromise, drift, or attack influence.
8
+ *
9
+ * Tracks: response patterns, tool usage, topic distribution, timing,
10
+ * output length, sentiment shifts, and more.
11
+ *
12
+ * All processing runs locally — no data ever leaves your environment.
13
+ */
14
+
15
+ // =========================================================================
16
+ // STATISTICAL HELPERS
17
+ // =========================================================================
18
+
19
+ /**
20
+ * Calculate mean of an array.
21
+ * @param {number[]} arr
22
+ * @returns {number}
23
+ */
24
+ function mean(arr) {
25
+ return arr.length > 0 ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;
26
+ }
27
+
28
+ /**
29
+ * Calculate standard deviation.
30
+ * @param {number[]} arr
31
+ * @returns {number}
32
+ */
33
+ function stdDev(arr) {
34
+ if (arr.length < 2) return 0;
35
+ const m = mean(arr);
36
+ const squaredDiffs = arr.map(x => (x - m) ** 2);
37
+ return Math.sqrt(squaredDiffs.reduce((a, b) => a + b, 0) / (arr.length - 1));
38
+ }
39
+
40
+ /**
41
+ * Check if a value is anomalous (beyond N standard deviations from mean).
42
+ * @param {number} value
43
+ * @param {number} m - Mean.
44
+ * @param {number} sd - Standard deviation.
45
+ * @param {number} [threshold=2] - Number of standard deviations.
46
+ * @returns {boolean}
47
+ */
48
+ function isAnomaly(value, m, sd, threshold = 2) {
49
+ if (sd === 0) return value !== m;
50
+ return Math.abs(value - m) > threshold * sd;
51
+ }
52
+
53
+ // =========================================================================
54
+ // BEHAVIOR PROFILE
55
+ // =========================================================================
56
+
57
+ /**
58
+ * Maintains a statistical profile of agent behavior.
59
+ */
60
+ class BehaviorProfile {
61
+ /**
62
+ * @param {object} [options]
63
+ * @param {number} [options.windowSize=200] - Number of observations to maintain.
64
+ * @param {number} [options.learningPeriod=20] - Observations before anomaly detection activates.
65
+ * @param {number} [options.anomalyThreshold=2.5] - Standard deviations for anomaly detection.
66
+ */
67
+ constructor(options = {}) {
68
+ this.windowSize = options.windowSize || 200;
69
+ this.learningPeriod = options.learningPeriod || 20;
70
+ this.anomalyThreshold = options.anomalyThreshold || 2.5;
71
+
72
+ this._metrics = {
73
+ responseLength: [],
74
+ responseTime: [],
75
+ toolCallCount: [],
76
+ threatScore: [],
77
+ topicEntropy: []
78
+ };
79
+
80
+ this._toolUsage = {};
81
+ this._topicDistribution = {};
82
+ this._totalObservations = 0;
83
+ this._anomalies = [];
84
+
85
+ console.log('[Agent Shield] BehaviorProfile initialized (windowSize: %d, learningPeriod: %d)', this.windowSize, this.learningPeriod);
86
+ }
87
+
88
+ /**
89
+ * Record an observation of agent behavior.
90
+ *
91
+ * @param {object} observation
92
+ * @param {number} [observation.responseLength] - Length of agent response.
93
+ * @param {number} [observation.responseTimeMs] - Time taken to respond.
94
+ * @param {string[]} [observation.toolsCalled] - Tools used in this turn.
95
+ * @param {number} [observation.threatScore] - Threat score from scanning.
96
+ * @param {string} [observation.topic] - Detected topic/category.
97
+ * @returns {object} { anomalies: Array, isLearning: boolean }
98
+ */
99
+ record(observation) {
100
+ this._totalObservations++;
101
+
102
+ // Record metrics
103
+ if (observation.responseLength !== undefined) {
104
+ this._addMetric('responseLength', observation.responseLength);
105
+ }
106
+ if (observation.responseTimeMs !== undefined) {
107
+ this._addMetric('responseTime', observation.responseTimeMs);
108
+ }
109
+ if (observation.toolsCalled) {
110
+ this._addMetric('toolCallCount', observation.toolsCalled.length);
111
+ for (const tool of observation.toolsCalled) {
112
+ this._toolUsage[tool] = (this._toolUsage[tool] || 0) + 1;
113
+ }
114
+ }
115
+ if (observation.threatScore !== undefined) {
116
+ this._addMetric('threatScore', observation.threatScore);
117
+ }
118
+ if (observation.topic) {
119
+ this._topicDistribution[observation.topic] = (this._topicDistribution[observation.topic] || 0) + 1;
120
+ }
121
+
122
+ // Check for anomalies (only after learning period)
123
+ const isLearning = this._totalObservations < this.learningPeriod;
124
+ const anomalies = isLearning ? [] : this._detectAnomalies(observation);
125
+
126
+ if (anomalies.length > 0) {
127
+ this._anomalies.push({
128
+ timestamp: Date.now(),
129
+ observation: this._totalObservations,
130
+ anomalies
131
+ });
132
+ // Prevent unbounded growth of anomaly history
133
+ if (this._anomalies.length > this.windowSize * 5) {
134
+ this._anomalies = this._anomalies.slice(-this.windowSize * 2);
135
+ }
136
+ }
137
+
138
+ return { anomalies, isLearning };
139
+ }
140
+
141
+ /**
142
+ * Get the current behavior baseline.
143
+ * @returns {object}
144
+ */
145
+ getBaseline() {
146
+ const baseline = {};
147
+ for (const [metric, values] of Object.entries(this._metrics)) {
148
+ if (values.length > 0) {
149
+ let min = values[0];
150
+ let max = values[0];
151
+ for (let i = 1; i < values.length; i++) {
152
+ if (values[i] < min) min = values[i];
153
+ if (values[i] > max) max = values[i];
154
+ }
155
+ baseline[metric] = {
156
+ mean: Math.round(mean(values) * 100) / 100,
157
+ stdDev: Math.round(stdDev(values) * 100) / 100,
158
+ min,
159
+ max,
160
+ samples: values.length
161
+ };
162
+ }
163
+ }
164
+ return baseline;
165
+ }
166
+
167
+ /**
168
+ * Get a full behavior report.
169
+ * @returns {object}
170
+ */
171
+ getReport() {
172
+ return {
173
+ totalObservations: this._totalObservations,
174
+ isLearning: this._totalObservations < this.learningPeriod,
175
+ baseline: this.getBaseline(),
176
+ toolUsage: { ...this._toolUsage },
177
+ topicDistribution: { ...this._topicDistribution },
178
+ anomalyCount: this._anomalies.length,
179
+ recentAnomalies: this._anomalies.slice(-10),
180
+ riskLevel: this._calculateRiskLevel()
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Check if the agent appears to be behaving normally.
186
+ * @returns {object} { normal: boolean, riskLevel, concerns: string[] }
187
+ */
188
+ healthCheck() {
189
+ const concerns = [];
190
+ const report = this.getReport();
191
+
192
+ if (report.isLearning) {
193
+ return { normal: true, riskLevel: 'unknown', concerns: ['Still in learning period.'] };
194
+ }
195
+
196
+ // Check for sudden tool usage changes
197
+ const totalToolCalls = Object.values(this._toolUsage).reduce((a, b) => a + b, 0);
198
+ if (totalToolCalls > 0) {
199
+ for (const [tool, count] of Object.entries(this._toolUsage)) {
200
+ const ratio = count / totalToolCalls;
201
+ if (ratio > 0.7 && totalToolCalls > 10) {
202
+ concerns.push(`Tool "${tool}" dominates usage at ${(ratio * 100).toFixed(0)}%.`);
203
+ }
204
+ }
205
+ }
206
+
207
+ // Check for high threat score trend
208
+ const recentThreats = this._metrics.threatScore.slice(-20);
209
+ if (recentThreats.length >= 5 && mean(recentThreats) > 0.5) {
210
+ concerns.push('Recent threat scores are elevated.');
211
+ }
212
+
213
+ // Check for anomaly frequency
214
+ const recentAnomalies = this._anomalies.filter(a => Date.now() - a.timestamp < 300000);
215
+ if (recentAnomalies.length > 5) {
216
+ concerns.push(`${recentAnomalies.length} anomalies in the last 5 minutes.`);
217
+ }
218
+
219
+ return {
220
+ normal: concerns.length === 0,
221
+ riskLevel: report.riskLevel,
222
+ concerns
223
+ };
224
+ }
225
+
226
+ /** Reset the profile. */
227
+ reset() {
228
+ for (const key of Object.keys(this._metrics)) {
229
+ this._metrics[key] = [];
230
+ }
231
+ this._toolUsage = {};
232
+ this._topicDistribution = {};
233
+ this._totalObservations = 0;
234
+ this._anomalies = [];
235
+ }
236
+
237
+ /** @private */
238
+ _addMetric(name, value) {
239
+ if (!this._metrics[name]) this._metrics[name] = [];
240
+ this._metrics[name].push(value);
241
+ if (this._metrics[name].length > this.windowSize) {
242
+ this._metrics[name].shift();
243
+ }
244
+ }
245
+
246
+ /** @private */
247
+ _detectAnomalies(observation) {
248
+ const anomalies = [];
249
+
250
+ const checks = [
251
+ { metric: 'responseLength', value: observation.responseLength, label: 'Response length' },
252
+ { metric: 'responseTime', value: observation.responseTimeMs, label: 'Response time' },
253
+ { metric: 'toolCallCount', value: observation.toolsCalled ? observation.toolsCalled.length : undefined, label: 'Tool call count' },
254
+ { metric: 'threatScore', value: observation.threatScore, label: 'Threat score' }
255
+ ];
256
+
257
+ for (const check of checks) {
258
+ if (check.value === undefined) continue;
259
+
260
+ const values = this._metrics[check.metric];
261
+ if (values.length < this.learningPeriod) continue;
262
+
263
+ const m = mean(values.slice(0, -1)); // Exclude current observation
264
+ const sd = stdDev(values.slice(0, -1));
265
+
266
+ if (isAnomaly(check.value, m, sd, this.anomalyThreshold)) {
267
+ const zScore = sd > 0 ? Math.round(((check.value - m) / sd) * 100) / 100 : 0;
268
+ anomalies.push({
269
+ metric: check.metric,
270
+ label: check.label,
271
+ value: check.value,
272
+ expected: { mean: Math.round(m * 100) / 100, stdDev: Math.round(sd * 100) / 100 },
273
+ zScore,
274
+ direction: check.value > m ? 'above' : 'below'
275
+ });
276
+ }
277
+ }
278
+
279
+ return anomalies;
280
+ }
281
+
282
+ /** @private */
283
+ _calculateRiskLevel() {
284
+ if (this._totalObservations < this.learningPeriod) return 'unknown';
285
+
286
+ const recentAnomalies = this._anomalies.filter(a => Date.now() - a.timestamp < 600000);
287
+ if (recentAnomalies.length > 10) return 'critical';
288
+ if (recentAnomalies.length > 5) return 'high';
289
+ if (recentAnomalies.length > 2) return 'medium';
290
+ if (recentAnomalies.length > 0) return 'low';
291
+ return 'normal';
292
+ }
293
+ }
294
+
295
+ // =========================================================================
296
+ // EXPORTS
297
+ // =========================================================================
298
+
299
+ module.exports = { BehaviorProfile, mean, stdDev, isAnomaly };