agentshield-sdk 7.3.0 → 7.4.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +36 -7
  3. package/package.json +7 -3
  4. package/src/agent-protocol.js +4 -0
  5. package/src/allowlist.js +605 -603
  6. package/src/audit-streaming.js +486 -469
  7. package/src/audit.js +1 -1
  8. package/src/behavior-profiling.js +299 -289
  9. package/src/behavioral-dna.js +4 -9
  10. package/src/canary.js +273 -271
  11. package/src/compliance.js +619 -617
  12. package/src/confidence-tuning.js +328 -324
  13. package/src/context-scoring.js +362 -360
  14. package/src/cost-optimizer.js +1024 -1024
  15. package/src/detector-core.js +186 -0
  16. package/src/distributed.js +5 -1
  17. package/src/embedding.js +310 -307
  18. package/src/herd-immunity.js +12 -12
  19. package/src/honeypot.js +332 -328
  20. package/src/integrations.js +1 -2
  21. package/src/intent-firewall.js +14 -14
  22. package/src/llm-redteam.js +678 -670
  23. package/src/main.js +10 -0
  24. package/src/middleware.js +5 -2
  25. package/src/model-fingerprint.js +1059 -1042
  26. package/src/multi-agent-trust.js +459 -453
  27. package/src/multi-agent.js +1 -1
  28. package/src/normalizer.js +734 -0
  29. package/src/pii.js +4 -0
  30. package/src/policy-dsl.js +775 -775
  31. package/src/presets.js +409 -409
  32. package/src/production.js +22 -9
  33. package/src/redteam.js +475 -475
  34. package/src/response-handler.js +436 -429
  35. package/src/scanners.js +358 -357
  36. package/src/self-healing.js +368 -363
  37. package/src/semantic.js +339 -339
  38. package/src/shield-score.js +250 -250
  39. package/src/sso-saml.js +8 -4
  40. package/src/testing.js +24 -2
  41. package/src/tool-guard.js +412 -412
  42. package/src/watermark.js +242 -235
  43. package/src/worker-scanner.js +608 -601
