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.
- package/CHANGELOG.md +35 -0
- package/README.md +36 -7
- package/package.json +7 -3
- package/src/agent-protocol.js +4 -0
- package/src/allowlist.js +605 -603
- package/src/audit-streaming.js +486 -469
- package/src/audit.js +1 -1
- package/src/behavior-profiling.js +299 -289
- package/src/behavioral-dna.js +4 -9
- package/src/canary.js +273 -271
- package/src/compliance.js +619 -617
- package/src/confidence-tuning.js +328 -324
- package/src/context-scoring.js +362 -360
- package/src/cost-optimizer.js +1024 -1024
- package/src/detector-core.js +186 -0
- package/src/distributed.js +5 -1
- package/src/embedding.js +310 -307
- package/src/herd-immunity.js +12 -12
- package/src/honeypot.js +332 -328
- package/src/integrations.js +1 -2
- package/src/intent-firewall.js +14 -14
- package/src/llm-redteam.js +678 -670
- package/src/main.js +10 -0
- package/src/middleware.js +5 -2
- package/src/model-fingerprint.js +1059 -1042
- package/src/multi-agent-trust.js +459 -453
- package/src/multi-agent.js +1 -1
- package/src/normalizer.js +734 -0
- package/src/pii.js +4 -0
- package/src/policy-dsl.js +775 -775
- package/src/presets.js +409 -409
- package/src/production.js +22 -9
- package/src/redteam.js +475 -475
- package/src/response-handler.js +436 -429
- package/src/scanners.js +358 -357
- package/src/self-healing.js +368 -363
- package/src/semantic.js +339 -339
- package/src/shield-score.js +250 -250
- package/src/sso-saml.js +8 -4
- package/src/testing.js +24 -2
- package/src/tool-guard.js +412 -412
- package/src/watermark.js +242 -235
- 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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
*
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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 };
|