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
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Statistical analysis utilities for behavioural profiling.
|
|
3
|
-
*
|
|
4
|
-
* Welford's online algorithm for incremental mean/stddev,
|
|
5
|
-
* Z-score computation, and multi-feature anomaly scoring.
|
|
6
|
-
* All synchronous, zero dependencies.
|
|
7
|
-
*/
|
|
8
|
-
import { AnomalyType, } from "./profiler-types.js";
|
|
9
|
-
// ===================================================================
|
|
10
|
-
// Welford's Online Algorithm
|
|
11
|
-
// ===================================================================
|
|
12
|
-
/** Create a fresh running stats accumulator. */
|
|
13
|
-
export function emptyStats() {
|
|
14
|
-
return { mean: 0, m2: 0, n: 0, min: Infinity, max: -Infinity };
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Update running stats with a new observation.
|
|
18
|
-
* Uses Welford's algorithm for numerically stable incremental mean/variance.
|
|
19
|
-
*/
|
|
20
|
-
export function updateStats(stats, x) {
|
|
21
|
-
const n = stats.n + 1;
|
|
22
|
-
const delta = x - stats.mean;
|
|
23
|
-
const mean = stats.mean + delta / n;
|
|
24
|
-
const delta2 = x - mean;
|
|
25
|
-
const m2 = stats.m2 + delta * delta2;
|
|
26
|
-
return {
|
|
27
|
-
mean,
|
|
28
|
-
m2,
|
|
29
|
-
n,
|
|
30
|
-
min: Math.min(stats.min, x),
|
|
31
|
-
max: Math.max(stats.max, x),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
/** Compute standard deviation from running stats. */
|
|
35
|
-
export function stddev(stats) {
|
|
36
|
-
if (stats.n < 2)
|
|
37
|
-
return 0;
|
|
38
|
-
return Math.sqrt(stats.m2 / stats.n);
|
|
39
|
-
}
|
|
40
|
-
// ===================================================================
|
|
41
|
-
// Z-Score and Anomaly Detection
|
|
42
|
-
// ===================================================================
|
|
43
|
-
/** Compute Z-score. Returns 0 if stddev is 0 and value equals mean. */
|
|
44
|
-
export function zScore(observed, mean, sd) {
|
|
45
|
-
if (sd === 0)
|
|
46
|
-
return observed === mean ? 0 : Infinity;
|
|
47
|
-
return (observed - mean) / sd;
|
|
48
|
-
}
|
|
49
|
-
/** Features extracted from a FeatureVector that we track in the baseline. */
|
|
50
|
-
const NUMERIC_FEATURES = [
|
|
51
|
-
{
|
|
52
|
-
name: "entityDensityPer1k",
|
|
53
|
-
extract: (fv) => fv.entityDensityPer1k,
|
|
54
|
-
anomalyType: AnomalyType.ENTITY_DENSITY_SPIKE,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: "entityCategoryEntropy",
|
|
58
|
-
extract: (fv) => fv.entityCategoryEntropy,
|
|
59
|
-
anomalyType: AnomalyType.ENTITY_CATEGORY_SHIFT,
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: "toolCallCount",
|
|
63
|
-
extract: (fv) => fv.toolCallCount,
|
|
64
|
-
anomalyType: AnomalyType.TOOL_OUTSIDE_PROFILE,
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: "directiveVerbCount",
|
|
68
|
-
extract: (fv) => fv.directiveVerbCount,
|
|
69
|
-
anomalyType: AnomalyType.ENTITY_CATEGORY_SHIFT,
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
name: "commandToQuestionRatio",
|
|
73
|
-
extract: (fv) => isFinite(fv.commandToQuestionRatio) ? fv.commandToQuestionRatio : 10,
|
|
74
|
-
anomalyType: AnomalyType.ENTITY_CATEGORY_SHIFT,
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: "responseLength",
|
|
78
|
-
extract: (fv) => fv.responseLength,
|
|
79
|
-
anomalyType: AnomalyType.EXFILTRATION_PATTERN,
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: "entityEchoRate",
|
|
83
|
-
extract: (fv) => fv.entityEchoRate,
|
|
84
|
-
anomalyType: AnomalyType.EXFILTRATION_PATTERN,
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
name: "lexicalOverlapWithPrevious",
|
|
88
|
-
extract: (fv) => fv.lexicalOverlapWithPrevious,
|
|
89
|
-
anomalyType: AnomalyType.TOPIC_DISCONTINUITY,
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: "newVocabularyRate",
|
|
93
|
-
extract: (fv) => fv.newVocabularyRate,
|
|
94
|
-
anomalyType: AnomalyType.TOPIC_DISCONTINUITY,
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: "tokenEstimate",
|
|
98
|
-
extract: (fv) => fv.tokenEstimate,
|
|
99
|
-
anomalyType: AnomalyType.ENTITY_DENSITY_SPIKE,
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
name: "nonLatinRatio",
|
|
103
|
-
extract: (fv) => fv.nonLatinRatio,
|
|
104
|
-
anomalyType: AnomalyType.TOPIC_DISCONTINUITY,
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
name: "imagePayloadCount",
|
|
108
|
-
extract: (fv) => fv.imagePayloadCount,
|
|
109
|
-
anomalyType: AnomalyType.ENTITY_CATEGORY_SHIFT,
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
name: "imagePayloadBytes",
|
|
113
|
-
extract: (fv) => fv.imagePayloadBytes,
|
|
114
|
-
anomalyType: AnomalyType.ENTITY_CATEGORY_SHIFT,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: "inputTokens",
|
|
118
|
-
extract: (fv) => fv.inputTokens,
|
|
119
|
-
anomalyType: AnomalyType.ENTITY_DENSITY_SPIKE,
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
name: "outputTokens",
|
|
123
|
-
extract: (fv) => fv.outputTokens,
|
|
124
|
-
anomalyType: AnomalyType.EXFILTRATION_PATTERN,
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: "cacheHitRatio",
|
|
128
|
-
extract: (fv) => fv.cacheHitRatio,
|
|
129
|
-
anomalyType: AnomalyType.TOPIC_DISCONTINUITY,
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: "cacheWriteTokens",
|
|
133
|
-
extract: (fv) => fv.cacheWriteTokens,
|
|
134
|
-
anomalyType: AnomalyType.ENTITY_CATEGORY_SHIFT,
|
|
135
|
-
},
|
|
136
|
-
];
|
|
137
|
-
/**
|
|
138
|
-
* Detect anomalies by comparing a feature vector against the baseline.
|
|
139
|
-
*
|
|
140
|
-
* @param features - Current turn's feature vector
|
|
141
|
-
* @param baseline - Accumulated baseline feature statistics
|
|
142
|
-
* @param sigma - Z-score threshold (default 3.0)
|
|
143
|
-
* @param knownTools - Set of tool names seen in baseline sessions
|
|
144
|
-
* @param knownCategories - Set of entity categories seen in baseline sessions
|
|
145
|
-
*/
|
|
146
|
-
export function detectAnomalies(features, baseline, sigma, knownTools, knownCategories) {
|
|
147
|
-
const alerts = [];
|
|
148
|
-
const ts = features.timestamp;
|
|
149
|
-
const turn = features.turnIndex;
|
|
150
|
-
// Z-score based anomalies for numeric features
|
|
151
|
-
for (const feat of NUMERIC_FEATURES) {
|
|
152
|
-
const stats = baseline[feat.name];
|
|
153
|
-
if (!stats || stats.n < 2)
|
|
154
|
-
continue; // Not enough data
|
|
155
|
-
const observed = feat.extract(features);
|
|
156
|
-
const sd = stddev(stats);
|
|
157
|
-
const z = zScore(observed, stats.mean, sd);
|
|
158
|
-
if (Math.abs(z) > sigma) {
|
|
159
|
-
const severity = Math.abs(z) > sigma * 1.5 ? "critical" : "warning";
|
|
160
|
-
alerts.push({
|
|
161
|
-
type: feat.anomalyType,
|
|
162
|
-
severity,
|
|
163
|
-
feature: feat.name,
|
|
164
|
-
observedValue: observed,
|
|
165
|
-
baselineMean: stats.mean,
|
|
166
|
-
baselineStddev: sd,
|
|
167
|
-
zScore: z,
|
|
168
|
-
turnIndex: turn,
|
|
169
|
-
timestamp: ts,
|
|
170
|
-
description: `${feat.name}: z=${z.toFixed(2)} (observed=${observed.toFixed(2)}, baseline=${stats.mean.toFixed(2)}±${sd.toFixed(2)})`,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// Tool outside profile — tool name never seen in baseline
|
|
175
|
-
if (knownTools.size > 0) {
|
|
176
|
-
for (const tool of features.toolNames) {
|
|
177
|
-
if (!knownTools.has(tool)) {
|
|
178
|
-
alerts.push({
|
|
179
|
-
type: AnomalyType.TOOL_OUTSIDE_PROFILE,
|
|
180
|
-
severity: "warning",
|
|
181
|
-
feature: "toolName",
|
|
182
|
-
observedValue: 1,
|
|
183
|
-
baselineMean: 0,
|
|
184
|
-
baselineStddev: 0,
|
|
185
|
-
zScore: Infinity,
|
|
186
|
-
turnIndex: turn,
|
|
187
|
-
timestamp: ts,
|
|
188
|
-
description: `Tool "${tool}" not in baseline tool set`,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
// Credential emergence — credential-like categories appearing for first time
|
|
194
|
-
const credentialCategories = new Set(["api_key", "jwt", "network_credential", "certificate"]);
|
|
195
|
-
for (const [cat, count] of Object.entries(features.entityCategoryCounts)) {
|
|
196
|
-
if (credentialCategories.has(cat) && count > 0 && !knownCategories.has(cat)) {
|
|
197
|
-
alerts.push({
|
|
198
|
-
type: AnomalyType.CREDENTIAL_EMERGENCE,
|
|
199
|
-
severity: "critical",
|
|
200
|
-
feature: `category:${cat}`,
|
|
201
|
-
observedValue: count,
|
|
202
|
-
baselineMean: 0,
|
|
203
|
-
baselineStddev: 0,
|
|
204
|
-
zScore: Infinity,
|
|
205
|
-
turnIndex: turn,
|
|
206
|
-
timestamp: ts,
|
|
207
|
-
description: `Credential category "${cat}" appeared (count=${count}) with no baseline history`,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return alerts;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Update the baseline with feature values from a completed turn.
|
|
215
|
-
*/
|
|
216
|
-
export function updateBaseline(baseline, features) {
|
|
217
|
-
const updated = { ...baseline };
|
|
218
|
-
for (const feat of NUMERIC_FEATURES) {
|
|
219
|
-
const value = feat.extract(features);
|
|
220
|
-
if (!updated[feat.name]) {
|
|
221
|
-
updated[feat.name] = emptyStats();
|
|
222
|
-
}
|
|
223
|
-
updated[feat.name] = updateStats(updated[feat.name], value);
|
|
224
|
-
}
|
|
225
|
-
return updated;
|
|
226
|
-
}
|
|
227
|
-
/** Get the list of tracked numeric feature names. */
|
|
228
|
-
export function getTrackedFeatureNames() {
|
|
229
|
-
return NUMERIC_FEATURES.map((f) => f.name);
|
|
230
|
-
}
|
package/dist/profiler-store.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File-based persistence for behavioural profiling baselines.
|
|
3
|
-
*
|
|
4
|
-
* Stores per-agent-build baselines as JSON files.
|
|
5
|
-
* Uses Welford's online algorithm — only running statistics are persisted,
|
|
6
|
-
* not raw data. Zero runtime dependencies.
|
|
7
|
-
*/
|
|
8
|
-
import { AgentBaseline, SessionProfile } from "./profiler-types.js";
|
|
9
|
-
/**
|
|
10
|
-
* File-based store for agent behavioural baselines.
|
|
11
|
-
*/
|
|
12
|
-
export declare class BaselineStore {
|
|
13
|
-
private readonly _profileDir;
|
|
14
|
-
constructor(profileDir: string);
|
|
15
|
-
/** Load a baseline for the given agent build ID. Returns null if not found. */
|
|
16
|
-
load(agentBuildId: string): AgentBaseline | null;
|
|
17
|
-
/** Save a baseline to disk. */
|
|
18
|
-
save(agentBuildId: string, baseline: AgentBaseline): void;
|
|
19
|
-
/**
|
|
20
|
-
* Update the baseline with a completed session's feature data.
|
|
21
|
-
* Uses Welford's algorithm for incremental statistics.
|
|
22
|
-
*/
|
|
23
|
-
updateFromSession(agentBuildId: string, session: SessionProfile): void;
|
|
24
|
-
/** Check if a baseline exists for the given build ID. */
|
|
25
|
-
exists(agentBuildId: string): boolean;
|
|
26
|
-
private _filePath;
|
|
27
|
-
private _computeMaturity;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Compute agent build ID from system prompt, plugin list, and model.
|
|
31
|
-
* Excludes dynamic files (MEMORY.md) to avoid constant invalidation.
|
|
32
|
-
*/
|
|
33
|
-
export declare function computeAgentBuildId(systemPrompt: string, pluginList: string[], modelId: string): string;
|
package/dist/profiler-store.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File-based persistence for behavioural profiling baselines.
|
|
3
|
-
*
|
|
4
|
-
* Stores per-agent-build baselines as JSON files.
|
|
5
|
-
* Uses Welford's online algorithm — only running statistics are persisted,
|
|
6
|
-
* not raw data. Zero runtime dependencies.
|
|
7
|
-
*/
|
|
8
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { createHash } from "node:crypto";
|
|
11
|
-
import { updateBaseline } from "./profiler-analysis.js";
|
|
12
|
-
/**
|
|
13
|
-
* File-based store for agent behavioural baselines.
|
|
14
|
-
*/
|
|
15
|
-
export class BaselineStore {
|
|
16
|
-
_profileDir;
|
|
17
|
-
constructor(profileDir) {
|
|
18
|
-
// Expand ~ to actual home directory (Node.js doesn't do this automatically)
|
|
19
|
-
this._profileDir = profileDir.startsWith("~")
|
|
20
|
-
? join(process.env.HOME || "/tmp", profileDir.slice(1))
|
|
21
|
-
: profileDir;
|
|
22
|
-
}
|
|
23
|
-
/** Load a baseline for the given agent build ID. Returns null if not found. */
|
|
24
|
-
load(agentBuildId) {
|
|
25
|
-
const filePath = this._filePath(agentBuildId);
|
|
26
|
-
if (!existsSync(filePath))
|
|
27
|
-
return null;
|
|
28
|
-
try {
|
|
29
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
30
|
-
return JSON.parse(raw);
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// Corrupt file — start fresh
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/** Save a baseline to disk. */
|
|
38
|
-
save(agentBuildId, baseline) {
|
|
39
|
-
try {
|
|
40
|
-
mkdirSync(this._profileDir, { recursive: true });
|
|
41
|
-
const filePath = this._filePath(agentBuildId);
|
|
42
|
-
writeFileSync(filePath, JSON.stringify(baseline, null, 2), "utf-8");
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Best-effort — don't crash the plugin if profile dir is unwritable
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Update the baseline with a completed session's feature data.
|
|
50
|
-
* Uses Welford's algorithm for incremental statistics.
|
|
51
|
-
*/
|
|
52
|
-
updateFromSession(agentBuildId, session) {
|
|
53
|
-
let baseline = this.load(agentBuildId);
|
|
54
|
-
if (!baseline) {
|
|
55
|
-
baseline = {
|
|
56
|
-
agentBuildId,
|
|
57
|
-
sessionCount: 0,
|
|
58
|
-
maturity: "learning",
|
|
59
|
-
features: {},
|
|
60
|
-
toolProfile: [],
|
|
61
|
-
categoryProfile: [],
|
|
62
|
-
lastUpdated: Date.now(),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
// Update running stats for each turn's features
|
|
66
|
-
for (const turn of session.turns) {
|
|
67
|
-
baseline.features = updateBaseline(baseline.features, turn);
|
|
68
|
-
}
|
|
69
|
-
// Update tool and category profiles
|
|
70
|
-
const toolSet = new Set(baseline.toolProfile);
|
|
71
|
-
const catSet = new Set(baseline.categoryProfile);
|
|
72
|
-
for (const turn of session.turns) {
|
|
73
|
-
for (const tool of turn.toolNames)
|
|
74
|
-
toolSet.add(tool);
|
|
75
|
-
for (const cat of Object.keys(turn.entityCategoryCounts)) {
|
|
76
|
-
if (turn.entityCategoryCounts[cat] > 0)
|
|
77
|
-
catSet.add(cat);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
baseline.toolProfile = [...toolSet];
|
|
81
|
-
baseline.categoryProfile = [...catSet];
|
|
82
|
-
baseline.sessionCount += 1;
|
|
83
|
-
baseline.maturity = this._computeMaturity(baseline.sessionCount);
|
|
84
|
-
baseline.lastUpdated = Date.now();
|
|
85
|
-
this.save(agentBuildId, baseline);
|
|
86
|
-
}
|
|
87
|
-
/** Check if a baseline exists for the given build ID. */
|
|
88
|
-
exists(agentBuildId) {
|
|
89
|
-
return existsSync(this._filePath(agentBuildId));
|
|
90
|
-
}
|
|
91
|
-
_filePath(agentBuildId) {
|
|
92
|
-
// Sanitize build ID for filesystem safety
|
|
93
|
-
const safe = agentBuildId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
94
|
-
return join(this._profileDir, `${safe}.json`);
|
|
95
|
-
}
|
|
96
|
-
_computeMaturity(sessionCount) {
|
|
97
|
-
if (sessionCount >= 50)
|
|
98
|
-
return "mature";
|
|
99
|
-
if (sessionCount >= 5)
|
|
100
|
-
return "reliable";
|
|
101
|
-
return "learning";
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Compute agent build ID from system prompt, plugin list, and model.
|
|
106
|
-
* Excludes dynamic files (MEMORY.md) to avoid constant invalidation.
|
|
107
|
-
*/
|
|
108
|
-
export function computeAgentBuildId(systemPrompt, pluginList, modelId) {
|
|
109
|
-
const components = [
|
|
110
|
-
systemPrompt,
|
|
111
|
-
pluginList.sort().join(","),
|
|
112
|
-
modelId,
|
|
113
|
-
];
|
|
114
|
-
return createHash("sha256")
|
|
115
|
-
.update(components.join("\n"))
|
|
116
|
-
.digest("hex")
|
|
117
|
-
.slice(0, 16);
|
|
118
|
-
}
|
package/dist/profiler-types.d.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types for the behavioural profiling system (Track 3).
|
|
3
|
-
*
|
|
4
|
-
* Per-turn feature extraction, per-session aggregation,
|
|
5
|
-
* cross-session baseline accumulation, and anomaly detection.
|
|
6
|
-
*/
|
|
7
|
-
/** Per-turn feature vector — all computable sync, zero-dep. */
|
|
8
|
-
export interface FeatureVector {
|
|
9
|
-
/** Entity category → count for this turn. */
|
|
10
|
-
entityCategoryCounts: Record<string, number>;
|
|
11
|
-
/** Entities per 1,000 tokens. */
|
|
12
|
-
entityDensityPer1k: number;
|
|
13
|
-
/** Shannon entropy of entity category distribution. */
|
|
14
|
-
entityCategoryEntropy: number;
|
|
15
|
-
/** Number of tool calls this turn. */
|
|
16
|
-
toolCallCount: number;
|
|
17
|
-
/** Ordered list of tool names called this turn. */
|
|
18
|
-
toolNames: string[];
|
|
19
|
-
/** Count of imperative/directive verbs in the prompt. */
|
|
20
|
-
directiveVerbCount: number;
|
|
21
|
-
/** Count of questions (sentences ending in ?). */
|
|
22
|
-
questionCount: number;
|
|
23
|
-
/** Ratio of directives to questions (Infinity if questionCount=0). */
|
|
24
|
-
commandToQuestionRatio: number;
|
|
25
|
-
/** Response text length in characters. */
|
|
26
|
-
responseLength: number;
|
|
27
|
-
/** Fraction of request entities that appear in the response. */
|
|
28
|
-
entityEchoRate: number;
|
|
29
|
-
/** Jaccard similarity of word bigrams between this and previous turn (0-1). */
|
|
30
|
-
lexicalOverlapWithPrevious: number;
|
|
31
|
-
/** Fraction of words not seen in any previous turn this session. */
|
|
32
|
-
newVocabularyRate: number;
|
|
33
|
-
/** Turn index (0-based). */
|
|
34
|
-
turnIndex: number;
|
|
35
|
-
/** Timestamp (ms since epoch). */
|
|
36
|
-
timestamp: number;
|
|
37
|
-
/** Estimated token count (chars / 4). */
|
|
38
|
-
tokenEstimate: number;
|
|
39
|
-
/** Detected script/language of the input (e.g. "latin", "cjk", "cyrillic", "arabic"). */
|
|
40
|
-
detectedScript: string;
|
|
41
|
-
/** Fraction of non-Latin characters in the input (0-1). */
|
|
42
|
-
nonLatinRatio: number;
|
|
43
|
-
/** Number of image payloads in this turn's API request. */
|
|
44
|
-
imagePayloadCount: number;
|
|
45
|
-
/** Total size of image payloads in bytes (base64 decoded). */
|
|
46
|
-
imagePayloadBytes: number;
|
|
47
|
-
/** Input tokens sent to the LLM. */
|
|
48
|
-
inputTokens: number;
|
|
49
|
-
/** Output tokens generated by the LLM. */
|
|
50
|
-
outputTokens: number;
|
|
51
|
-
/** Tokens read from cache (high = stable context, drop = new content injected). */
|
|
52
|
-
cacheReadTokens: number;
|
|
53
|
-
/** Tokens written to cache (spike = large new context entering). */
|
|
54
|
-
cacheWriteTokens: number;
|
|
55
|
-
/** Cache hit ratio: cacheRead / inputTokens (0-1). */
|
|
56
|
-
cacheHitRatio: number;
|
|
57
|
-
}
|
|
58
|
-
/** Aggregate statistics for a session. */
|
|
59
|
-
export interface SessionAggregates {
|
|
60
|
-
/** Top 3 entity categories by frequency. */
|
|
61
|
-
dominantCategories: string[];
|
|
62
|
-
/** SHA256 of sorted unique tool names. */
|
|
63
|
-
toolSequenceFingerprint: string;
|
|
64
|
-
/** Mean entity density across turns. */
|
|
65
|
-
averageEntityDensity: number;
|
|
66
|
-
/** Mean directive verb count across turns. */
|
|
67
|
-
averageDirectiveVerbCount: number;
|
|
68
|
-
/** Mean response length across turns. */
|
|
69
|
-
averageResponseLength: number;
|
|
70
|
-
/** Mean lexical overlap across turns. */
|
|
71
|
-
averageLexicalOverlap: number;
|
|
72
|
-
/** Total turn count. */
|
|
73
|
-
turnCount: number;
|
|
74
|
-
}
|
|
75
|
-
/** A session's full profile. */
|
|
76
|
-
export interface SessionProfile {
|
|
77
|
-
sessionId: string;
|
|
78
|
-
agentBuildId: string;
|
|
79
|
-
startedAt: number;
|
|
80
|
-
turns: FeatureVector[];
|
|
81
|
-
aggregates: SessionAggregates;
|
|
82
|
-
}
|
|
83
|
-
/** Running statistics for a single feature (Welford's algorithm). */
|
|
84
|
-
export interface RunningStats {
|
|
85
|
-
mean: number;
|
|
86
|
-
m2: number;
|
|
87
|
-
n: number;
|
|
88
|
-
min: number;
|
|
89
|
-
max: number;
|
|
90
|
-
}
|
|
91
|
-
/** Baseline for a feature set derived from multiple sessions. */
|
|
92
|
-
export interface BaselineFeatureStats {
|
|
93
|
-
[featureName: string]: RunningStats;
|
|
94
|
-
}
|
|
95
|
-
/** Baseline maturity level. */
|
|
96
|
-
export type BaselineMaturity = "learning" | "reliable" | "mature";
|
|
97
|
-
/** Agent baseline — cross-session profile keyed by build ID. */
|
|
98
|
-
export interface AgentBaseline {
|
|
99
|
-
agentBuildId: string;
|
|
100
|
-
sessionCount: number;
|
|
101
|
-
maturity: BaselineMaturity;
|
|
102
|
-
features: BaselineFeatureStats;
|
|
103
|
-
toolProfile: string[];
|
|
104
|
-
categoryProfile: string[];
|
|
105
|
-
lastUpdated: number;
|
|
106
|
-
}
|
|
107
|
-
/** Named anomaly types modelled on IDS alert categories. */
|
|
108
|
-
export declare enum AnomalyType {
|
|
109
|
-
ENTITY_CATEGORY_SHIFT = "entity_category_shift",
|
|
110
|
-
ENTITY_DENSITY_SPIKE = "entity_density_spike",
|
|
111
|
-
TOOL_OUTSIDE_PROFILE = "tool_outside_profile",
|
|
112
|
-
TOPIC_DISCONTINUITY = "topic_discontinuity",
|
|
113
|
-
CREDENTIAL_EMERGENCE = "credential_emergence",
|
|
114
|
-
EXFILTRATION_PATTERN = "exfiltration_pattern"
|
|
115
|
-
}
|
|
116
|
-
/** An anomaly alert emitted when profiler detects deviation. */
|
|
117
|
-
export interface AnomalyAlert {
|
|
118
|
-
type: AnomalyType;
|
|
119
|
-
severity: "info" | "warning" | "critical";
|
|
120
|
-
feature: string;
|
|
121
|
-
observedValue: number;
|
|
122
|
-
baselineMean: number;
|
|
123
|
-
baselineStddev: number;
|
|
124
|
-
zScore: number;
|
|
125
|
-
turnIndex: number;
|
|
126
|
-
timestamp: number;
|
|
127
|
-
description: string;
|
|
128
|
-
}
|
package/dist/profiler-types.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types for the behavioural profiling system (Track 3).
|
|
3
|
-
*
|
|
4
|
-
* Per-turn feature extraction, per-session aggregation,
|
|
5
|
-
* cross-session baseline accumulation, and anomaly detection.
|
|
6
|
-
*/
|
|
7
|
-
/** Named anomaly types modelled on IDS alert categories. */
|
|
8
|
-
export var AnomalyType;
|
|
9
|
-
(function (AnomalyType) {
|
|
10
|
-
AnomalyType["ENTITY_CATEGORY_SHIFT"] = "entity_category_shift";
|
|
11
|
-
AnomalyType["ENTITY_DENSITY_SPIKE"] = "entity_density_spike";
|
|
12
|
-
AnomalyType["TOOL_OUTSIDE_PROFILE"] = "tool_outside_profile";
|
|
13
|
-
AnomalyType["TOPIC_DISCONTINUITY"] = "topic_discontinuity";
|
|
14
|
-
AnomalyType["CREDENTIAL_EMERGENCE"] = "credential_emergence";
|
|
15
|
-
AnomalyType["EXFILTRATION_PATTERN"] = "exfiltration_pattern";
|
|
16
|
-
})(AnomalyType || (AnomalyType = {}));
|
package/dist/profiler.d.ts
DELETED
|
@@ -1,81 +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 { AnomalyAlert, FeatureVector, SessionProfile } from "./profiler-types.js";
|
|
10
|
-
import { BaselineStore } from "./profiler-store.js";
|
|
11
|
-
/** Configuration for the profiler. */
|
|
12
|
-
export interface ProfilerConfig {
|
|
13
|
-
mode: "learning" | "active" | "strict";
|
|
14
|
-
sigma: number;
|
|
15
|
-
minBaseline: number;
|
|
16
|
-
profileDir: string;
|
|
17
|
-
}
|
|
18
|
-
/** Tool call info passed from hooks layer. */
|
|
19
|
-
export interface ToolCallInfo {
|
|
20
|
-
name: string;
|
|
21
|
-
arguments?: Record<string, unknown>;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Behavioural profiler. One instance per session.
|
|
25
|
-
*/
|
|
26
|
-
export declare class BehaviouralProfiler {
|
|
27
|
-
private _config;
|
|
28
|
-
private _store;
|
|
29
|
-
private _sessionId;
|
|
30
|
-
private _agentBuildId;
|
|
31
|
-
private _turns;
|
|
32
|
-
private _alerts;
|
|
33
|
-
private _previousTurnBigrams;
|
|
34
|
-
private _allSessionWords;
|
|
35
|
-
private _startedAt;
|
|
36
|
-
private _pendingRequest;
|
|
37
|
-
constructor(config: ProfilerConfig, store: BaselineStore);
|
|
38
|
-
/** Set the agent build ID (from system prompt hash). */
|
|
39
|
-
setAgentBuildId(buildId: string): void;
|
|
40
|
-
/**
|
|
41
|
-
* Extract features from the outbound request text.
|
|
42
|
-
* Called after obfuscation, before sending to LLM.
|
|
43
|
-
*/
|
|
44
|
-
extractRequestFeatures(text: string, entityCategoryCounts: Record<string, number>, toolCalls?: ToolCallInfo[], imagePayloads?: {
|
|
45
|
-
count: number;
|
|
46
|
-
totalBytes: number;
|
|
47
|
-
}): void;
|
|
48
|
-
/**
|
|
49
|
-
* Complete the feature vector with response-side features.
|
|
50
|
-
* Called after deobfuscation of the LLM response.
|
|
51
|
-
*/
|
|
52
|
-
extractResponseFeatures(responseText: string, requestEntities: string[], cacheUsage?: {
|
|
53
|
-
inputTokens: number;
|
|
54
|
-
outputTokens: number;
|
|
55
|
-
cacheReadTokens: number;
|
|
56
|
-
cacheWriteTokens: number;
|
|
57
|
-
}): FeatureVector | null;
|
|
58
|
-
/**
|
|
59
|
-
* Analyze a completed turn against the baseline.
|
|
60
|
-
* Returns anomaly alerts (empty if in learning mode or insufficient baseline).
|
|
61
|
-
*/
|
|
62
|
-
analyzeTurn(features: FeatureVector): AnomalyAlert[];
|
|
63
|
-
/**
|
|
64
|
-
* Finalize the session: compute aggregates, update baseline store.
|
|
65
|
-
*/
|
|
66
|
-
finalizeSession(): SessionProfile;
|
|
67
|
-
/** Get all anomaly alerts from this session. */
|
|
68
|
-
getAlerts(): readonly AnomalyAlert[];
|
|
69
|
-
/** Get LLM cache usage summary for the current session. */
|
|
70
|
-
getCacheStats(): {
|
|
71
|
-
totalInput: number;
|
|
72
|
-
totalOutput: number;
|
|
73
|
-
totalCacheRead: number;
|
|
74
|
-
totalCacheWrite: number;
|
|
75
|
-
hitRatio: number;
|
|
76
|
-
turns: number;
|
|
77
|
-
};
|
|
78
|
-
/** Get current session profile (without finalizing). */
|
|
79
|
-
getSessionProfile(): SessionProfile;
|
|
80
|
-
private _computeAggregates;
|
|
81
|
-
}
|