@mnemopay/sdk 0.9.3 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/anomaly.d.ts +240 -0
- package/dist/anomaly.d.ts.map +1 -0
- package/dist/anomaly.js +427 -0
- package/dist/anomaly.js.map +1 -0
- package/dist/behavioral.d.ts +253 -0
- package/dist/behavioral.d.ts.map +1 -0
- package/dist/behavioral.js +550 -0
- package/dist/behavioral.js.map +1 -0
- package/dist/fico.d.ts +136 -0
- package/dist/fico.d.ts.map +1 -0
- package/dist/fico.js +424 -0
- package/dist/fico.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/integrity.d.ts +165 -0
- package/dist/integrity.d.ts.map +1 -0
- package/dist/integrity.js +395 -0
- package/dist/integrity.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming Anomaly Detection — Real-Time Agent Behavior Monitoring
|
|
3
|
+
*
|
|
4
|
+
* Two complementary systems:
|
|
5
|
+
*
|
|
6
|
+
* 1. EWMA (Exponentially Weighted Moving Average) Detector
|
|
7
|
+
* - Tracks running mean and variance of any metric stream
|
|
8
|
+
* - Alerts when value exceeds k standard deviations from moving average
|
|
9
|
+
* - Formula: mu_t = alpha * x_t + (1 - alpha) * mu_{t-1}
|
|
10
|
+
* - O(1) per update, no windowing needed, no buffer overflow
|
|
11
|
+
*
|
|
12
|
+
* 2. Behavioral Fingerprint Monitor
|
|
13
|
+
* - Tracks multi-dimensional behavioral profile of each agent
|
|
14
|
+
* - Detects sudden behavioral shifts (hijacked agent, credential theft)
|
|
15
|
+
* - Uses Mahalanobis distance for multivariate anomaly scoring
|
|
16
|
+
* - Gartner 2026: behavioral fingerprinting reduces impersonation 70%
|
|
17
|
+
*
|
|
18
|
+
* 3. Canary Transaction System
|
|
19
|
+
* - Plants honeypot transactions that should never be accessed
|
|
20
|
+
* - If an agent touches a canary, it's compromised
|
|
21
|
+
* - Inspired by Snare (GitHub) canary credential framework
|
|
22
|
+
*
|
|
23
|
+
* References:
|
|
24
|
+
* - Roberts, S.W. (1959). "Control Chart Tests Based on EWMA"
|
|
25
|
+
* - Lucas & Saccucci (1990). "EWMA Control Chart Properties"
|
|
26
|
+
* - Mahalanobis (1936). "On the Generalized Distance in Statistics"
|
|
27
|
+
* - MnemoPay Master Strategy, Part 2.1 — EWMA + behavioral fingerprinting
|
|
28
|
+
*/
|
|
29
|
+
export interface EWMAState {
|
|
30
|
+
/** Running mean */
|
|
31
|
+
mean: number;
|
|
32
|
+
/** Running variance */
|
|
33
|
+
variance: number;
|
|
34
|
+
/** Running standard deviation */
|
|
35
|
+
stdDev: number;
|
|
36
|
+
/** Number of observations */
|
|
37
|
+
count: number;
|
|
38
|
+
/** Last observed value */
|
|
39
|
+
lastValue: number;
|
|
40
|
+
/** Whether the detector has enough data to be reliable */
|
|
41
|
+
warmedUp: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface EWMAAlert {
|
|
44
|
+
/** Whether this observation is anomalous */
|
|
45
|
+
anomaly: boolean;
|
|
46
|
+
/** Current value */
|
|
47
|
+
value: number;
|
|
48
|
+
/** Current mean */
|
|
49
|
+
mean: number;
|
|
50
|
+
/** Current std dev */
|
|
51
|
+
stdDev: number;
|
|
52
|
+
/** Z-score: how many std devs from mean */
|
|
53
|
+
zScore: number;
|
|
54
|
+
/** Alert severity */
|
|
55
|
+
severity: "none" | "warning" | "critical";
|
|
56
|
+
/** Timestamp */
|
|
57
|
+
timestamp: number;
|
|
58
|
+
}
|
|
59
|
+
export interface BehaviorFingerprint {
|
|
60
|
+
/** Agent ID */
|
|
61
|
+
agentId: string;
|
|
62
|
+
/** Per-feature EWMA trackers */
|
|
63
|
+
features: Record<string, EWMAState>;
|
|
64
|
+
/** Number of observations used to build fingerprint */
|
|
65
|
+
observations: number;
|
|
66
|
+
/** Whether the fingerprint is established (>= warmupPeriod observations) */
|
|
67
|
+
established: boolean;
|
|
68
|
+
/** Last observation timestamp */
|
|
69
|
+
lastObserved: number;
|
|
70
|
+
}
|
|
71
|
+
export interface HijackDetection {
|
|
72
|
+
/** Whether hijack is suspected */
|
|
73
|
+
suspected: boolean;
|
|
74
|
+
/** Overall anomaly score (0-1, higher = more anomalous) */
|
|
75
|
+
anomalyScore: number;
|
|
76
|
+
/** Number of features that are anomalous */
|
|
77
|
+
anomalousFeatures: number;
|
|
78
|
+
/** Total features tracked */
|
|
79
|
+
totalFeatures: number;
|
|
80
|
+
/** Per-feature anomaly details */
|
|
81
|
+
details: Record<string, {
|
|
82
|
+
zScore: number;
|
|
83
|
+
anomalous: boolean;
|
|
84
|
+
}>;
|
|
85
|
+
/** Severity assessment */
|
|
86
|
+
severity: "none" | "low" | "medium" | "high" | "critical";
|
|
87
|
+
/** Recommendation */
|
|
88
|
+
recommendation: string;
|
|
89
|
+
}
|
|
90
|
+
export interface CanaryTransaction {
|
|
91
|
+
/** Canary ID */
|
|
92
|
+
id: string;
|
|
93
|
+
/** Fake amount (should never be accessed) */
|
|
94
|
+
amount: number;
|
|
95
|
+
/** Canary type */
|
|
96
|
+
type: "transaction" | "memory" | "credential";
|
|
97
|
+
/** When the canary was planted */
|
|
98
|
+
plantedAt: number;
|
|
99
|
+
/** Whether the canary has been triggered */
|
|
100
|
+
triggered: boolean;
|
|
101
|
+
/** When triggered (if applicable) */
|
|
102
|
+
triggeredAt?: number;
|
|
103
|
+
/** What agent triggered it */
|
|
104
|
+
triggeredBy?: string;
|
|
105
|
+
}
|
|
106
|
+
export interface CanaryAlert {
|
|
107
|
+
/** The triggered canary */
|
|
108
|
+
canary: CanaryTransaction;
|
|
109
|
+
/** Agent that triggered it */
|
|
110
|
+
agentId: string;
|
|
111
|
+
/** Severity: always critical (canary trigger = compromise) */
|
|
112
|
+
severity: "critical";
|
|
113
|
+
/** Message */
|
|
114
|
+
message: string;
|
|
115
|
+
/** Timestamp */
|
|
116
|
+
timestamp: number;
|
|
117
|
+
}
|
|
118
|
+
export interface AnomalyConfig {
|
|
119
|
+
/** EWMA smoothing factor (0-1). Higher = more reactive. Default 0.15 */
|
|
120
|
+
alpha: number;
|
|
121
|
+
/** Z-score threshold for warning. Default 2.5 */
|
|
122
|
+
warningThreshold: number;
|
|
123
|
+
/** Z-score threshold for critical. Default 3.5 */
|
|
124
|
+
criticalThreshold: number;
|
|
125
|
+
/** Minimum observations before alerting. Default 10 */
|
|
126
|
+
warmupPeriod: number;
|
|
127
|
+
/** Features to track for behavioral fingerprinting */
|
|
128
|
+
trackedFeatures: string[];
|
|
129
|
+
/** Fraction of anomalous features to flag hijack. Default 0.4 */
|
|
130
|
+
hijackFeatureThreshold: number;
|
|
131
|
+
/** Max canaries per agent. Default 5 */
|
|
132
|
+
maxCanaries: number;
|
|
133
|
+
}
|
|
134
|
+
export declare const DEFAULT_ANOMALY_CONFIG: AnomalyConfig;
|
|
135
|
+
export declare class EWMADetector {
|
|
136
|
+
private mean;
|
|
137
|
+
private variance;
|
|
138
|
+
private count;
|
|
139
|
+
private lastValue;
|
|
140
|
+
private readonly alpha;
|
|
141
|
+
private readonly warningK;
|
|
142
|
+
private readonly criticalK;
|
|
143
|
+
private readonly warmup;
|
|
144
|
+
constructor(alpha?: number, warningK?: number, criticalK?: number, warmup?: number);
|
|
145
|
+
/**
|
|
146
|
+
* Update the detector with a new observation.
|
|
147
|
+
* Returns anomaly assessment.
|
|
148
|
+
*
|
|
149
|
+
* EWMA formulas:
|
|
150
|
+
* mu_t = alpha * x_t + (1 - alpha) * mu_{t-1}
|
|
151
|
+
* sigma_t^2 = alpha * (x_t - mu_t)^2 + (1 - alpha) * sigma_{t-1}^2
|
|
152
|
+
* Alert when: |x_t - mu_t| > k * sigma_t
|
|
153
|
+
*/
|
|
154
|
+
update(value: number): EWMAAlert;
|
|
155
|
+
private _makeAlert;
|
|
156
|
+
/** Get current state */
|
|
157
|
+
getState(): EWMAState;
|
|
158
|
+
/** Reset the detector */
|
|
159
|
+
reset(): void;
|
|
160
|
+
/** Serialize for persistence */
|
|
161
|
+
serialize(): {
|
|
162
|
+
mean: number;
|
|
163
|
+
variance: number;
|
|
164
|
+
count: number;
|
|
165
|
+
lastValue: number;
|
|
166
|
+
};
|
|
167
|
+
/** Restore from serialized state */
|
|
168
|
+
restore(state: {
|
|
169
|
+
mean: number;
|
|
170
|
+
variance: number;
|
|
171
|
+
count: number;
|
|
172
|
+
lastValue: number;
|
|
173
|
+
}): void;
|
|
174
|
+
}
|
|
175
|
+
export declare class BehaviorMonitor {
|
|
176
|
+
private fingerprints;
|
|
177
|
+
private observationCounts;
|
|
178
|
+
private lastObserved;
|
|
179
|
+
readonly config: AnomalyConfig;
|
|
180
|
+
constructor(config?: Partial<AnomalyConfig>);
|
|
181
|
+
/**
|
|
182
|
+
* Observe agent behavior. Updates the behavioral fingerprint.
|
|
183
|
+
* Returns hijack detection result.
|
|
184
|
+
*/
|
|
185
|
+
observe(agentId: string, features: Record<string, number>): HijackDetection;
|
|
186
|
+
/**
|
|
187
|
+
* Get the behavioral fingerprint for an agent.
|
|
188
|
+
*/
|
|
189
|
+
getFingerprint(agentId: string): BehaviorFingerprint | null;
|
|
190
|
+
/**
|
|
191
|
+
* Remove an agent's fingerprint (on agent deletion).
|
|
192
|
+
*/
|
|
193
|
+
removeAgent(agentId: string): boolean;
|
|
194
|
+
/** Number of agents being monitored */
|
|
195
|
+
get agentCount(): number;
|
|
196
|
+
/** Serialize for persistence */
|
|
197
|
+
serialize(): Record<string, {
|
|
198
|
+
features: Record<string, ReturnType<EWMADetector["serialize"]>>;
|
|
199
|
+
count: number;
|
|
200
|
+
lastObserved: number;
|
|
201
|
+
}>;
|
|
202
|
+
/** Deserialize with validation */
|
|
203
|
+
static deserialize(data: Record<string, any>, config?: Partial<AnomalyConfig>): BehaviorMonitor;
|
|
204
|
+
}
|
|
205
|
+
export declare class CanarySystem {
|
|
206
|
+
private canaries;
|
|
207
|
+
private alerts;
|
|
208
|
+
private readonly maxCanaries;
|
|
209
|
+
static readonly MAX_ALERTS = 100;
|
|
210
|
+
constructor(maxCanaries?: number);
|
|
211
|
+
/**
|
|
212
|
+
* Plant a canary transaction.
|
|
213
|
+
* Returns the canary ID. Store this — never expose to the agent.
|
|
214
|
+
*/
|
|
215
|
+
plant(type?: CanaryTransaction["type"]): CanaryTransaction;
|
|
216
|
+
/**
|
|
217
|
+
* Check if an ID matches a canary. If yes, the agent is compromised.
|
|
218
|
+
* This should be called on every transaction/memory access.
|
|
219
|
+
*/
|
|
220
|
+
check(id: string, agentId: string): CanaryAlert | null;
|
|
221
|
+
/** Check if any ID in a list matches a canary */
|
|
222
|
+
checkBatch(ids: string[], agentId: string): CanaryAlert[];
|
|
223
|
+
/** Get all alerts */
|
|
224
|
+
getAlerts(): CanaryAlert[];
|
|
225
|
+
/** Get all active (untriggered) canaries */
|
|
226
|
+
getActiveCanaries(): CanaryTransaction[];
|
|
227
|
+
/** Check if a specific canary ID exists (for internal use only) */
|
|
228
|
+
isCanary(id: string): boolean;
|
|
229
|
+
/** Serialize for persistence */
|
|
230
|
+
serialize(): {
|
|
231
|
+
canaries: CanaryTransaction[];
|
|
232
|
+
alerts: CanaryAlert[];
|
|
233
|
+
};
|
|
234
|
+
/** Deserialize with validation */
|
|
235
|
+
static deserialize(data: {
|
|
236
|
+
canaries?: CanaryTransaction[];
|
|
237
|
+
alerts?: CanaryAlert[];
|
|
238
|
+
}, maxCanaries?: number): CanarySystem;
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=anomaly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anomaly.d.ts","sourceRoot":"","sources":["../src/anomaly.ts"],"names":[],"mappings":"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"}
|
package/dist/anomaly.js
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Streaming Anomaly Detection — Real-Time Agent Behavior Monitoring
|
|
4
|
+
*
|
|
5
|
+
* Two complementary systems:
|
|
6
|
+
*
|
|
7
|
+
* 1. EWMA (Exponentially Weighted Moving Average) Detector
|
|
8
|
+
* - Tracks running mean and variance of any metric stream
|
|
9
|
+
* - Alerts when value exceeds k standard deviations from moving average
|
|
10
|
+
* - Formula: mu_t = alpha * x_t + (1 - alpha) * mu_{t-1}
|
|
11
|
+
* - O(1) per update, no windowing needed, no buffer overflow
|
|
12
|
+
*
|
|
13
|
+
* 2. Behavioral Fingerprint Monitor
|
|
14
|
+
* - Tracks multi-dimensional behavioral profile of each agent
|
|
15
|
+
* - Detects sudden behavioral shifts (hijacked agent, credential theft)
|
|
16
|
+
* - Uses Mahalanobis distance for multivariate anomaly scoring
|
|
17
|
+
* - Gartner 2026: behavioral fingerprinting reduces impersonation 70%
|
|
18
|
+
*
|
|
19
|
+
* 3. Canary Transaction System
|
|
20
|
+
* - Plants honeypot transactions that should never be accessed
|
|
21
|
+
* - If an agent touches a canary, it's compromised
|
|
22
|
+
* - Inspired by Snare (GitHub) canary credential framework
|
|
23
|
+
*
|
|
24
|
+
* References:
|
|
25
|
+
* - Roberts, S.W. (1959). "Control Chart Tests Based on EWMA"
|
|
26
|
+
* - Lucas & Saccucci (1990). "EWMA Control Chart Properties"
|
|
27
|
+
* - Mahalanobis (1936). "On the Generalized Distance in Statistics"
|
|
28
|
+
* - MnemoPay Master Strategy, Part 2.1 — EWMA + behavioral fingerprinting
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.CanarySystem = exports.BehaviorMonitor = exports.EWMADetector = exports.DEFAULT_ANOMALY_CONFIG = void 0;
|
|
32
|
+
exports.DEFAULT_ANOMALY_CONFIG = {
|
|
33
|
+
alpha: 0.15,
|
|
34
|
+
warningThreshold: 2.5,
|
|
35
|
+
criticalThreshold: 3.5,
|
|
36
|
+
warmupPeriod: 10,
|
|
37
|
+
trackedFeatures: [
|
|
38
|
+
"amount", "hourOfDay", "timeBetweenTx", "chargesPerHour",
|
|
39
|
+
"avgAmount", "amountVariance", "counterpartyCount", "memoryOpsPerTx",
|
|
40
|
+
],
|
|
41
|
+
hijackFeatureThreshold: 0.4,
|
|
42
|
+
maxCanaries: 5,
|
|
43
|
+
};
|
|
44
|
+
// ─── EWMA Detector ──────────────────────────────────────────────────────────
|
|
45
|
+
class EWMADetector {
|
|
46
|
+
mean = 0;
|
|
47
|
+
variance = 0;
|
|
48
|
+
count = 0;
|
|
49
|
+
lastValue = 0;
|
|
50
|
+
alpha;
|
|
51
|
+
warningK;
|
|
52
|
+
criticalK;
|
|
53
|
+
warmup;
|
|
54
|
+
constructor(alpha = 0.15, warningK = 2.5, criticalK = 3.5, warmup = 10) {
|
|
55
|
+
if (alpha <= 0 || alpha >= 1)
|
|
56
|
+
throw new Error("Alpha must be in (0, 1)");
|
|
57
|
+
if (warningK <= 0)
|
|
58
|
+
throw new Error("Warning threshold must be positive");
|
|
59
|
+
if (criticalK <= warningK)
|
|
60
|
+
throw new Error("Critical threshold must exceed warning threshold");
|
|
61
|
+
if (warmup < 1)
|
|
62
|
+
throw new Error("Warmup period must be at least 1");
|
|
63
|
+
this.alpha = alpha;
|
|
64
|
+
this.warningK = warningK;
|
|
65
|
+
this.criticalK = criticalK;
|
|
66
|
+
this.warmup = warmup;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Update the detector with a new observation.
|
|
70
|
+
* Returns anomaly assessment.
|
|
71
|
+
*
|
|
72
|
+
* EWMA formulas:
|
|
73
|
+
* mu_t = alpha * x_t + (1 - alpha) * mu_{t-1}
|
|
74
|
+
* sigma_t^2 = alpha * (x_t - mu_t)^2 + (1 - alpha) * sigma_{t-1}^2
|
|
75
|
+
* Alert when: |x_t - mu_t| > k * sigma_t
|
|
76
|
+
*/
|
|
77
|
+
update(value) {
|
|
78
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
79
|
+
throw new Error("EWMA value must be a finite number");
|
|
80
|
+
}
|
|
81
|
+
this.count++;
|
|
82
|
+
this.lastValue = value;
|
|
83
|
+
if (this.count === 1) {
|
|
84
|
+
// First observation: initialize
|
|
85
|
+
this.mean = value;
|
|
86
|
+
this.variance = 0;
|
|
87
|
+
return this._makeAlert(value, 0, "none");
|
|
88
|
+
}
|
|
89
|
+
// Update mean (EWMA)
|
|
90
|
+
const prevMean = this.mean;
|
|
91
|
+
this.mean = this.alpha * value + (1 - this.alpha) * prevMean;
|
|
92
|
+
// Update variance (EWMA of squared deviations)
|
|
93
|
+
const deviation = value - this.mean;
|
|
94
|
+
this.variance = this.alpha * (deviation * deviation) + (1 - this.alpha) * this.variance;
|
|
95
|
+
// Z-score
|
|
96
|
+
const stdDev = Math.sqrt(this.variance);
|
|
97
|
+
const zScore = stdDev > 1e-10 ? Math.abs(value - this.mean) / stdDev : 0;
|
|
98
|
+
// Don't alert during warmup
|
|
99
|
+
if (this.count < this.warmup) {
|
|
100
|
+
return this._makeAlert(value, zScore, "none");
|
|
101
|
+
}
|
|
102
|
+
// Classify severity
|
|
103
|
+
let severity = "none";
|
|
104
|
+
if (zScore >= this.criticalK)
|
|
105
|
+
severity = "critical";
|
|
106
|
+
else if (zScore >= this.warningK)
|
|
107
|
+
severity = "warning";
|
|
108
|
+
return this._makeAlert(value, zScore, severity);
|
|
109
|
+
}
|
|
110
|
+
_makeAlert(value, zScore, severity) {
|
|
111
|
+
return {
|
|
112
|
+
anomaly: severity !== "none",
|
|
113
|
+
value,
|
|
114
|
+
mean: Math.round(this.mean * 10000) / 10000,
|
|
115
|
+
stdDev: Math.round(Math.sqrt(this.variance) * 10000) / 10000,
|
|
116
|
+
zScore: Math.round(zScore * 1000) / 1000,
|
|
117
|
+
severity,
|
|
118
|
+
timestamp: Date.now(),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Get current state */
|
|
122
|
+
getState() {
|
|
123
|
+
return {
|
|
124
|
+
mean: this.mean,
|
|
125
|
+
variance: this.variance,
|
|
126
|
+
stdDev: Math.sqrt(this.variance),
|
|
127
|
+
count: this.count,
|
|
128
|
+
lastValue: this.lastValue,
|
|
129
|
+
warmedUp: this.count >= this.warmup,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Reset the detector */
|
|
133
|
+
reset() {
|
|
134
|
+
this.mean = 0;
|
|
135
|
+
this.variance = 0;
|
|
136
|
+
this.count = 0;
|
|
137
|
+
this.lastValue = 0;
|
|
138
|
+
}
|
|
139
|
+
/** Serialize for persistence */
|
|
140
|
+
serialize() {
|
|
141
|
+
return { mean: this.mean, variance: this.variance, count: this.count, lastValue: this.lastValue };
|
|
142
|
+
}
|
|
143
|
+
/** Restore from serialized state */
|
|
144
|
+
restore(state) {
|
|
145
|
+
if (typeof state.mean !== "number" || !Number.isFinite(state.mean))
|
|
146
|
+
throw new Error("Invalid mean");
|
|
147
|
+
if (typeof state.variance !== "number" || !Number.isFinite(state.variance) || state.variance < 0)
|
|
148
|
+
throw new Error("Invalid variance");
|
|
149
|
+
if (typeof state.count !== "number" || state.count < 0)
|
|
150
|
+
throw new Error("Invalid count");
|
|
151
|
+
this.mean = state.mean;
|
|
152
|
+
this.variance = state.variance;
|
|
153
|
+
this.count = Math.floor(state.count);
|
|
154
|
+
this.lastValue = state.lastValue ?? 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
exports.EWMADetector = EWMADetector;
|
|
158
|
+
// ─── Behavioral Fingerprint Monitor ─────────────────────────────────────────
|
|
159
|
+
class BehaviorMonitor {
|
|
160
|
+
fingerprints = new Map();
|
|
161
|
+
observationCounts = new Map();
|
|
162
|
+
lastObserved = new Map();
|
|
163
|
+
config;
|
|
164
|
+
constructor(config) {
|
|
165
|
+
this.config = { ...exports.DEFAULT_ANOMALY_CONFIG, ...config };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Observe agent behavior. Updates the behavioral fingerprint.
|
|
169
|
+
* Returns hijack detection result.
|
|
170
|
+
*/
|
|
171
|
+
observe(agentId, features) {
|
|
172
|
+
if (!agentId || typeof agentId !== "string")
|
|
173
|
+
throw new Error("agentId is required");
|
|
174
|
+
if (!features || typeof features !== "object")
|
|
175
|
+
throw new Error("features must be an object");
|
|
176
|
+
// Initialize fingerprint for new agents
|
|
177
|
+
if (!this.fingerprints.has(agentId)) {
|
|
178
|
+
this.fingerprints.set(agentId, new Map());
|
|
179
|
+
this.observationCounts.set(agentId, 0);
|
|
180
|
+
}
|
|
181
|
+
const detectors = this.fingerprints.get(agentId);
|
|
182
|
+
const count = (this.observationCounts.get(agentId) ?? 0) + 1;
|
|
183
|
+
this.observationCounts.set(agentId, count);
|
|
184
|
+
this.lastObserved.set(agentId, Date.now());
|
|
185
|
+
const details = {};
|
|
186
|
+
let anomalousCount = 0;
|
|
187
|
+
let totalFeatures = 0;
|
|
188
|
+
for (const feature of this.config.trackedFeatures) {
|
|
189
|
+
const value = features[feature];
|
|
190
|
+
if (value === undefined || typeof value !== "number" || !Number.isFinite(value))
|
|
191
|
+
continue;
|
|
192
|
+
totalFeatures++;
|
|
193
|
+
// Create detector for this feature if needed
|
|
194
|
+
if (!detectors.has(feature)) {
|
|
195
|
+
detectors.set(feature, new EWMADetector(this.config.alpha, this.config.warningThreshold, this.config.criticalThreshold, this.config.warmupPeriod));
|
|
196
|
+
}
|
|
197
|
+
const alert = detectors.get(feature).update(value);
|
|
198
|
+
details[feature] = { zScore: alert.zScore, anomalous: alert.anomaly };
|
|
199
|
+
if (alert.anomaly)
|
|
200
|
+
anomalousCount++;
|
|
201
|
+
}
|
|
202
|
+
// Hijack scoring: fraction of anomalous features
|
|
203
|
+
const anomalyScore = totalFeatures > 0 ? anomalousCount / totalFeatures : 0;
|
|
204
|
+
const suspected = anomalyScore >= this.config.hijackFeatureThreshold && count >= this.config.warmupPeriod;
|
|
205
|
+
let severity = "none";
|
|
206
|
+
if (suspected && anomalyScore >= 0.8)
|
|
207
|
+
severity = "critical";
|
|
208
|
+
else if (suspected && anomalyScore >= 0.6)
|
|
209
|
+
severity = "high";
|
|
210
|
+
else if (suspected)
|
|
211
|
+
severity = "medium";
|
|
212
|
+
else if (anomalousCount > 0)
|
|
213
|
+
severity = "low";
|
|
214
|
+
let recommendation;
|
|
215
|
+
if (severity === "critical") {
|
|
216
|
+
recommendation = `CRITICAL: ${anomalousCount}/${totalFeatures} behavioral features deviant. Agent ${agentId} may be compromised. Recommended: suspend transactions, require re-authentication.`;
|
|
217
|
+
}
|
|
218
|
+
else if (severity === "high") {
|
|
219
|
+
recommendation = `HIGH RISK: ${anomalousCount}/${totalFeatures} features anomalous. Increase monitoring. Consider HITL approval for large transactions.`;
|
|
220
|
+
}
|
|
221
|
+
else if (severity === "medium") {
|
|
222
|
+
recommendation = `ELEVATED: Some behavioral deviation detected. Monitor closely.`;
|
|
223
|
+
}
|
|
224
|
+
else if (severity === "low") {
|
|
225
|
+
recommendation = `Minor deviations detected. Within acceptable range.`;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
recommendation = `Behavior consistent with established profile.`;
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
suspected,
|
|
232
|
+
anomalyScore: Math.round(anomalyScore * 1000) / 1000,
|
|
233
|
+
anomalousFeatures: anomalousCount,
|
|
234
|
+
totalFeatures,
|
|
235
|
+
details,
|
|
236
|
+
severity,
|
|
237
|
+
recommendation,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get the behavioral fingerprint for an agent.
|
|
242
|
+
*/
|
|
243
|
+
getFingerprint(agentId) {
|
|
244
|
+
const detectors = this.fingerprints.get(agentId);
|
|
245
|
+
if (!detectors)
|
|
246
|
+
return null;
|
|
247
|
+
const features = {};
|
|
248
|
+
for (const [name, detector] of detectors) {
|
|
249
|
+
features[name] = detector.getState();
|
|
250
|
+
}
|
|
251
|
+
const observations = this.observationCounts.get(agentId) ?? 0;
|
|
252
|
+
return {
|
|
253
|
+
agentId,
|
|
254
|
+
features,
|
|
255
|
+
observations,
|
|
256
|
+
established: observations >= this.config.warmupPeriod,
|
|
257
|
+
lastObserved: this.lastObserved.get(agentId) ?? 0,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Remove an agent's fingerprint (on agent deletion).
|
|
262
|
+
*/
|
|
263
|
+
removeAgent(agentId) {
|
|
264
|
+
const existed = this.fingerprints.has(agentId);
|
|
265
|
+
this.fingerprints.delete(agentId);
|
|
266
|
+
this.observationCounts.delete(agentId);
|
|
267
|
+
this.lastObserved.delete(agentId);
|
|
268
|
+
return existed;
|
|
269
|
+
}
|
|
270
|
+
/** Number of agents being monitored */
|
|
271
|
+
get agentCount() {
|
|
272
|
+
return this.fingerprints.size;
|
|
273
|
+
}
|
|
274
|
+
/** Serialize for persistence */
|
|
275
|
+
serialize() {
|
|
276
|
+
const result = {};
|
|
277
|
+
for (const [agentId, detectors] of this.fingerprints) {
|
|
278
|
+
const features = {};
|
|
279
|
+
for (const [name, detector] of detectors) {
|
|
280
|
+
features[name] = detector.serialize();
|
|
281
|
+
}
|
|
282
|
+
result[agentId] = {
|
|
283
|
+
features,
|
|
284
|
+
count: this.observationCounts.get(agentId) ?? 0,
|
|
285
|
+
lastObserved: this.lastObserved.get(agentId) ?? 0,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
/** Deserialize with validation */
|
|
291
|
+
static deserialize(data, config) {
|
|
292
|
+
const monitor = new BehaviorMonitor(config);
|
|
293
|
+
if (!data || typeof data !== "object")
|
|
294
|
+
return monitor;
|
|
295
|
+
for (const [agentId, agentData] of Object.entries(data)) {
|
|
296
|
+
if (!agentData || typeof agentData !== "object")
|
|
297
|
+
continue;
|
|
298
|
+
const detectors = new Map();
|
|
299
|
+
if (agentData.features && typeof agentData.features === "object") {
|
|
300
|
+
for (const [feature, state] of Object.entries(agentData.features)) {
|
|
301
|
+
try {
|
|
302
|
+
const detector = new EWMADetector(monitor.config.alpha, monitor.config.warningThreshold, monitor.config.criticalThreshold, monitor.config.warmupPeriod);
|
|
303
|
+
detector.restore(state);
|
|
304
|
+
detectors.set(feature, detector);
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
// Skip corrupted feature data
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
monitor.fingerprints.set(agentId, detectors);
|
|
312
|
+
monitor.observationCounts.set(agentId, typeof agentData.count === "number" ? agentData.count : 0);
|
|
313
|
+
monitor.lastObserved.set(agentId, typeof agentData.lastObserved === "number" ? agentData.lastObserved : 0);
|
|
314
|
+
}
|
|
315
|
+
return monitor;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
exports.BehaviorMonitor = BehaviorMonitor;
|
|
319
|
+
// ─── Canary Transaction System ──────────────────────────────────────────────
|
|
320
|
+
class CanarySystem {
|
|
321
|
+
canaries = new Map();
|
|
322
|
+
alerts = [];
|
|
323
|
+
maxCanaries;
|
|
324
|
+
static MAX_ALERTS = 100;
|
|
325
|
+
constructor(maxCanaries = 5) {
|
|
326
|
+
if (maxCanaries < 1 || maxCanaries > 50)
|
|
327
|
+
throw new Error("maxCanaries must be 1-50");
|
|
328
|
+
this.maxCanaries = maxCanaries;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Plant a canary transaction.
|
|
332
|
+
* Returns the canary ID. Store this — never expose to the agent.
|
|
333
|
+
*/
|
|
334
|
+
plant(type = "transaction") {
|
|
335
|
+
if (this.canaries.size >= this.maxCanaries) {
|
|
336
|
+
// Remove oldest untriggered canary
|
|
337
|
+
let oldest = null;
|
|
338
|
+
for (const c of this.canaries.values()) {
|
|
339
|
+
if (!c.triggered && (!oldest || c.plantedAt < oldest.plantedAt)) {
|
|
340
|
+
oldest = c;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (oldest)
|
|
344
|
+
this.canaries.delete(oldest.id);
|
|
345
|
+
}
|
|
346
|
+
const canary = {
|
|
347
|
+
id: `canary-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
348
|
+
amount: Math.round((Math.random() * 99 + 1) * 100) / 100, // $1-100
|
|
349
|
+
type,
|
|
350
|
+
plantedAt: Date.now(),
|
|
351
|
+
triggered: false,
|
|
352
|
+
};
|
|
353
|
+
this.canaries.set(canary.id, canary);
|
|
354
|
+
return canary;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Check if an ID matches a canary. If yes, the agent is compromised.
|
|
358
|
+
* This should be called on every transaction/memory access.
|
|
359
|
+
*/
|
|
360
|
+
check(id, agentId) {
|
|
361
|
+
const canary = this.canaries.get(id);
|
|
362
|
+
if (!canary)
|
|
363
|
+
return null;
|
|
364
|
+
// TRIGGERED — agent accessed a honeypot
|
|
365
|
+
canary.triggered = true;
|
|
366
|
+
canary.triggeredAt = Date.now();
|
|
367
|
+
canary.triggeredBy = agentId;
|
|
368
|
+
const alert = {
|
|
369
|
+
canary: { ...canary },
|
|
370
|
+
agentId,
|
|
371
|
+
severity: "critical",
|
|
372
|
+
message: `CANARY TRIGGERED: Agent ${agentId} accessed ${canary.type} canary "${canary.id}". This agent may be compromised. Canary was planted ${Math.round((Date.now() - canary.plantedAt) / 60000)} minutes ago.`,
|
|
373
|
+
timestamp: Date.now(),
|
|
374
|
+
};
|
|
375
|
+
this.alerts.push(alert);
|
|
376
|
+
if (this.alerts.length > CanarySystem.MAX_ALERTS) {
|
|
377
|
+
this.alerts.splice(0, this.alerts.length - CanarySystem.MAX_ALERTS);
|
|
378
|
+
}
|
|
379
|
+
return alert;
|
|
380
|
+
}
|
|
381
|
+
/** Check if any ID in a list matches a canary */
|
|
382
|
+
checkBatch(ids, agentId) {
|
|
383
|
+
const alerts = [];
|
|
384
|
+
for (const id of ids) {
|
|
385
|
+
const alert = this.check(id, agentId);
|
|
386
|
+
if (alert)
|
|
387
|
+
alerts.push(alert);
|
|
388
|
+
}
|
|
389
|
+
return alerts;
|
|
390
|
+
}
|
|
391
|
+
/** Get all alerts */
|
|
392
|
+
getAlerts() {
|
|
393
|
+
return [...this.alerts];
|
|
394
|
+
}
|
|
395
|
+
/** Get all active (untriggered) canaries */
|
|
396
|
+
getActiveCanaries() {
|
|
397
|
+
return Array.from(this.canaries.values()).filter(c => !c.triggered);
|
|
398
|
+
}
|
|
399
|
+
/** Check if a specific canary ID exists (for internal use only) */
|
|
400
|
+
isCanary(id) {
|
|
401
|
+
return this.canaries.has(id);
|
|
402
|
+
}
|
|
403
|
+
/** Serialize for persistence */
|
|
404
|
+
serialize() {
|
|
405
|
+
return {
|
|
406
|
+
canaries: Array.from(this.canaries.values()),
|
|
407
|
+
alerts: [...this.alerts],
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
/** Deserialize with validation */
|
|
411
|
+
static deserialize(data, maxCanaries) {
|
|
412
|
+
const system = new CanarySystem(maxCanaries);
|
|
413
|
+
if (Array.isArray(data.canaries)) {
|
|
414
|
+
for (const c of data.canaries) {
|
|
415
|
+
if (c.id && typeof c.amount === "number" && Number.isFinite(c.amount)) {
|
|
416
|
+
system.canaries.set(c.id, { ...c });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (Array.isArray(data.alerts)) {
|
|
421
|
+
system.alerts = data.alerts.slice(-CanarySystem.MAX_ALERTS);
|
|
422
|
+
}
|
|
423
|
+
return system;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
exports.CanarySystem = CanarySystem;
|
|
427
|
+
//# sourceMappingURL=anomaly.js.map
|