agentshield-sdk 8.0.0 → 11.0.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 +19 -0
- package/LICENSE +21 -21
- package/README.md +257 -50
- package/bin/agentshield-audit +51 -0
- package/package.json +7 -10
- package/src/adaptive.js +330 -330
- package/src/alert-tuning.js +480 -480
- package/src/attack-surface.js +408 -0
- package/src/audit-streaming.js +1 -1
- package/src/badges.js +196 -196
- package/src/behavioral-dna.js +12 -0
- package/src/canary.js +2 -3
- package/src/certification.js +563 -563
- package/src/circuit-breaker.js +2 -2
- package/src/confused-deputy.js +4 -0
- package/src/continuous-security.js +237 -0
- package/src/conversation.js +494 -494
- package/src/cross-turn.js +3 -17
- package/src/ctf.js +462 -462
- package/src/detector-core.js +845 -105
- package/src/document-scanner.js +795 -795
- package/src/drift-monitor.js +356 -0
- package/src/encoding.js +429 -429
- package/src/enterprise.js +405 -405
- package/src/flight-recorder.js +2 -0
- package/src/i18n-patterns.js +523 -523
- package/src/index.js +19 -0
- package/src/intent-binding.js +314 -0
- package/src/intent-graph.js +381 -0
- package/src/main.js +134 -41
- package/src/mcp-guard.js +1532 -0
- package/src/message-integrity.js +226 -0
- package/src/micro-model.js +939 -0
- package/src/ml-detector.js +316 -0
- package/src/model-finetuning.js +884 -884
- package/src/multimodal.js +296 -296
- package/src/nist-mapping.js +2 -2
- package/src/observability.js +330 -330
- package/src/openclaw.js +450 -450
- package/src/otel.js +544 -544
- package/src/owasp-2025.js +1 -1
- package/src/owasp-agentic.js +420 -0
- package/src/plugin-marketplace.js +628 -628
- package/src/plugin-system.js +349 -349
- package/src/policy-extended.js +635 -635
- package/src/policy.js +443 -443
- package/src/prompt-hardening.js +195 -0
- package/src/prompt-leakage.js +2 -2
- package/src/real-attack-datasets.js +2 -2
- package/src/redteam-cli.js +440 -0
- package/src/self-training.js +586 -631
- package/src/semantic-isolation.js +303 -0
- package/src/sota-benchmark.js +491 -0
- package/src/supply-chain-scanner.js +889 -0
- package/src/testing.js +5 -1
- package/src/threat-encyclopedia.js +629 -629
- package/src/threat-intel-network.js +1017 -1017
- package/src/token-analysis.js +467 -467
- package/src/tool-output-validator.js +354 -354
- package/src/watermark.js +1 -2
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Shield — Message Integrity Verification (SOTA)
|
|
5
|
+
*
|
|
6
|
+
* HMAC-signs every message in a conversation to detect tampering.
|
|
7
|
+
* Based on IEEE S&P 2026 finding: plugins transmit message history
|
|
8
|
+
* without integrity checks, enabling adversaries to inject forged
|
|
9
|
+
* messages impersonating high-privilege roles.
|
|
10
|
+
*
|
|
11
|
+
* All processing runs locally — no data ever leaves your environment.
|
|
12
|
+
*
|
|
13
|
+
* @module message-integrity
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
|
|
18
|
+
// =========================================================================
|
|
19
|
+
// MessageIntegrityChain
|
|
20
|
+
// =========================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Maintains an HMAC-signed chain of messages. Each message's signature
|
|
24
|
+
* includes the previous signature, creating a tamper-evident chain
|
|
25
|
+
* (like a blockchain of conversation messages).
|
|
26
|
+
*/
|
|
27
|
+
class MessageIntegrityChain {
|
|
28
|
+
/**
|
|
29
|
+
* @param {object} [options]
|
|
30
|
+
* @param {string} [options.signingKey] - HMAC key. Auto-generated if not provided.
|
|
31
|
+
* @param {string} [options.algorithm='sha256'] - Hash algorithm.
|
|
32
|
+
*/
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
this.signingKey = options.signingKey || crypto.randomBytes(32).toString('hex');
|
|
35
|
+
this.algorithm = options.algorithm || 'sha256';
|
|
36
|
+
|
|
37
|
+
/** @type {Array<{ role: string, content: string, signature: string, index: number, timestamp: number }>} */
|
|
38
|
+
this.chain = [];
|
|
39
|
+
this.stats = { messagesAdded: 0, verificationsRun: 0, tamperingsDetected: 0 };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Add a message to the chain and sign it.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} role - Message role: 'system', 'user', 'assistant', 'tool'.
|
|
46
|
+
* @param {string} content - Message content.
|
|
47
|
+
* @returns {{ index: number, signature: string }}
|
|
48
|
+
*/
|
|
49
|
+
addMessage(role, content) {
|
|
50
|
+
const index = this.chain.length;
|
|
51
|
+
const previousSig = index > 0 ? this.chain[index - 1].signature : '0'.repeat(64);
|
|
52
|
+
const timestamp = Date.now();
|
|
53
|
+
|
|
54
|
+
// Sign: role + content + index + previousSig + timestamp
|
|
55
|
+
const payload = `${role}:${content}:${index}:${previousSig}:${timestamp}`;
|
|
56
|
+
const signature = this._sign(payload);
|
|
57
|
+
|
|
58
|
+
this.chain.push({ role, content, signature, index, timestamp });
|
|
59
|
+
this.stats.messagesAdded++;
|
|
60
|
+
|
|
61
|
+
return { index, signature };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Verify the integrity of the entire chain.
|
|
66
|
+
* Detects any message that was modified, inserted, deleted, or reordered.
|
|
67
|
+
*
|
|
68
|
+
* @returns {{ valid: boolean, tampered: Array<object> }}
|
|
69
|
+
*/
|
|
70
|
+
verifyChain() {
|
|
71
|
+
this.stats.verificationsRun++;
|
|
72
|
+
const tampered = [];
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < this.chain.length; i++) {
|
|
75
|
+
const msg = this.chain[i];
|
|
76
|
+
const previousSig = i > 0 ? this.chain[i - 1].signature : '0'.repeat(64);
|
|
77
|
+
|
|
78
|
+
const payload = `${msg.role}:${msg.content}:${msg.index}:${previousSig}:${msg.timestamp}`;
|
|
79
|
+
const expectedSig = this._sign(payload);
|
|
80
|
+
|
|
81
|
+
if (msg.signature !== expectedSig) {
|
|
82
|
+
tampered.push({
|
|
83
|
+
index: i,
|
|
84
|
+
role: msg.role,
|
|
85
|
+
reason: 'Signature mismatch — message content or order was tampered.',
|
|
86
|
+
expected: expectedSig.substring(0, 16) + '...',
|
|
87
|
+
actual: msg.signature.substring(0, 16) + '...'
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (msg.index !== i) {
|
|
92
|
+
tampered.push({
|
|
93
|
+
index: i,
|
|
94
|
+
role: msg.role,
|
|
95
|
+
reason: `Index mismatch — expected ${i} but found ${msg.index}. Message may have been reordered.`
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (tampered.length > 0) {
|
|
101
|
+
this.stats.tamperingsDetected += tampered.length;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { valid: tampered.length === 0, tampered };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Verify a single message at a given index.
|
|
109
|
+
*
|
|
110
|
+
* @param {number} index
|
|
111
|
+
* @returns {{ valid: boolean, reason: string|null }}
|
|
112
|
+
*/
|
|
113
|
+
verifyMessage(index) {
|
|
114
|
+
if (index < 0 || index >= this.chain.length) {
|
|
115
|
+
return { valid: false, reason: 'Index out of range.' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const msg = this.chain[index];
|
|
119
|
+
const previousSig = index > 0 ? this.chain[index - 1].signature : '0'.repeat(64);
|
|
120
|
+
const payload = `${msg.role}:${msg.content}:${msg.index}:${previousSig}:${msg.timestamp}`;
|
|
121
|
+
const expectedSig = this._sign(payload);
|
|
122
|
+
|
|
123
|
+
if (msg.signature !== expectedSig) {
|
|
124
|
+
return { valid: false, reason: 'Signature mismatch.' };
|
|
125
|
+
}
|
|
126
|
+
return { valid: true, reason: null };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Detect role boundary violations — messages claiming a role they shouldn't have.
|
|
131
|
+
* Ref: IEEE S&P 2026 — plugins inject forged messages impersonating high-privilege roles.
|
|
132
|
+
*
|
|
133
|
+
* @param {object} [rolePolicy]
|
|
134
|
+
* @param {Set<string>} [rolePolicy.systemSources] - Sources allowed to send 'system' messages.
|
|
135
|
+
* @returns {Array<object>} Violations.
|
|
136
|
+
*/
|
|
137
|
+
detectRoleViolations(rolePolicy = {}) {
|
|
138
|
+
const violations = [];
|
|
139
|
+
const systemSources = rolePolicy.systemSources || new Set(['system_init']);
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < this.chain.length; i++) {
|
|
142
|
+
const msg = this.chain[i];
|
|
143
|
+
|
|
144
|
+
// System messages should only appear at the start
|
|
145
|
+
if (msg.role === 'system' && i > 0) {
|
|
146
|
+
violations.push({
|
|
147
|
+
index: i,
|
|
148
|
+
type: 'late_system_message',
|
|
149
|
+
severity: 'critical',
|
|
150
|
+
description: `System message at index ${i} — system messages should only appear at conversation start. Possible role injection.`
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Detect role impersonation patterns in content
|
|
155
|
+
if (msg.role === 'user') {
|
|
156
|
+
const roleImpersonation = /^(?:system|assistant|admin|developer)\s*[:\-]\s/i.test(msg.content);
|
|
157
|
+
if (roleImpersonation) {
|
|
158
|
+
violations.push({
|
|
159
|
+
index: i,
|
|
160
|
+
type: 'role_impersonation_in_content',
|
|
161
|
+
severity: 'high',
|
|
162
|
+
description: `User message at index ${i} starts with a role prefix — possible role impersonation attempt.`
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return violations;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Export the chain for storage or transmission.
|
|
173
|
+
* @returns {object}
|
|
174
|
+
*/
|
|
175
|
+
export() {
|
|
176
|
+
return {
|
|
177
|
+
chain: this.chain.map(m => ({ ...m })),
|
|
178
|
+
chainLength: this.chain.length,
|
|
179
|
+
lastSignature: this.chain.length > 0 ? this.chain[this.chain.length - 1].signature : null,
|
|
180
|
+
exportedAt: Date.now()
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Import and verify a chain.
|
|
186
|
+
* @param {object} data - Data from export().
|
|
187
|
+
* @returns {{ valid: boolean, imported: number, tampered: Array<object> }}
|
|
188
|
+
*/
|
|
189
|
+
import(data) {
|
|
190
|
+
if (!data || !Array.isArray(data.chain)) {
|
|
191
|
+
return { valid: false, imported: 0, tampered: [{ reason: 'Invalid import data.' }] };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const oldChain = this.chain;
|
|
195
|
+
this.chain = data.chain;
|
|
196
|
+
const verification = this.verifyChain();
|
|
197
|
+
|
|
198
|
+
if (!verification.valid) {
|
|
199
|
+
this.chain = oldChain; // Rollback
|
|
200
|
+
return { valid: false, imported: 0, tampered: verification.tampered };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { valid: true, imported: this.chain.length, tampered: [] };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get stats.
|
|
208
|
+
* @returns {object}
|
|
209
|
+
*/
|
|
210
|
+
getStats() {
|
|
211
|
+
return { ...this.stats, chainLength: this.chain.length };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** @private */
|
|
215
|
+
_sign(payload) {
|
|
216
|
+
return crypto.createHmac(this.algorithm, this.signingKey).update(payload).digest('hex');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// =========================================================================
|
|
221
|
+
// EXPORTS
|
|
222
|
+
// =========================================================================
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
MessageIntegrityChain
|
|
226
|
+
};
|