forked from Streampay-Org/StreamPay-Frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdetector.ts
More file actions
147 lines (129 loc) · 4.67 KB
/
Copy pathdetector.ts
File metadata and controls
147 lines (129 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { AnomalyAlert, AnomalyThresholds, MetricSnapshot } from "./types";
import { auditLogStore } from "./app/lib/audit-log";
function getDefaultThresholds(): AnomalyThresholds {
const { getConfig } = require("./app/lib/config");
const config = getConfig();
return {
creationBurstLimit: config.anomalyThresholds.creationBurstLimit,
settleRateLimit: config.anomalyThresholds.settleRateLimit,
cancelBurstLimit: config.anomalyThresholds.cancelBurstLimit,
};
}
/**
* In-memory store for cancellation timestamps per tenant to support moving-window heuristic.
* Maps tenantId to an array of timestamps (in milliseconds).
*/
const cancelTimestamps = new Map<string, number[]>();
/**
* In-memory whitelist for snoozing alerts per tenant during incidents.
* In production, this should be backed by a distributed cache or DB.
*/
const whitelist = new Set<string>();
/**
* Rule-based anomaly detection for early fraud/bug mitigation.
* SECURITY NOTE: These alerts are for observation and manual review.
* Do not use for unilateral fund freezing without a compliance policy.
*/
export const AnomalyDetector = {
evaluate(snapshot: MetricSnapshot, config: AnomalyThresholds = getDefaultThresholds()): AnomalyAlert[] {
if (whitelist.has(snapshot.tenantId)) {
return [];
}
const alerts: AnomalyAlert[] = [];
// Rule 1: High frequency of new stream creation
if (snapshot.streamCreations > config.creationBurstLimit) {
alerts.push({
tenantId: snapshot.tenantId,
ruleName: "STREAM_CREATION_BURST",
observedValue: snapshot.streamCreations,
threshold: config.creationBurstLimit,
severity: "high",
detectedAt: new Date().toISOString(),
});
}
// Rule 2: Abnormal settlement activity
if (snapshot.settleAttempts > config.settleRateLimit) {
alerts.push({
tenantId: snapshot.tenantId,
ruleName: "SETTLE_RATE_SPIKE",
observedValue: snapshot.settleAttempts,
threshold: config.settleRateLimit,
severity: "medium",
detectedAt: new Date().toISOString(),
});
}
// Rule 3: High submission failure rate
const total = snapshot.stellarSubmissionsTotal ?? 0;
const failed = snapshot.stellarSubmissionsFailed ?? 0;
const failureRate = total > 0 ? failed / total : 0;
if (failureRate > (config.submissionFailureThreshold ?? 0.05)) {
alerts.push({
tenantId: snapshot.tenantId,
ruleName: "HIGH_SUBMISSION_FAILURE_RATE" as any,
observedValue: failureRate,
threshold: config.submissionFailureThreshold ?? 0.05,
severity: "high",
detectedAt: new Date().toISOString(),
});
}
// Rule 4: DLQ Growth
const dlq = snapshot.dlqDepth ?? 0;
if (dlq > (config.maxDlqDepth ?? 10)) {
alerts.push({
tenantId: snapshot.tenantId,
ruleName: "DLQ_DEPTH_EXCEEDED" as any,
observedValue: dlq,
threshold: config.maxDlqDepth ?? 10,
severity: "high",
detectedAt: new Date().toISOString(),
});
}
// Rule 5: Stream cancel burst (moving window)
const now = snapshot.timestamp || Date.now();
const cancelLimit = config.cancelBurstLimit ?? 5;
if (snapshot.streamCancels && snapshot.streamCancels > 0) {
let times = cancelTimestamps.get(snapshot.tenantId) || [];
for (let i = 0; i < snapshot.streamCancels; i++) {
times.push(now);
}
cancelTimestamps.set(snapshot.tenantId, times);
}
let times = cancelTimestamps.get(snapshot.tenantId) || [];
const oneMinuteAgo = now - 60 * 1000;
times = times.filter(t => t > oneMinuteAgo);
if (times.length > 0) {
cancelTimestamps.set(snapshot.tenantId, times);
} else {
cancelTimestamps.delete(snapshot.tenantId);
}
if (times.length > cancelLimit) {
alerts.push({
tenantId: snapshot.tenantId,
ruleName: "STREAM_CANCEL_BURST",
observedValue: times.length,
threshold: cancelLimit,
severity: "high",
detectedAt: new Date(now).toISOString(),
});
// Write to audit log
auditLogStore.append({
action: "security.anomaly.cancel_burst",
actor: { id: "system:detector", role: "system" },
target: { id: snapshot.tenantId, type: "account" },
requestId: `detector-${snapshot.tenantId}-${now}`,
metadata: {
observedValue: times.length,
threshold: cancelLimit,
windowMs: 60000,
},
});
}
return alerts;
},
setWhitelist(tenantId: string, active: boolean) {
active ? whitelist.add(tenantId) : whitelist.delete(tenantId);
},
resetCancelHistory() {
cancelTimestamps.clear();
}
};