agentshield-sdk 7.2.0 → 7.3.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.
@@ -0,0 +1,246 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield - Attack Replay Platform
5
+ *
6
+ * Record real attacks, replay them against updated defenses,
7
+ * generate mutations, and track defense improvements over time.
8
+ * Like BurpSuite's Repeater but for AI agent security.
9
+ *
10
+ * @module attack-replay
11
+ */
12
+
13
+ const crypto = require('crypto');
14
+ const { scanText } = require('./detector-core');
15
+
16
+ /**
17
+ * Records and replays attacks against defenses.
18
+ */
19
+ class AttackReplayEngine {
20
+ /**
21
+ * @param {object} [options]
22
+ * @param {number} [options.maxRecordings=10000] - Max stored recordings.
23
+ * @param {string} [options.sensitivity='high'] - Detection sensitivity.
24
+ */
25
+ constructor(options = {}) {
26
+ this.maxRecordings = options.maxRecordings || 10000;
27
+ this.sensitivity = options.sensitivity || 'high';
28
+ this._recordings = [];
29
+ this._replayHistory = [];
30
+ }
31
+
32
+ /**
33
+ * Record an attack for later replay.
34
+ * @param {object} attack
35
+ * @param {string} attack.text - The attack text.
36
+ * @param {string} [attack.category] - Attack category.
37
+ * @param {string} [attack.source] - Where the attack came from.
38
+ * @param {boolean} [attack.wasDetected] - Whether it was caught originally.
39
+ * @param {object} [attack.metadata] - Additional metadata.
40
+ * @returns {object} The recording with ID and timestamp.
41
+ */
42
+ record(attack) {
43
+ const recording = {
44
+ id: crypto.randomBytes(8).toString('hex'),
45
+ text: attack.text,
46
+ category: attack.category || 'unknown',
47
+ source: attack.source || 'manual',
48
+ wasDetected: attack.wasDetected != null ? attack.wasDetected : null,
49
+ metadata: attack.metadata || {},
50
+ timestamp: Date.now(),
51
+ hash: crypto.createHash('sha256').update(attack.text).digest('hex').substring(0, 16),
52
+ };
53
+
54
+ this._recordings.push(recording);
55
+ if (this._recordings.length > this.maxRecordings) {
56
+ this._recordings = this._recordings.slice(-Math.floor(this.maxRecordings * 0.75));
57
+ }
58
+
59
+ return recording;
60
+ }
61
+
62
+ /**
63
+ * Replay a single recording against current defenses.
64
+ * @param {string} recordingId
65
+ * @returns {object} Replay result with detection status.
66
+ */
67
+ replay(recordingId) {
68
+ const recording = this._recordings.find(r => r.id === recordingId);
69
+ if (!recording) return null;
70
+
71
+ const result = scanText(recording.text, { sensitivity: this.sensitivity });
72
+ const detected = result.threats.length > 0;
73
+
74
+ const replayResult = {
75
+ recordingId,
76
+ text: recording.text.substring(0, 100),
77
+ category: recording.category,
78
+ originallyDetected: recording.wasDetected,
79
+ nowDetected: detected,
80
+ improved: !recording.wasDetected && detected,
81
+ regressed: recording.wasDetected && !detected,
82
+ threats: result.threats,
83
+ replayedAt: Date.now(),
84
+ };
85
+
86
+ this._replayHistory.push(replayResult);
87
+ return replayResult;
88
+ }
89
+
90
+ /**
91
+ * Replay ALL recordings against current defenses.
92
+ * Shows what improved, regressed, or stayed the same.
93
+ * @returns {object} Aggregate replay results.
94
+ */
95
+ replayAll() {
96
+ const results = [];
97
+ let improved = 0;
98
+ let regressed = 0;
99
+ let unchanged = 0;
100
+ let nowDetected = 0;
101
+ let nowMissed = 0;
102
+
103
+ for (const recording of this._recordings) {
104
+ const result = scanText(recording.text, { sensitivity: this.sensitivity });
105
+ const detected = result.threats.length > 0;
106
+
107
+ if (recording.wasDetected === false && detected) improved++;
108
+ else if (recording.wasDetected === true && !detected) regressed++;
109
+ else unchanged++;
110
+
111
+ if (detected) nowDetected++;
112
+ else nowMissed++;
113
+
114
+ results.push({
115
+ id: recording.id,
116
+ category: recording.category,
117
+ originally: recording.wasDetected,
118
+ now: detected,
119
+ status: recording.wasDetected === false && detected ? 'improved'
120
+ : recording.wasDetected === true && !detected ? 'regressed'
121
+ : 'unchanged',
122
+ });
123
+ }
124
+
125
+ return {
126
+ total: this._recordings.length,
127
+ nowDetected,
128
+ nowMissed,
129
+ improved,
130
+ regressed,
131
+ unchanged,
132
+ detectionRate: this._recordings.length > 0
133
+ ? (nowDetected / this._recordings.length * 100).toFixed(1) + '%'
134
+ : '0%',
135
+ results,
136
+ replayedAt: Date.now(),
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Find recordings that currently evade detection (for targeted hardening).
142
+ * @returns {Array} Recordings that are not detected by current defenses.
143
+ */
144
+ findEvasions() {
145
+ const evasions = [];
146
+ for (const recording of this._recordings) {
147
+ const result = scanText(recording.text, { sensitivity: this.sensitivity });
148
+ if (result.threats.length === 0) {
149
+ evasions.push({
150
+ id: recording.id,
151
+ text: recording.text,
152
+ category: recording.category,
153
+ source: recording.source,
154
+ hash: recording.hash,
155
+ });
156
+ }
157
+ }
158
+ return evasions;
159
+ }
160
+
161
+ /**
162
+ * Export recordings for sharing or archival.
163
+ * @returns {string} JSON string.
164
+ */
165
+ export() {
166
+ return JSON.stringify({
167
+ version: '1.0',
168
+ exportedAt: Date.now(),
169
+ recordings: this._recordings,
170
+ count: this._recordings.length,
171
+ }, null, 2);
172
+ }
173
+
174
+ /**
175
+ * Import recordings from export.
176
+ * @param {string} json
177
+ * @returns {number} Number of recordings imported.
178
+ */
179
+ import(json) {
180
+ const data = JSON.parse(json);
181
+ const recordings = data.recordings || [];
182
+ this._recordings.push(...recordings);
183
+ if (this._recordings.length > this.maxRecordings) {
184
+ this._recordings = this._recordings.slice(-this.maxRecordings);
185
+ }
186
+ return recordings.length;
187
+ }
188
+
189
+ /**
190
+ * Get all recordings.
191
+ * @param {string} [category] - Filter by category.
192
+ * @returns {Array}
193
+ */
194
+ getRecordings(category) {
195
+ if (category) return this._recordings.filter(r => r.category === category);
196
+ return [...this._recordings];
197
+ }
198
+
199
+ /**
200
+ * Get replay history.
201
+ * @returns {Array}
202
+ */
203
+ getHistory() {
204
+ return [...this._replayHistory];
205
+ }
206
+
207
+ /**
208
+ * Get stats.
209
+ * @returns {object}
210
+ */
211
+ getStats() {
212
+ const categories = {};
213
+ for (const r of this._recordings) {
214
+ categories[r.category] = (categories[r.category] || 0) + 1;
215
+ }
216
+ return {
217
+ totalRecordings: this._recordings.length,
218
+ totalReplays: this._replayHistory.length,
219
+ categories,
220
+ };
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Compare defense effectiveness across two time periods.
226
+ * @param {object} before - replayAll() result from before.
227
+ * @param {object} after - replayAll() result from after.
228
+ * @returns {object} Comparison.
229
+ */
230
+ function compareDefenses(before, after) {
231
+ return {
232
+ detectionRateBefore: before.detectionRate,
233
+ detectionRateAfter: after.detectionRate,
234
+ improvement: parseFloat(after.detectionRate) - parseFloat(before.detectionRate),
235
+ newlyDetected: after.improved,
236
+ regressions: after.regressed,
237
+ verdict: parseFloat(after.detectionRate) > parseFloat(before.detectionRate) ? 'improved'
238
+ : parseFloat(after.detectionRate) < parseFloat(before.detectionRate) ? 'regressed'
239
+ : 'unchanged',
240
+ };
241
+ }
242
+
243
+ module.exports = {
244
+ AttackReplayEngine,
245
+ compareDefenses,
246
+ };