shroud-privacy 2.2.11 → 2.2.13
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/README.md +19 -10
- package/dist/hooks.js +246 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -2
- package/dist/agent-session.d.ts +0 -259
- package/dist/agent-session.js +0 -693
- package/dist/compliance.d.ts +0 -44
- package/dist/compliance.js +0 -76
- package/dist/dashboard.d.ts +0 -42
- package/dist/dashboard.js +0 -1558
- package/dist/detectors/injection-multilingual.d.ts +0 -27
- package/dist/detectors/injection-multilingual.js +0 -399
- package/dist/detectors/injection-signatures.d.ts +0 -26
- package/dist/detectors/injection-signatures.js +0 -508
- package/dist/detectors/injection.d.ts +0 -56
- package/dist/detectors/injection.js +0 -269
- package/dist/detectors/tool-guard.d.ts +0 -27
- package/dist/detectors/tool-guard.js +0 -418
- package/dist/event-grader.d.ts +0 -97
- package/dist/event-grader.js +0 -214
- package/dist/exposure.d.ts +0 -29
- package/dist/exposure.js +0 -72
- package/dist/policy.d.ts +0 -99
- package/dist/policy.js +0 -212
- package/dist/profiler-analysis.d.ts +0 -35
- package/dist/profiler-analysis.js +0 -230
- package/dist/profiler-store.d.ts +0 -33
- package/dist/profiler-store.js +0 -118
- package/dist/profiler-types.d.ts +0 -128
- package/dist/profiler-types.js +0 -16
- package/dist/profiler.d.ts +0 -81
- package/dist/profiler.js +0 -392
- package/dist/security-event.d.ts +0 -70
- package/dist/security-event.js +0 -80
- package/dist/siem.d.ts +0 -49
- package/dist/siem.js +0 -113
- package/dist/signature-loader.d.ts +0 -113
- package/dist/signature-loader.js +0 -255
- package/dist/store-file.d.ts +0 -26
- package/dist/store-file.js +0 -79
package/dist/profiler.js
DELETED
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Behavioural profiler for LLM agent sessions (Track 3).
|
|
3
|
-
*
|
|
4
|
-
* Extracts per-turn feature vectors from request/response text,
|
|
5
|
-
* maintains session aggregates, and detects anomalies against
|
|
6
|
-
* cross-session baselines. All feature extraction is synchronous
|
|
7
|
-
* and zero-dependency.
|
|
8
|
-
*/
|
|
9
|
-
import { createHash } from "node:crypto";
|
|
10
|
-
import { detectAnomalies } from "./profiler-analysis.js";
|
|
11
|
-
/** ~30 common imperative/directive verbs. */
|
|
12
|
-
const DIRECTIVE_VERBS = /\b(?:do|create|run|execute|build|make|write|read|delete|remove|update|modify|change|set|get|fetch|send|post|deploy|install|configure|enable|disable|start|stop|kill|restart|migrate|revert|rollback)\b/gi;
|
|
13
|
-
/**
|
|
14
|
-
* Behavioural profiler. One instance per session.
|
|
15
|
-
*/
|
|
16
|
-
export class BehaviouralProfiler {
|
|
17
|
-
_config;
|
|
18
|
-
_store;
|
|
19
|
-
_sessionId;
|
|
20
|
-
_agentBuildId = "";
|
|
21
|
-
_turns = [];
|
|
22
|
-
_alerts = [];
|
|
23
|
-
_previousTurnBigrams = new Set();
|
|
24
|
-
_allSessionWords = new Set();
|
|
25
|
-
_startedAt;
|
|
26
|
-
// Partial features accumulated during a turn (request side fills, response side completes)
|
|
27
|
-
_pendingRequest = null;
|
|
28
|
-
constructor(config, store) {
|
|
29
|
-
this._config = config;
|
|
30
|
-
this._store = store;
|
|
31
|
-
this._sessionId = createHash("sha256")
|
|
32
|
-
.update(`profile:${Date.now()}:${Math.random()}`)
|
|
33
|
-
.digest("hex")
|
|
34
|
-
.slice(0, 12);
|
|
35
|
-
this._startedAt = Date.now();
|
|
36
|
-
}
|
|
37
|
-
/** Set the agent build ID (from system prompt hash). */
|
|
38
|
-
setAgentBuildId(buildId) {
|
|
39
|
-
this._agentBuildId = buildId;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Extract features from the outbound request text.
|
|
43
|
-
* Called after obfuscation, before sending to LLM.
|
|
44
|
-
*/
|
|
45
|
-
extractRequestFeatures(text, entityCategoryCounts, toolCalls, imagePayloads) {
|
|
46
|
-
const tokenEstimate = Math.ceil(text.length / 4);
|
|
47
|
-
const totalEntities = Object.values(entityCategoryCounts).reduce((a, b) => a + b, 0);
|
|
48
|
-
// Entity density
|
|
49
|
-
const entityDensityPer1k = tokenEstimate > 0
|
|
50
|
-
? (totalEntities / tokenEstimate) * 1000
|
|
51
|
-
: 0;
|
|
52
|
-
// Shannon entropy of entity category distribution
|
|
53
|
-
const entityCategoryEntropy = shannonEntropy(entityCategoryCounts);
|
|
54
|
-
// Directive verbs
|
|
55
|
-
const directiveVerbCount = (text.match(DIRECTIVE_VERBS) || []).length;
|
|
56
|
-
// Questions
|
|
57
|
-
const questionCount = (text.match(/\?\s/g) || []).length + (text.endsWith("?") ? 1 : 0);
|
|
58
|
-
// Command-to-question ratio
|
|
59
|
-
const commandToQuestionRatio = questionCount > 0
|
|
60
|
-
? directiveVerbCount / questionCount
|
|
61
|
-
: directiveVerbCount > 0 ? Infinity : 0;
|
|
62
|
-
// Tool calls
|
|
63
|
-
const toolCallCount = toolCalls?.length ?? 0;
|
|
64
|
-
const toolNames = toolCalls?.map((tc) => tc.name) ?? [];
|
|
65
|
-
// Lexical analysis
|
|
66
|
-
const words = extractWords(text);
|
|
67
|
-
const bigrams = computeBigrams(words);
|
|
68
|
-
const lexicalOverlapWithPrevious = this._previousTurnBigrams.size > 0
|
|
69
|
-
? jaccardSimilarity(bigrams, this._previousTurnBigrams)
|
|
70
|
-
: 1.0; // First turn: assume no discontinuity
|
|
71
|
-
// New vocabulary rate
|
|
72
|
-
const newWords = words.filter((w) => !this._allSessionWords.has(w));
|
|
73
|
-
const newVocabularyRate = words.length > 0 ? newWords.length / words.length : 0;
|
|
74
|
-
// Update cumulative word tracking
|
|
75
|
-
for (const w of words)
|
|
76
|
-
this._allSessionWords.add(w);
|
|
77
|
-
this._previousTurnBigrams = bigrams;
|
|
78
|
-
// Script/language detection
|
|
79
|
-
const { script, nonLatinRatio } = detectScript(text);
|
|
80
|
-
this._pendingRequest = {
|
|
81
|
-
entityCategoryCounts,
|
|
82
|
-
entityDensityPer1k,
|
|
83
|
-
entityCategoryEntropy,
|
|
84
|
-
toolCallCount,
|
|
85
|
-
toolNames,
|
|
86
|
-
directiveVerbCount,
|
|
87
|
-
questionCount,
|
|
88
|
-
commandToQuestionRatio,
|
|
89
|
-
lexicalOverlapWithPrevious,
|
|
90
|
-
newVocabularyRate,
|
|
91
|
-
turnIndex: this._turns.length,
|
|
92
|
-
timestamp: Date.now(),
|
|
93
|
-
tokenEstimate,
|
|
94
|
-
detectedScript: script,
|
|
95
|
-
nonLatinRatio,
|
|
96
|
-
imagePayloadCount: imagePayloads?.count ?? 0,
|
|
97
|
-
imagePayloadBytes: imagePayloads?.totalBytes ?? 0,
|
|
98
|
-
// Cache metrics filled in from response (extractResponseFeatures)
|
|
99
|
-
inputTokens: 0,
|
|
100
|
-
outputTokens: 0,
|
|
101
|
-
cacheReadTokens: 0,
|
|
102
|
-
cacheWriteTokens: 0,
|
|
103
|
-
cacheHitRatio: 0,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Complete the feature vector with response-side features.
|
|
108
|
-
* Called after deobfuscation of the LLM response.
|
|
109
|
-
*/
|
|
110
|
-
extractResponseFeatures(responseText, requestEntities, cacheUsage) {
|
|
111
|
-
if (!this._pendingRequest)
|
|
112
|
-
return null;
|
|
113
|
-
const responseLength = responseText.length;
|
|
114
|
-
// Entity echo rate: how many request entities appear in the response
|
|
115
|
-
let echoCount = 0;
|
|
116
|
-
for (const entity of requestEntities) {
|
|
117
|
-
if (responseText.includes(entity))
|
|
118
|
-
echoCount++;
|
|
119
|
-
}
|
|
120
|
-
const entityEchoRate = requestEntities.length > 0
|
|
121
|
-
? echoCount / requestEntities.length
|
|
122
|
-
: 0;
|
|
123
|
-
const features = {
|
|
124
|
-
entityCategoryCounts: this._pendingRequest.entityCategoryCounts ?? {},
|
|
125
|
-
entityDensityPer1k: this._pendingRequest.entityDensityPer1k ?? 0,
|
|
126
|
-
entityCategoryEntropy: this._pendingRequest.entityCategoryEntropy ?? 0,
|
|
127
|
-
toolCallCount: this._pendingRequest.toolCallCount ?? 0,
|
|
128
|
-
toolNames: this._pendingRequest.toolNames ?? [],
|
|
129
|
-
directiveVerbCount: this._pendingRequest.directiveVerbCount ?? 0,
|
|
130
|
-
questionCount: this._pendingRequest.questionCount ?? 0,
|
|
131
|
-
commandToQuestionRatio: this._pendingRequest.commandToQuestionRatio ?? 0,
|
|
132
|
-
responseLength,
|
|
133
|
-
entityEchoRate,
|
|
134
|
-
lexicalOverlapWithPrevious: this._pendingRequest.lexicalOverlapWithPrevious ?? 1,
|
|
135
|
-
newVocabularyRate: this._pendingRequest.newVocabularyRate ?? 0,
|
|
136
|
-
turnIndex: this._pendingRequest.turnIndex ?? this._turns.length,
|
|
137
|
-
timestamp: this._pendingRequest.timestamp ?? Date.now(),
|
|
138
|
-
tokenEstimate: this._pendingRequest.tokenEstimate ?? 0,
|
|
139
|
-
detectedScript: this._pendingRequest.detectedScript ?? "latin",
|
|
140
|
-
nonLatinRatio: this._pendingRequest.nonLatinRatio ?? 0,
|
|
141
|
-
imagePayloadCount: this._pendingRequest.imagePayloadCount ?? 0,
|
|
142
|
-
imagePayloadBytes: this._pendingRequest.imagePayloadBytes ?? 0,
|
|
143
|
-
inputTokens: cacheUsage?.inputTokens ?? 0,
|
|
144
|
-
outputTokens: cacheUsage?.outputTokens ?? 0,
|
|
145
|
-
cacheReadTokens: cacheUsage?.cacheReadTokens ?? 0,
|
|
146
|
-
cacheWriteTokens: cacheUsage?.cacheWriteTokens ?? 0,
|
|
147
|
-
cacheHitRatio: cacheUsage && cacheUsage.inputTokens > 0
|
|
148
|
-
? cacheUsage.cacheReadTokens / cacheUsage.inputTokens
|
|
149
|
-
: 0,
|
|
150
|
-
};
|
|
151
|
-
this._turns.push(features);
|
|
152
|
-
this._pendingRequest = null;
|
|
153
|
-
return features;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Analyze a completed turn against the baseline.
|
|
157
|
-
* Returns anomaly alerts (empty if in learning mode or insufficient baseline).
|
|
158
|
-
*/
|
|
159
|
-
analyzeTurn(features) {
|
|
160
|
-
if (this._config.mode === "learning")
|
|
161
|
-
return [];
|
|
162
|
-
if (!this._agentBuildId)
|
|
163
|
-
return [];
|
|
164
|
-
const baseline = this._store.load(this._agentBuildId);
|
|
165
|
-
if (!baseline)
|
|
166
|
-
return [];
|
|
167
|
-
if (baseline.sessionCount < this._config.minBaseline)
|
|
168
|
-
return [];
|
|
169
|
-
const knownTools = new Set(baseline.toolProfile);
|
|
170
|
-
const knownCategories = new Set(baseline.categoryProfile);
|
|
171
|
-
const alerts = detectAnomalies(features, baseline.features, this._config.sigma, knownTools, knownCategories);
|
|
172
|
-
this._alerts.push(...alerts);
|
|
173
|
-
return alerts;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Finalize the session: compute aggregates, update baseline store.
|
|
177
|
-
*/
|
|
178
|
-
finalizeSession() {
|
|
179
|
-
const aggregates = this._computeAggregates();
|
|
180
|
-
const profile = {
|
|
181
|
-
sessionId: this._sessionId,
|
|
182
|
-
agentBuildId: this._agentBuildId,
|
|
183
|
-
startedAt: this._startedAt,
|
|
184
|
-
turns: this._turns,
|
|
185
|
-
aggregates,
|
|
186
|
-
};
|
|
187
|
-
// Update the persistent baseline
|
|
188
|
-
if (this._agentBuildId && this._turns.length > 0) {
|
|
189
|
-
this._store.updateFromSession(this._agentBuildId, profile);
|
|
190
|
-
}
|
|
191
|
-
return profile;
|
|
192
|
-
}
|
|
193
|
-
/** Get all anomaly alerts from this session. */
|
|
194
|
-
getAlerts() {
|
|
195
|
-
return this._alerts;
|
|
196
|
-
}
|
|
197
|
-
/** Get LLM cache usage summary for the current session. */
|
|
198
|
-
getCacheStats() {
|
|
199
|
-
let totalInput = 0, totalOutput = 0, totalCacheRead = 0, totalCacheWrite = 0;
|
|
200
|
-
for (const t of this._turns) {
|
|
201
|
-
totalInput += t.inputTokens;
|
|
202
|
-
totalOutput += t.outputTokens;
|
|
203
|
-
totalCacheRead += t.cacheReadTokens;
|
|
204
|
-
totalCacheWrite += t.cacheWriteTokens;
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
totalInput, totalOutput, totalCacheRead, totalCacheWrite,
|
|
208
|
-
hitRatio: totalInput > 0 ? totalCacheRead / totalInput : 0,
|
|
209
|
-
turns: this._turns.length,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
/** Get current session profile (without finalizing). */
|
|
213
|
-
getSessionProfile() {
|
|
214
|
-
return {
|
|
215
|
-
sessionId: this._sessionId,
|
|
216
|
-
agentBuildId: this._agentBuildId,
|
|
217
|
-
startedAt: this._startedAt,
|
|
218
|
-
turns: [...this._turns],
|
|
219
|
-
aggregates: this._computeAggregates(),
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
_computeAggregates() {
|
|
223
|
-
if (this._turns.length === 0) {
|
|
224
|
-
return {
|
|
225
|
-
dominantCategories: [],
|
|
226
|
-
toolSequenceFingerprint: "",
|
|
227
|
-
averageEntityDensity: 0,
|
|
228
|
-
averageDirectiveVerbCount: 0,
|
|
229
|
-
averageResponseLength: 0,
|
|
230
|
-
averageLexicalOverlap: 0,
|
|
231
|
-
turnCount: 0,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
// Category frequency
|
|
235
|
-
const catFreq = {};
|
|
236
|
-
const allTools = new Set();
|
|
237
|
-
let totalDensity = 0;
|
|
238
|
-
let totalDirective = 0;
|
|
239
|
-
let totalResponse = 0;
|
|
240
|
-
let totalOverlap = 0;
|
|
241
|
-
for (const turn of this._turns) {
|
|
242
|
-
for (const [cat, count] of Object.entries(turn.entityCategoryCounts)) {
|
|
243
|
-
catFreq[cat] = (catFreq[cat] ?? 0) + count;
|
|
244
|
-
}
|
|
245
|
-
for (const tool of turn.toolNames)
|
|
246
|
-
allTools.add(tool);
|
|
247
|
-
totalDensity += turn.entityDensityPer1k;
|
|
248
|
-
totalDirective += turn.directiveVerbCount;
|
|
249
|
-
totalResponse += turn.responseLength;
|
|
250
|
-
totalOverlap += turn.lexicalOverlapWithPrevious;
|
|
251
|
-
}
|
|
252
|
-
const n = this._turns.length;
|
|
253
|
-
const dominantCategories = Object.entries(catFreq)
|
|
254
|
-
.sort((a, b) => b[1] - a[1])
|
|
255
|
-
.slice(0, 3)
|
|
256
|
-
.map(([cat]) => cat);
|
|
257
|
-
const toolList = [...allTools].sort();
|
|
258
|
-
const toolSequenceFingerprint = createHash("sha256")
|
|
259
|
-
.update(toolList.join(","))
|
|
260
|
-
.digest("hex")
|
|
261
|
-
.slice(0, 12);
|
|
262
|
-
return {
|
|
263
|
-
dominantCategories,
|
|
264
|
-
toolSequenceFingerprint,
|
|
265
|
-
averageEntityDensity: totalDensity / n,
|
|
266
|
-
averageDirectiveVerbCount: totalDirective / n,
|
|
267
|
-
averageResponseLength: totalResponse / n,
|
|
268
|
-
averageLexicalOverlap: totalOverlap / n,
|
|
269
|
-
turnCount: n,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
// ===================================================================
|
|
274
|
-
// Script/language detection (zero-dep, Unicode range based)
|
|
275
|
-
// ===================================================================
|
|
276
|
-
/** Detect the dominant script and non-Latin ratio of text. */
|
|
277
|
-
function detectScript(text) {
|
|
278
|
-
let latin = 0;
|
|
279
|
-
let cjk = 0;
|
|
280
|
-
let cyrillic = 0;
|
|
281
|
-
let arabic = 0;
|
|
282
|
-
let devanagari = 0;
|
|
283
|
-
let hangul = 0;
|
|
284
|
-
let total = 0;
|
|
285
|
-
for (const ch of text) {
|
|
286
|
-
const cp = ch.codePointAt(0);
|
|
287
|
-
if (cp < 0x20)
|
|
288
|
-
continue; // control chars
|
|
289
|
-
if (cp < 0x7F) {
|
|
290
|
-
latin++;
|
|
291
|
-
total++;
|
|
292
|
-
continue;
|
|
293
|
-
} // Basic Latin (ASCII)
|
|
294
|
-
if (cp >= 0x00C0 && cp <= 0x024F) {
|
|
295
|
-
latin++;
|
|
296
|
-
total++;
|
|
297
|
-
continue;
|
|
298
|
-
} // Latin Extended
|
|
299
|
-
if (cp >= 0x4E00 && cp <= 0x9FFF) {
|
|
300
|
-
cjk++;
|
|
301
|
-
total++;
|
|
302
|
-
continue;
|
|
303
|
-
} // CJK Unified
|
|
304
|
-
if (cp >= 0x3040 && cp <= 0x30FF) {
|
|
305
|
-
cjk++;
|
|
306
|
-
total++;
|
|
307
|
-
continue;
|
|
308
|
-
} // Hiragana + Katakana
|
|
309
|
-
if (cp >= 0x3400 && cp <= 0x4DBF) {
|
|
310
|
-
cjk++;
|
|
311
|
-
total++;
|
|
312
|
-
continue;
|
|
313
|
-
} // CJK Extension A
|
|
314
|
-
if (cp >= 0x0400 && cp <= 0x04FF) {
|
|
315
|
-
cyrillic++;
|
|
316
|
-
total++;
|
|
317
|
-
continue;
|
|
318
|
-
} // Cyrillic
|
|
319
|
-
if (cp >= 0x0600 && cp <= 0x06FF) {
|
|
320
|
-
arabic++;
|
|
321
|
-
total++;
|
|
322
|
-
continue;
|
|
323
|
-
} // Arabic
|
|
324
|
-
if (cp >= 0x0900 && cp <= 0x097F) {
|
|
325
|
-
devanagari++;
|
|
326
|
-
total++;
|
|
327
|
-
continue;
|
|
328
|
-
} // Devanagari
|
|
329
|
-
if (cp >= 0xAC00 && cp <= 0xD7AF) {
|
|
330
|
-
hangul++;
|
|
331
|
-
total++;
|
|
332
|
-
continue;
|
|
333
|
-
} // Hangul
|
|
334
|
-
total++;
|
|
335
|
-
}
|
|
336
|
-
if (total === 0)
|
|
337
|
-
return { script: "latin", nonLatinRatio: 0 };
|
|
338
|
-
const nonLatin = total - latin;
|
|
339
|
-
const nonLatinRatio = nonLatin / total;
|
|
340
|
-
// Determine dominant script
|
|
341
|
-
const counts = [
|
|
342
|
-
["latin", latin],
|
|
343
|
-
["cjk", cjk],
|
|
344
|
-
["cyrillic", cyrillic],
|
|
345
|
-
["arabic", arabic],
|
|
346
|
-
["devanagari", devanagari],
|
|
347
|
-
["hangul", hangul],
|
|
348
|
-
];
|
|
349
|
-
counts.sort((a, b) => b[1] - a[1]);
|
|
350
|
-
const dominant = counts[0][1] > 0 ? counts[0][0] : "unknown";
|
|
351
|
-
return { script: dominant, nonLatinRatio };
|
|
352
|
-
}
|
|
353
|
-
// ===================================================================
|
|
354
|
-
// Text analysis utilities
|
|
355
|
-
// ===================================================================
|
|
356
|
-
/** Extract lowercase words from text. */
|
|
357
|
-
function extractWords(text) {
|
|
358
|
-
return text.toLowerCase().match(/\b[a-z]{2,}\b/g) || [];
|
|
359
|
-
}
|
|
360
|
-
/** Compute word bigrams from a word array. */
|
|
361
|
-
function computeBigrams(words) {
|
|
362
|
-
const bigrams = new Set();
|
|
363
|
-
for (let i = 0; i < words.length - 1; i++) {
|
|
364
|
-
bigrams.add(`${words[i]} ${words[i + 1]}`);
|
|
365
|
-
}
|
|
366
|
-
return bigrams;
|
|
367
|
-
}
|
|
368
|
-
/** Jaccard similarity between two sets. */
|
|
369
|
-
function jaccardSimilarity(a, b) {
|
|
370
|
-
if (a.size === 0 && b.size === 0)
|
|
371
|
-
return 1;
|
|
372
|
-
let intersection = 0;
|
|
373
|
-
for (const item of a) {
|
|
374
|
-
if (b.has(item))
|
|
375
|
-
intersection++;
|
|
376
|
-
}
|
|
377
|
-
const union = a.size + b.size - intersection;
|
|
378
|
-
return union > 0 ? intersection / union : 0;
|
|
379
|
-
}
|
|
380
|
-
/** Shannon entropy of a category count distribution. */
|
|
381
|
-
function shannonEntropy(counts) {
|
|
382
|
-
const values = Object.values(counts).filter((v) => v > 0);
|
|
383
|
-
if (values.length === 0)
|
|
384
|
-
return 0;
|
|
385
|
-
const total = values.reduce((a, b) => a + b, 0);
|
|
386
|
-
let entropy = 0;
|
|
387
|
-
for (const v of values) {
|
|
388
|
-
const p = v / total;
|
|
389
|
-
entropy -= p * Math.log2(p);
|
|
390
|
-
}
|
|
391
|
-
return entropy;
|
|
392
|
-
}
|
package/dist/security-event.d.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Security event types and in-memory event bus.
|
|
3
|
-
*
|
|
4
|
-
* Shared by all security tracks: injection detection, canary monitoring,
|
|
5
|
-
* and behavioural profiling. Events are accumulated in memory and
|
|
6
|
-
* queryable during the session.
|
|
7
|
-
*/
|
|
8
|
-
/** Threat classes for injection signature detection. */
|
|
9
|
-
export declare enum ThreatClass {
|
|
10
|
-
INSTRUCTION_OVERRIDE = "instruction_override",
|
|
11
|
-
ROLE_SWITCH = "role_switch",
|
|
12
|
-
PROMPT_EXTRACTION = "prompt_extraction",
|
|
13
|
-
CONVERSATION_MOCKUP = "conversation_mockup",
|
|
14
|
-
ENCODING_BYPASS = "encoding_bypass",
|
|
15
|
-
DATA_EXFILTRATION = "data_exfiltration",
|
|
16
|
-
PRIVILEGE_ESCALATION = "privilege_escalation",
|
|
17
|
-
MCP_TOOL_POISONING = "mcp_tool_poisoning"
|
|
18
|
-
}
|
|
19
|
-
/** Severity levels for security events. */
|
|
20
|
-
export type SecuritySeverity = "low" | "medium" | "high";
|
|
21
|
-
/** Actions taken in response to a security event. */
|
|
22
|
-
export type SecurityAction = "flagged" | "blocked";
|
|
23
|
-
/** A security event emitted by any of the three tracks. */
|
|
24
|
-
export interface SecurityEvent {
|
|
25
|
-
timestamp: number;
|
|
26
|
-
eventType: "injection_detected" | "canary_triggered" | "anomaly_detected";
|
|
27
|
-
direction: "request" | "response";
|
|
28
|
-
threatClass: ThreatClass;
|
|
29
|
-
signatureId: string;
|
|
30
|
-
severity: SecuritySeverity;
|
|
31
|
-
matchedText: string;
|
|
32
|
-
matchStart: number;
|
|
33
|
-
matchEnd: number;
|
|
34
|
-
textLength: number;
|
|
35
|
-
action: SecurityAction;
|
|
36
|
-
description: string;
|
|
37
|
-
/** Agent identity — which agent's session produced this event. */
|
|
38
|
-
agentBuildId?: string;
|
|
39
|
-
/** Human-readable agent label. */
|
|
40
|
-
agentLabel?: string;
|
|
41
|
-
/** Agent session ID. */
|
|
42
|
-
agentSessionId?: string;
|
|
43
|
-
/** Channel the event originated from (slack, whatsapp, tui, etc.). */
|
|
44
|
-
channel?: string;
|
|
45
|
-
}
|
|
46
|
-
/** Aggregate statistics for security events. */
|
|
47
|
-
export interface SecurityStats {
|
|
48
|
-
totalEvents: number;
|
|
49
|
-
byThreatClass: Record<string, number>;
|
|
50
|
-
bySeverity: Record<string, number>;
|
|
51
|
-
byDirection: Record<string, number>;
|
|
52
|
-
blockedCount: number;
|
|
53
|
-
flaggedCount: number;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* In-memory accumulator for security events. Bounded by maxEvents
|
|
57
|
-
* to prevent unbounded growth during long sessions.
|
|
58
|
-
*/
|
|
59
|
-
export declare class SecurityEventBus {
|
|
60
|
-
private _events;
|
|
61
|
-
private _maxEvents;
|
|
62
|
-
private _listeners;
|
|
63
|
-
constructor(maxEvents?: number);
|
|
64
|
-
emit(event: SecurityEvent): void;
|
|
65
|
-
/** Subscribe to real-time events. Returns unsubscribe function. */
|
|
66
|
-
onEvent(listener: (event: SecurityEvent) => void): () => void;
|
|
67
|
-
getEvents(): readonly SecurityEvent[];
|
|
68
|
-
getStats(): SecurityStats;
|
|
69
|
-
clear(): void;
|
|
70
|
-
}
|
package/dist/security-event.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Security event types and in-memory event bus.
|
|
3
|
-
*
|
|
4
|
-
* Shared by all security tracks: injection detection, canary monitoring,
|
|
5
|
-
* and behavioural profiling. Events are accumulated in memory and
|
|
6
|
-
* queryable during the session.
|
|
7
|
-
*/
|
|
8
|
-
/** Threat classes for injection signature detection. */
|
|
9
|
-
export var ThreatClass;
|
|
10
|
-
(function (ThreatClass) {
|
|
11
|
-
ThreatClass["INSTRUCTION_OVERRIDE"] = "instruction_override";
|
|
12
|
-
ThreatClass["ROLE_SWITCH"] = "role_switch";
|
|
13
|
-
ThreatClass["PROMPT_EXTRACTION"] = "prompt_extraction";
|
|
14
|
-
ThreatClass["CONVERSATION_MOCKUP"] = "conversation_mockup";
|
|
15
|
-
ThreatClass["ENCODING_BYPASS"] = "encoding_bypass";
|
|
16
|
-
ThreatClass["DATA_EXFILTRATION"] = "data_exfiltration";
|
|
17
|
-
ThreatClass["PRIVILEGE_ESCALATION"] = "privilege_escalation";
|
|
18
|
-
ThreatClass["MCP_TOOL_POISONING"] = "mcp_tool_poisoning";
|
|
19
|
-
})(ThreatClass || (ThreatClass = {}));
|
|
20
|
-
/**
|
|
21
|
-
* In-memory accumulator for security events. Bounded by maxEvents
|
|
22
|
-
* to prevent unbounded growth during long sessions.
|
|
23
|
-
*/
|
|
24
|
-
export class SecurityEventBus {
|
|
25
|
-
_events = [];
|
|
26
|
-
_maxEvents;
|
|
27
|
-
_listeners = [];
|
|
28
|
-
constructor(maxEvents = 500) {
|
|
29
|
-
this._maxEvents = maxEvents;
|
|
30
|
-
}
|
|
31
|
-
emit(event) {
|
|
32
|
-
this._events.push(event);
|
|
33
|
-
// Evict oldest if over capacity
|
|
34
|
-
if (this._events.length > this._maxEvents) {
|
|
35
|
-
this._events.splice(0, this._events.length - this._maxEvents);
|
|
36
|
-
}
|
|
37
|
-
// Notify listeners (SSE dashboard, SIEM, etc.)
|
|
38
|
-
for (const listener of this._listeners) {
|
|
39
|
-
try {
|
|
40
|
-
listener(event);
|
|
41
|
-
}
|
|
42
|
-
catch { /* listeners must not break emit */ }
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
/** Subscribe to real-time events. Returns unsubscribe function. */
|
|
46
|
-
onEvent(listener) {
|
|
47
|
-
this._listeners.push(listener);
|
|
48
|
-
return () => {
|
|
49
|
-
const idx = this._listeners.indexOf(listener);
|
|
50
|
-
if (idx >= 0)
|
|
51
|
-
this._listeners.splice(idx, 1);
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
getEvents() {
|
|
55
|
-
return this._events;
|
|
56
|
-
}
|
|
57
|
-
getStats() {
|
|
58
|
-
const stats = {
|
|
59
|
-
totalEvents: this._events.length,
|
|
60
|
-
byThreatClass: {},
|
|
61
|
-
bySeverity: {},
|
|
62
|
-
byDirection: {},
|
|
63
|
-
blockedCount: 0,
|
|
64
|
-
flaggedCount: 0,
|
|
65
|
-
};
|
|
66
|
-
for (const e of this._events) {
|
|
67
|
-
stats.byThreatClass[e.threatClass] = (stats.byThreatClass[e.threatClass] ?? 0) + 1;
|
|
68
|
-
stats.bySeverity[e.severity] = (stats.bySeverity[e.severity] ?? 0) + 1;
|
|
69
|
-
stats.byDirection[e.direction] = (stats.byDirection[e.direction] ?? 0) + 1;
|
|
70
|
-
if (e.action === "blocked")
|
|
71
|
-
stats.blockedCount++;
|
|
72
|
-
else
|
|
73
|
-
stats.flaggedCount++;
|
|
74
|
-
}
|
|
75
|
-
return stats;
|
|
76
|
-
}
|
|
77
|
-
clear() {
|
|
78
|
-
this._events = [];
|
|
79
|
-
}
|
|
80
|
-
}
|
package/dist/siem.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SIEM integration — ships security events to external systems.
|
|
3
|
-
*
|
|
4
|
-
* Two output modes:
|
|
5
|
-
* 1. Webhook POST: sends events as JSON to a configured URL (Splunk HEC, Grafana Loki, custom)
|
|
6
|
-
* 2. JSONL file: appends events as newline-delimited JSON to a local file
|
|
7
|
-
*
|
|
8
|
-
* Both are async fire-and-forget — never blocks the request pipeline.
|
|
9
|
-
* Zero runtime dependencies.
|
|
10
|
-
*/
|
|
11
|
-
import type { SecurityEvent } from "./security-event.js";
|
|
12
|
-
/** SIEM output configuration. */
|
|
13
|
-
export interface SiemConfig {
|
|
14
|
-
/** Webhook URL to POST events to. Null = disabled. */
|
|
15
|
-
webhookUrl: string | null;
|
|
16
|
-
/** Optional auth header value (e.g. "Bearer xxx" or "Splunk xxx"). */
|
|
17
|
-
webhookAuth: string | null;
|
|
18
|
-
/** JSONL file path to append events. Null = disabled. */
|
|
19
|
-
jsonlPath: string | null;
|
|
20
|
-
/** Batch size — buffer events and flush at this count. 1 = immediate. */
|
|
21
|
-
batchSize: number;
|
|
22
|
-
/** Max flush interval in ms. Events flushed even if batch not full. */
|
|
23
|
-
flushIntervalMs: number;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* SIEM event shipper. Subscribes to SecurityEventBus and ships events
|
|
27
|
-
* to configured destinations.
|
|
28
|
-
*/
|
|
29
|
-
export declare class SiemShipper {
|
|
30
|
-
private _config;
|
|
31
|
-
private _buffer;
|
|
32
|
-
private _flushTimer;
|
|
33
|
-
private _stats;
|
|
34
|
-
constructor(config: SiemConfig);
|
|
35
|
-
/** Called for each security event. Buffers and flushes. */
|
|
36
|
-
onEvent(event: SecurityEvent): void;
|
|
37
|
-
/** Flush buffered events to all configured destinations. */
|
|
38
|
-
flush(): void;
|
|
39
|
-
/** Stop the flush timer and flush remaining events. */
|
|
40
|
-
stop(): void;
|
|
41
|
-
/** Get shipping stats. */
|
|
42
|
-
getStats(): {
|
|
43
|
-
buffered: number;
|
|
44
|
-
shipped: number;
|
|
45
|
-
webhookErrors: number;
|
|
46
|
-
fileErrors: number;
|
|
47
|
-
};
|
|
48
|
-
private _postWebhook;
|
|
49
|
-
}
|