package/src/watermark.js CHANGED
@@ -1,235 +1,242 @@
1
- 'use strict';
2
-
3
- /**
4
- * Output Watermarking (#44) and Differential Privacy (#46)
5
- *
6
- * - Watermarking: Embed invisible watermarks in agent outputs for tracing.
7
- * - Differential Privacy: Add noise to stored conversation data.
8
- */
9
-
10
- const crypto = require('crypto');
11
-
12
- // =========================================================================
13
- // OUTPUT WATERMARKING
14
- // =========================================================================
15
-
16
- /**
17
- * Zero-width characters used for binary watermark encoding.
18
- */
19
- const WM_ZERO = '\u200B'; // zero-width space = 0
20
- const WM_ONE = '\u200C'; // zero-width non-joiner = 1
21
- const WM_START = '\u200D'; // zero-width joiner = start marker
22
- const WM_END = '\uFEFF'; // byte order mark = end marker
23
-
24
- class OutputWatermark {
25
- /**
26
- * @param {object} [options]
27
- * @param {string} [options.secret] - Secret key for HMAC signing.
28
- * @param {boolean} [options.includeTimestamp=true] - Include timestamp in watermark.
29
- */
30
- constructor(options = {}) {
31
- this.secret = options.secret || crypto.randomBytes(16).toString('hex');
32
- this.includeTimestamp = options.includeTimestamp !== undefined ? options.includeTimestamp : true;
33
- }
34
-
35
- /**
36
- * Embeds an invisible watermark in text.
37
- *
38
- * @param {string} text - The text to watermark.
39
- * @param {object} metadata - Data to encode in the watermark.
40
- * @param {string} [metadata.agentId] - Agent identifier.
41
- * @param {string} [metadata.sessionId] - Session identifier.
42
- * @returns {string} Watermarked text.
43
- */
44
- embed(text, metadata = {}) {
45
- if (!text) return text;
46
-
47
- const payload = {
48
- ...metadata,
49
- ts: this.includeTimestamp ? Date.now() : undefined
50
- };
51
-
52
- // Create signed payload
53
- const payloadStr = JSON.stringify(payload);
54
- const signature = crypto.createHmac('sha256', this.secret)
55
- .update(payloadStr)
56
- .digest('hex')
57
- .substring(0, 8);
58
-
59
- const data = `${payloadStr}|${signature}`;
60
-
61
- // Encode to binary using zero-width characters
62
- const binary = this._textToBinary(data);
63
- const watermark = WM_START + binary + WM_END;
64
-
65
- // Insert watermark in the middle of the text to be less detectable
66
- const midpoint = Math.floor(text.length / 2);
67
- // Find a space near the midpoint
68
- let insertAt = text.indexOf(' ', midpoint);
69
- if (insertAt === -1) insertAt = midpoint;
70
-
71
- return text.slice(0, insertAt) + watermark + text.slice(insertAt);
72
- }
73
-
74
- /**
75
- * Extracts a watermark from text.
76
- *
77
- * @param {string} text - Text that may contain a watermark.
78
- * @returns {object} { found: boolean, metadata?: object, verified: boolean }
79
- */
80
- extract(text) {
81
- if (!text) return { found: false };
82
-
83
- const startIdx = text.indexOf(WM_START);
84
- const endIdx = text.indexOf(WM_END, startIdx);
85
-
86
- if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
87
- return { found: false };
88
- }
89
-
90
- const binary = text.slice(startIdx + 1, endIdx);
91
- const data = this._binaryToText(binary);
92
-
93
- if (!data) return { found: false };
94
-
95
- const pipeIdx = data.lastIndexOf('|');
96
- if (pipeIdx === -1) return { found: false };
97
-
98
- const payloadStr = data.substring(0, pipeIdx);
99
- const signature = data.substring(pipeIdx + 1);
100
-
101
- // Verify signature
102
- const expectedSig = crypto.createHmac('sha256', this.secret)
103
- .update(payloadStr)
104
- .digest('hex')
105
- .substring(0, 8);
106
-
107
- const verified = signature === expectedSig;
108
-
109
- let metadata = null;
110
- try {
111
- metadata = JSON.parse(payloadStr);
112
- } catch (e) {
113
- return { found: true, verified: false, raw: payloadStr };
114
- }
115
-
116
- return { found: true, metadata, verified };
117
- }
118
-
119
- /**
120
- * Removes watermark from text.
121
- *
122
- * @param {string} text
123
- * @returns {string} Clean text.
124
- */
125
- strip(text) {
126
- if (!text) return text;
127
- return text.replace(/[\u200B\u200C\u200D\uFEFF]/g, '');
128
- }
129
-
130
- /** @private */
131
- _textToBinary(text) {
132
- let binary = '';
133
- for (let i = 0; i < text.length; i++) {
134
- const charCode = text.charCodeAt(i);
135
- const bits = charCode.toString(2).padStart(8, '0');
136
- for (const bit of bits) {
137
- binary += bit === '0' ? WM_ZERO : WM_ONE;
138
- }
139
- }
140
- return binary;
141
- }
142
-
143
- /** @private */
144
- _binaryToText(binary) {
145
- try {
146
- let text = '';
147
- let bits = '';
148
- for (const char of binary) {
149
- if (char === WM_ZERO) bits += '0';
150
- else if (char === WM_ONE) bits += '1';
151
- else continue;
152
-
153
- if (bits.length === 8) {
154
- text += String.fromCharCode(parseInt(bits, 2));
155
- bits = '';
156
- }
157
- }
158
- return text;
159
- } catch (e) {
160
- return null;
161
- }
162
- }
163
- }
164
-
165
- // =========================================================================
166
- // DIFFERENTIAL PRIVACY FOR AGENT MEMORY
167
- // =========================================================================
168
-
169
- class DifferentialPrivacy {
170
- /**
171
- * Adds noise to stored conversation data so individual user data
172
- * can't be extracted even if the memory store is compromised.
173
- *
174
- * @param {object} [options]
175
- * @param {number} [options.epsilon=1.0] - Privacy budget. Lower = more private, noisier.
176
- * @param {number} [options.redactProbability=0.1] - Probability of redacting a token.
177
- */
178
- constructor(options = {}) {
179
- this.epsilon = options.epsilon || 1.0;
180
- this.redactProbability = options.redactProbability || 0.1;
181
- }
182
-
183
- /**
184
- * Sanitizes text for storage by adding noise.
185
- *
186
- * @param {string} text - Text to sanitize.
187
- * @returns {object} { sanitized: string, tokensRedacted: number }
188
- */
189
- sanitize(text) {
190
- if (!text) return { sanitized: text, tokensRedacted: 0 };
191
-
192
- const words = text.split(/\s+/);
193
- let tokensRedacted = 0;
194
-
195
- const sanitized = words.map(word => {
196
- // Higher epsilon = less noise (more utility)
197
- const threshold = this.redactProbability / this.epsilon;
198
-
199
- if (Math.random() < threshold) {
200
- tokensRedacted++;
201
- return '[REDACTED]';
202
- }
203
-
204
- // For numbers, add Laplacian noise
205
- if (/^\d+\.?\d*$/.test(word)) {
206
- const num = parseFloat(word);
207
- const noise = this._laplacianNoise(1 / this.epsilon);
208
- const noisy = Math.round((num + noise) * 100) / 100;
209
- return String(noisy);
210
- }
211
-
212
- return word;
213
- });
214
-
215
- return {
216
- sanitized: sanitized.join(' '),
217
- tokensRedacted
218
- };
219
- }
220
-
221
- /**
222
- * Generates Laplacian noise for numeric privacy.
223
- * @private
224
- * @param {number} scale - Scale parameter (b = sensitivity/epsilon).
225
- * @returns {number}
226
- */
227
- _laplacianNoise(scale) {
228
- // Use crypto for proper randomness instead of Math.random()
229
- const bytes = crypto.randomBytes(4);
230
- const u = (bytes.readUInt32BE(0) / 0xFFFFFFFF) - 0.5;
231
- return -scale * Math.sign(u) * Math.log(1 - 2 * Math.abs(u));
232
- }
233
- }
234
-
235
- module.exports = { OutputWatermark, DifferentialPrivacy };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Output Watermarking (#44) and Differential Privacy (#46)
5
+ *
6
+ * - Watermarking: Embed invisible watermarks in agent outputs for tracing.
7
+ * - Differential Privacy: Add noise to stored conversation data.
8
+ */
9
+
10
+ const crypto = require('crypto');
11
+
12
+ // =========================================================================
13
+ // OUTPUT WATERMARKING
14
+ // =========================================================================
15
+
16
+ /**
17
+ * Zero-width characters used for binary watermark encoding.
18
+ */
19
+ const WM_ZERO = '\u200B'; // zero-width space = 0
20
+ const WM_ONE = '\u200C'; // zero-width non-joiner = 1
21
+ const WM_START = '\u200D'; // zero-width joiner = start marker
22
+ const WM_END = '\uFEFF'; // byte order mark = end marker
23
+
24
+ class OutputWatermark {
25
+ /**
26
+ * @param {object} [options]
27
+ * @param {string} [options.secret] - Secret key for HMAC signing.
28
+ * @param {boolean} [options.includeTimestamp=true] - Include timestamp in watermark.
29
+ */
30
+ constructor(options = {}) {
31
+ this.secret = options.secret || crypto.randomBytes(16).toString('hex');
32
+ this.includeTimestamp = options.includeTimestamp !== undefined ? options.includeTimestamp : true;
33
+ }
34
+
35
+ /**
36
+ * Embeds an invisible watermark in text.
37
+ *
38
+ * @param {string} text - The text to watermark.
39
+ * @param {object} metadata - Data to encode in the watermark.
40
+ * @param {string} [metadata.agentId] - Agent identifier.
41
+ * @param {string} [metadata.sessionId] - Session identifier.
42
+ * @returns {string} Watermarked text.
43
+ */
44
+ embed(text, metadata = {}) {
45
+ if (!text) return text;
46
+
47
+ const payload = {
48
+ ...metadata,
49
+ ts: this.includeTimestamp ? Date.now() : undefined
50
+ };
51
+
52
+ // Create signed payload
53
+ const payloadStr = JSON.stringify(payload);
54
+ const signature = crypto.createHmac('sha256', this.secret)
55
+ .update(payloadStr)
56
+ .digest('hex')
57
+ .substring(0, 8);
58
+
59
+ const data = `${payloadStr}|${signature}`;
60
+
61
+ // Encode to binary using zero-width characters
62
+ const binary = this._textToBinary(data);
63
+ const watermark = WM_START + binary + WM_END;
64
+
65
+ // Insert watermark in the middle of the text to be less detectable
66
+ const midpoint = Math.floor(text.length / 2);
67
+ // Find a space near the midpoint
68
+ let insertAt = text.indexOf(' ', midpoint);
69
+ if (insertAt === -1) insertAt = midpoint;
70
+
71
+ return text.slice(0, insertAt) + watermark + text.slice(insertAt);
72
+ }
73
+
74
+ /**
75
+ * Extracts a watermark from text.
76
+ *
77
+ * @param {string} text - Text that may contain a watermark.
78
+ * @returns {object} { found: boolean, metadata?: object, verified: boolean }
79
+ */
80
+ extract(text) {
81
+ if (!text) return { found: false };
82
+
83
+ const startIdx = text.indexOf(WM_START);
84
+ const endIdx = text.indexOf(WM_END, startIdx);
85
+
86
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
87
+ return { found: false };
88
+ }
89
+
90
+ const binary = text.slice(startIdx + 1, endIdx);
91
+ const data = this._binaryToText(binary);
92
+
93
+ if (!data) return { found: false };
94
+
95
+ const pipeIdx = data.lastIndexOf('|');
96
+ if (pipeIdx === -1) return { found: false };
97
+
98
+ const payloadStr = data.substring(0, pipeIdx);
99
+ const signature = data.substring(pipeIdx + 1);
100
+
101
+ // Verify signature
102
+ const expectedSig = crypto.createHmac('sha256', this.secret)
103
+ .update(payloadStr)
104
+ .digest('hex')
105
+ .substring(0, 8);
106
+
107
+ let verified = false;
108
+ try {
109
+ verified = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSig));
110
+ } catch (e) {
111
+ verified = false;
112
+ }
113
+
114
+ let metadata = null;
115
+ try {
116
+ metadata = JSON.parse(payloadStr);
117
+ } catch (e) {
118
+ return { found: true, verified: false, raw: payloadStr };
119
+ }
120
+
121
+ return { found: true, metadata, verified };
122
+ }
123
+
124
+ /**
125
+ * Removes watermark from text.
126
+ *
127
+ * @param {string} text
128
+ * @returns {string} Clean text.
129
+ */
130
+ strip(text) {
131
+ if (!text) return text;
132
+ return text.replace(/[\u200B\u200C\u200D\uFEFF]/g, '');
133
+ }
134
+
135
+ /** @private */
136
+ _textToBinary(text) {
137
+ let binary = '';
138
+ for (let i = 0; i < text.length; i++) {
139
+ const charCode = text.charCodeAt(i);
140
+ const bits = charCode.toString(2).padStart(16, '0');
141
+ for (const bit of bits) {
142
+ binary += bit === '0' ? WM_ZERO : WM_ONE;
143
+ }
144
+ }
145
+ return binary;
146
+ }
147
+
148
+ /** @private */
149
+ _binaryToText(binary) {
150
+ try {
151
+ let text = '';
152
+ let bits = '';
153
+ for (const char of binary) {
154
+ if (char === WM_ZERO) bits += '0';
155
+ else if (char === WM_ONE) bits += '1';
156
+ else continue;
157
+
158
+ if (bits.length === 16) {
159
+ text += String.fromCharCode(parseInt(bits, 2));
160
+ bits = '';
161
+ }
162
+ }
163
+ return text;
164
+ } catch (e) {
165
+ return null;
166
+ }
167
+ }
168
+ }
169
+
170
+ // =========================================================================
171
+ // DIFFERENTIAL PRIVACY FOR AGENT MEMORY
172
+ // =========================================================================
173
+
174
+ class DifferentialPrivacy {
175
+ /**
176
+ * Adds noise to stored conversation data so individual user data
177
+ * can't be extracted even if the memory store is compromised.
178
+ *
179
+ * @param {object} [options]
180
+ * @param {number} [options.epsilon=1.0] - Privacy budget. Lower = more private, noisier.
181
+ * @param {number} [options.redactProbability=0.1] - Probability of redacting a token.
182
+ */
183
+ constructor(options = {}) {
184
+ this.epsilon = options.epsilon || 1.0;
185
+ this.redactProbability = options.redactProbability || 0.1;
186
+ }
187
+
188
+ /**
189
+ * Sanitizes text for storage by adding noise.
190
+ *
191
+ * @param {string} text - Text to sanitize.
192
+ * @returns {object} { sanitized: string, tokensRedacted: number }
193
+ */
194
+ sanitize(text) {
195
+ if (!text) return { sanitized: text, tokensRedacted: 0 };
196
+
197
+ const words = text.split(/\s+/);
198
+ let tokensRedacted = 0;
199
+
200
+ const sanitized = words.map(word => {
201
+ // Higher epsilon = less noise (more utility)
202
+ const threshold = this.redactProbability / this.epsilon;
203
+
204
+ if (Math.random() < threshold) {
205
+ tokensRedacted++;
206
+ return '[REDACTED]';
207
+ }
208
+
209
+ // For numbers, add Laplacian noise
210
+ if (/^\d+\.?\d*$/.test(word)) {
211
+ const num = parseFloat(word);
212
+ const noise = this._laplacianNoise(1 / this.epsilon);
213
+ const noisy = Math.round((num + noise) * 100) / 100;
214
+ return String(noisy);
215
+ }
216
+
217
+ return word;
218
+ });
219
+
220
+ return {
221
+ sanitized: sanitized.join(' '),
222
+ tokensRedacted
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Generates Laplacian noise for numeric privacy.
228
+ * @private
229
+ * @param {number} scale - Scale parameter (b = sensitivity/epsilon).
230
+ * @returns {number}
231
+ */
232
+ _laplacianNoise(scale) {
233
+ // Use crypto for proper randomness instead of Math.random()
234
+ const bytes = crypto.randomBytes(4);
235
+ const u = (bytes.readUInt32BE(0) / 0xFFFFFFFF) - 0.5;
236
+ // Clamp to avoid Math.log(0) which produces -Infinity
237
+ const absU = Math.min(Math.abs(u), 0.4999999);
238
+ return -scale * Math.sign(u) * Math.log(1 - 2 * absU);
239
+ }
240
+ }
241
+
242
+ module.exports = { OutputWatermark, DifferentialPrivacy };