agentshield-sdk 13.3.0 → 14.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 +161 -0
- package/README.md +13 -2
- package/package.json +2 -2
- package/src/audit-immutable.js +59 -1
- package/src/audit.js +1 -1
- package/src/cross-turn.js +25 -1
- package/src/detector-core.js +333 -51
- package/src/document-scanner.js +20 -0
- package/src/enterprise.js +127 -12
- package/src/integrations-frameworks.js +373 -0
- package/src/integrations.js +207 -0
- package/src/main.js +10 -14
- package/src/memory-guard.js +60 -0
- package/src/middleware.js +107 -2
- package/src/native-scanner.js +104 -0
- package/src/plugin-system.js +422 -6
- package/src/supply-chain-scanner.js +112 -2
- package/src/sybil-detector.js +3 -6
- package/src/persistent-learning.js +0 -161
- package/src/threat-intel-federation.js +0 -343
package/src/enterprise.js
CHANGED
|
@@ -16,18 +16,104 @@ const { loadPolicy } = require('./policy');
|
|
|
16
16
|
// Multi-Tenant Shield
|
|
17
17
|
// =========================================================================
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Multi-tenant Shield.
|
|
21
|
+
*
|
|
22
|
+
* SECURITY: Tenant IDs are treated as trust boundaries — scans, stats,
|
|
23
|
+
* and policies are partitioned per `tenantId`. In production, callers
|
|
24
|
+
* MUST configure `options.tenantVerifier` to prove that a supplied
|
|
25
|
+
* tenantId was established by a trusted authentication mechanism
|
|
26
|
+
* (JWT, session, mTLS, etc.). Without a verifier, a caller that can
|
|
27
|
+
* invent tenant IDs can read/write any tenant's data.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const shield = new MultiTenantShield({
|
|
31
|
+
* tenantVerifier: (tenantId, ctx) => ctx && ctx.jwt && ctx.jwt.tenant === tenantId,
|
|
32
|
+
* strictAuth: true
|
|
33
|
+
* });
|
|
34
|
+
* shield.scan('tenant-42', userInput, { context: { jwt: decodedJwt } });
|
|
35
|
+
*/
|
|
19
36
|
class MultiTenantShield {
|
|
20
37
|
constructor(options = {}) {
|
|
21
38
|
this.tenants = new Map();
|
|
22
39
|
this.defaultPolicy = options.defaultPolicy || { sensitivity: 'high', blockOnThreat: true };
|
|
23
40
|
this.globalOverrides = options.globalOverrides || {};
|
|
24
41
|
this.onTenantCreated = options.onTenantCreated || null;
|
|
42
|
+
this.tenantVerifier = typeof options.tenantVerifier === 'function'
|
|
43
|
+
? options.tenantVerifier
|
|
44
|
+
: null;
|
|
45
|
+
this.strictAuth = options.strictAuth === true;
|
|
46
|
+
|
|
47
|
+
if (!this.tenantVerifier) {
|
|
48
|
+
if (this.strictAuth) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
'[Agent Shield] MultiTenantShield: strictAuth is enabled but no options.tenantVerifier was provided. Supply a (tenantId, context) => boolean verifier.'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
console.warn('[Agent Shield] WARNING: MultiTenantShield has no tenantVerifier. Tenant IDs are trusted by default. Set options.tenantVerifier in production.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Verify that a tenantId is authorized for the current caller.
|
|
59
|
+
* @param {string} tenantId
|
|
60
|
+
* @param {object} [context] - Request/auth context passed by the caller.
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
_verifyTenant(tenantId, context) {
|
|
65
|
+
if (typeof tenantId !== 'string' || tenantId.length === 0) {
|
|
66
|
+
throw new Error('[Agent Shield] MultiTenantShield: tenantId must be a non-empty string');
|
|
67
|
+
}
|
|
68
|
+
if (!this.tenantVerifier) {
|
|
69
|
+
// Backward-compatible: permit by default, warning already logged at construction.
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
let ok = false;
|
|
73
|
+
try {
|
|
74
|
+
ok = this.tenantVerifier(tenantId, context || {}) === true;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
throw new Error(`[Agent Shield] MultiTenantShield: tenantVerifier threw while verifying tenant "${tenantId}": ${err.message}`);
|
|
77
|
+
}
|
|
78
|
+
if (!ok) {
|
|
79
|
+
throw new Error(`[Agent Shield] MultiTenantShield: tenantVerifier rejected tenant "${tenantId}"`);
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Return a new MultiTenantShield that reuses this instance's tenant
|
|
86
|
+
* registrations/stats but enforces the supplied tenant verifier. Useful
|
|
87
|
+
* for adding auth to an existing shield without mutating global state.
|
|
88
|
+
*
|
|
89
|
+
* @param {(tenantId: string, context: object) => boolean} verifier
|
|
90
|
+
* @param {object} [extraOptions]
|
|
91
|
+
* @returns {MultiTenantShield}
|
|
92
|
+
*/
|
|
93
|
+
withAuth(verifier, extraOptions = {}) {
|
|
94
|
+
if (typeof verifier !== 'function') {
|
|
95
|
+
throw new Error('[Agent Shield] MultiTenantShield.withAuth: verifier must be a function');
|
|
96
|
+
}
|
|
97
|
+
const next = new MultiTenantShield({
|
|
98
|
+
defaultPolicy: this.defaultPolicy,
|
|
99
|
+
globalOverrides: this.globalOverrides,
|
|
100
|
+
onTenantCreated: this.onTenantCreated,
|
|
101
|
+
tenantVerifier: verifier,
|
|
102
|
+
strictAuth: extraOptions.strictAuth === true
|
|
103
|
+
});
|
|
104
|
+
// Share tenant registry so existing tenants remain accessible.
|
|
105
|
+
next.tenants = this.tenants;
|
|
106
|
+
return next;
|
|
25
107
|
}
|
|
26
108
|
|
|
27
109
|
/**
|
|
28
110
|
* Register a tenant with its own policy.
|
|
111
|
+
* @param {string} tenantId
|
|
112
|
+
* @param {object} [policy]
|
|
113
|
+
* @param {object} [context] - Auth context forwarded to the tenantVerifier.
|
|
29
114
|
*/
|
|
30
|
-
registerTenant(tenantId, policy = {}) {
|
|
115
|
+
registerTenant(tenantId, policy = {}, context) {
|
|
116
|
+
this._verifyTenant(tenantId, context);
|
|
31
117
|
const mergedPolicy = { ...this.defaultPolicy, ...policy, ...this.globalOverrides };
|
|
32
118
|
const shield = new AgentShield(mergedPolicy);
|
|
33
119
|
|
|
@@ -48,19 +134,38 @@ class MultiTenantShield {
|
|
|
48
134
|
|
|
49
135
|
/**
|
|
50
136
|
* Get or auto-create a tenant shield.
|
|
137
|
+
* @param {string} tenantId
|
|
138
|
+
* @param {object} [context] - Auth context forwarded to the tenantVerifier.
|
|
51
139
|
*/
|
|
52
|
-
getTenant(tenantId) {
|
|
140
|
+
getTenant(tenantId, context) {
|
|
141
|
+
this._verifyTenant(tenantId, context);
|
|
53
142
|
if (!this.tenants.has(tenantId)) {
|
|
54
|
-
|
|
143
|
+
// Skip re-verification — we just verified above.
|
|
144
|
+
const mergedPolicy = { ...this.defaultPolicy, ...this.globalOverrides };
|
|
145
|
+
const shield = new AgentShield(mergedPolicy);
|
|
146
|
+
this.tenants.set(tenantId, {
|
|
147
|
+
id: tenantId,
|
|
148
|
+
policy: mergedPolicy,
|
|
149
|
+
shield,
|
|
150
|
+
stats: { scans: 0, threats: 0, blocked: 0 },
|
|
151
|
+
createdAt: new Date().toISOString()
|
|
152
|
+
});
|
|
153
|
+
if (this.onTenantCreated) {
|
|
154
|
+
this.onTenantCreated(tenantId, mergedPolicy);
|
|
155
|
+
}
|
|
55
156
|
}
|
|
56
157
|
return this.tenants.get(tenantId);
|
|
57
158
|
}
|
|
58
159
|
|
|
59
160
|
/**
|
|
60
161
|
* Scan input for a specific tenant.
|
|
162
|
+
* @param {string} tenantId
|
|
163
|
+
* @param {string} text
|
|
164
|
+
* @param {object} [options]
|
|
165
|
+
* @param {object} [options.context] - Auth context forwarded to the tenantVerifier.
|
|
61
166
|
*/
|
|
62
167
|
scan(tenantId, text, options = {}) {
|
|
63
|
-
const tenant = this.getTenant(tenantId);
|
|
168
|
+
const tenant = this.getTenant(tenantId, options.context);
|
|
64
169
|
tenant.stats.scans++;
|
|
65
170
|
|
|
66
171
|
const result = tenant.shield.scan(text, options);
|
|
@@ -78,30 +183,39 @@ class MultiTenantShield {
|
|
|
78
183
|
/**
|
|
79
184
|
* Scan input for a specific tenant.
|
|
80
185
|
*/
|
|
81
|
-
scanInput(tenantId, text) {
|
|
82
|
-
return this.scan(tenantId, text);
|
|
186
|
+
scanInput(tenantId, text, options = {}) {
|
|
187
|
+
return this.scan(tenantId, text, options);
|
|
83
188
|
}
|
|
84
189
|
|
|
85
190
|
/**
|
|
86
191
|
* Scan output for a specific tenant.
|
|
87
192
|
*/
|
|
88
|
-
scanOutput(tenantId, text) {
|
|
89
|
-
const tenant = this.getTenant(tenantId);
|
|
193
|
+
scanOutput(tenantId, text, options = {}) {
|
|
194
|
+
const tenant = this.getTenant(tenantId, options.context);
|
|
90
195
|
return tenant.shield.scanOutput(text);
|
|
91
196
|
}
|
|
92
197
|
|
|
93
198
|
/**
|
|
94
199
|
* Update a tenant's policy.
|
|
95
200
|
*/
|
|
96
|
-
updatePolicy(tenantId, policy) {
|
|
97
|
-
const tenant = this.getTenant(tenantId);
|
|
201
|
+
updatePolicy(tenantId, policy, context) {
|
|
202
|
+
const tenant = this.getTenant(tenantId, context);
|
|
98
203
|
tenant.policy = { ...tenant.policy, ...policy, ...this.globalOverrides };
|
|
99
204
|
tenant.shield = new AgentShield(tenant.policy);
|
|
100
205
|
return tenant.policy;
|
|
101
206
|
}
|
|
102
207
|
|
|
103
208
|
/**
|
|
104
|
-
* Get stats for
|
|
209
|
+
* Get stats for a single tenant (auth-checked).
|
|
210
|
+
*/
|
|
211
|
+
getStats(tenantId, context) {
|
|
212
|
+
const tenant = this.getTenant(tenantId, context);
|
|
213
|
+
return { ...tenant.stats, policy: tenant.policy };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get stats for all tenants. NOTE: this method bypasses per-tenant
|
|
218
|
+
* auth — callers should gate access to it at the admin level.
|
|
105
219
|
*/
|
|
106
220
|
getAllStats() {
|
|
107
221
|
const stats = {};
|
|
@@ -114,7 +228,8 @@ class MultiTenantShield {
|
|
|
114
228
|
/**
|
|
115
229
|
* Remove a tenant.
|
|
116
230
|
*/
|
|
117
|
-
removeTenant(tenantId) {
|
|
231
|
+
removeTenant(tenantId, context) {
|
|
232
|
+
this._verifyTenant(tenantId, context);
|
|
118
233
|
return this.tenants.delete(tenantId);
|
|
119
234
|
}
|
|
120
235
|
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Shield — Framework Integration Wrappers
|
|
5
|
+
*
|
|
6
|
+
* Plug-and-play integrations for next-generation AI agent frameworks:
|
|
7
|
+
* - CrewAI (task decorators & callbacks)
|
|
8
|
+
* - Google Agent Development Kit (plugin system)
|
|
9
|
+
* - Microsoft Agent Framework (middleware pipeline)
|
|
10
|
+
*
|
|
11
|
+
* These close gaps identified in the Microsoft Agent Governance Toolkit
|
|
12
|
+
* parity audit.
|
|
13
|
+
*
|
|
14
|
+
* @module integrations-frameworks
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { AgentShield } = require('./index');
|
|
18
|
+
const { ShieldBlockError } = require('./integrations');
|
|
19
|
+
|
|
20
|
+
// =========================================================================
|
|
21
|
+
// CrewAI Integration
|
|
22
|
+
// =========================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates Agent Shield callbacks for CrewAI task lifecycle.
|
|
26
|
+
*
|
|
27
|
+
* CrewAI uses task decorators and callbacks. This wrapper provides
|
|
28
|
+
* beforeTask / afterTask hooks that scan task descriptions, expected
|
|
29
|
+
* outputs, and task results for prompt injection and other threats.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* const { shieldCrewAI } = require('agentshield-sdk/src/integrations-frameworks');
|
|
33
|
+
* const { beforeTask, afterTask } = shieldCrewAI({ blockOnThreat: true });
|
|
34
|
+
*
|
|
35
|
+
* // In your CrewAI task lifecycle:
|
|
36
|
+
* beforeTask(task, agent); // throws ShieldBlockError if threat found
|
|
37
|
+
* const output = await task.run();
|
|
38
|
+
* afterTask(task, output); // throws ShieldBlockError if threat found
|
|
39
|
+
*
|
|
40
|
+
* @param {object} [options]
|
|
41
|
+
* @param {string} [options.sensitivity='high'] - Detection sensitivity level.
|
|
42
|
+
* @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
|
|
43
|
+
* @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
|
|
44
|
+
* @param {function} [options.onThreat] - Callback when a threat is detected.
|
|
45
|
+
* @returns {{ beforeTask: function, afterTask: function, shield: AgentShield }}
|
|
46
|
+
*/
|
|
47
|
+
function shieldCrewAI(options = {}) {
|
|
48
|
+
const shield = new AgentShield({
|
|
49
|
+
sensitivity: options.sensitivity || 'high',
|
|
50
|
+
blockOnThreat: options.blockOnThreat !== false,
|
|
51
|
+
blockThreshold: options.blockThreshold || 'high'
|
|
52
|
+
});
|
|
53
|
+
const onThreat = options.onThreat || null;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Scans a CrewAI task before execution.
|
|
57
|
+
* Inspects task.description and task.expected_output for injection.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} task - CrewAI task object.
|
|
60
|
+
* @param {object} [agent] - CrewAI agent assigned to the task.
|
|
61
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
62
|
+
*/
|
|
63
|
+
function beforeTask(task, agent) {
|
|
64
|
+
if (!task) return;
|
|
65
|
+
|
|
66
|
+
const fields = [];
|
|
67
|
+
if (task.description) fields.push(String(task.description));
|
|
68
|
+
if (task.expected_output) fields.push(String(task.expected_output));
|
|
69
|
+
|
|
70
|
+
for (const text of fields) {
|
|
71
|
+
const result = shield.scanInput(text);
|
|
72
|
+
if (result.threats && result.threats.length > 0) {
|
|
73
|
+
if (onThreat) {
|
|
74
|
+
try {
|
|
75
|
+
onThreat({
|
|
76
|
+
phase: 'before_task',
|
|
77
|
+
threats: result.threats,
|
|
78
|
+
task: task.description || '',
|
|
79
|
+
agent: agent && agent.role ? agent.role : undefined
|
|
80
|
+
});
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (result.blocked) {
|
|
86
|
+
throw new ShieldBlockError('CrewAI task blocked by Agent Shield', result.threats);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Scans a CrewAI task output after execution.
|
|
94
|
+
*
|
|
95
|
+
* @param {object} task - CrewAI task object.
|
|
96
|
+
* @param {*} output - Task execution output.
|
|
97
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
98
|
+
*/
|
|
99
|
+
function afterTask(task, output) {
|
|
100
|
+
if (output == null) return;
|
|
101
|
+
|
|
102
|
+
const text = typeof output === 'string' ? output : JSON.stringify(output);
|
|
103
|
+
const result = shield.scanOutput(text);
|
|
104
|
+
|
|
105
|
+
if (result.threats && result.threats.length > 0) {
|
|
106
|
+
if (onThreat) {
|
|
107
|
+
try {
|
|
108
|
+
onThreat({
|
|
109
|
+
phase: 'after_task',
|
|
110
|
+
threats: result.threats,
|
|
111
|
+
task: task && task.description ? task.description : ''
|
|
112
|
+
});
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (result.blocked) {
|
|
118
|
+
throw new ShieldBlockError('CrewAI task output blocked by Agent Shield', result.threats);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { beforeTask, afterTask, shield };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// =========================================================================
|
|
127
|
+
// Google Agent Development Kit (ADK) Integration
|
|
128
|
+
// =========================================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Creates Agent Shield hooks for the Google Agent Development Kit plugin system.
|
|
132
|
+
*
|
|
133
|
+
* Google ADK uses a plugin architecture with lifecycle hooks. This wrapper
|
|
134
|
+
* provides beforeToolCall, afterToolCall, and beforeGenerate functions that
|
|
135
|
+
* scan tool arguments, tool results, and generation prompts for threats.
|
|
136
|
+
*
|
|
137
|
+
* Usage:
|
|
138
|
+
* const { shieldGoogleADK } = require('agentshield-sdk/src/integrations-frameworks');
|
|
139
|
+
* const hooks = shieldGoogleADK({ blockOnThreat: true });
|
|
140
|
+
*
|
|
141
|
+
* // Register as ADK plugin callbacks:
|
|
142
|
+
* hooks.beforeToolCall('web_search', { query: userInput });
|
|
143
|
+
* const result = await tool.execute(args);
|
|
144
|
+
* hooks.afterToolCall('web_search', result);
|
|
145
|
+
* hooks.beforeGenerate(prompt);
|
|
146
|
+
*
|
|
147
|
+
* @param {object} [options]
|
|
148
|
+
* @param {string} [options.sensitivity='high'] - Detection sensitivity level.
|
|
149
|
+
* @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
|
|
150
|
+
* @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
|
|
151
|
+
* @param {function} [options.onThreat] - Callback when a threat is detected.
|
|
152
|
+
* @returns {{ beforeToolCall: function, afterToolCall: function, beforeGenerate: function, shield: AgentShield }}
|
|
153
|
+
*/
|
|
154
|
+
function shieldGoogleADK(options = {}) {
|
|
155
|
+
const shield = new AgentShield({
|
|
156
|
+
sensitivity: options.sensitivity || 'high',
|
|
157
|
+
blockOnThreat: options.blockOnThreat !== false,
|
|
158
|
+
blockThreshold: options.blockThreshold || 'high'
|
|
159
|
+
});
|
|
160
|
+
const onThreat = options.onThreat || null;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Scans tool arguments before a tool call.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} toolName - Name of the tool being called.
|
|
166
|
+
* @param {*} args - Tool arguments (object, string, or any serializable value).
|
|
167
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
168
|
+
*/
|
|
169
|
+
function beforeToolCall(toolName, args) {
|
|
170
|
+
if (args == null) return;
|
|
171
|
+
|
|
172
|
+
const text = typeof args === 'string' ? args : JSON.stringify(args);
|
|
173
|
+
const result = shield.scanInput(text);
|
|
174
|
+
|
|
175
|
+
if (result.threats && result.threats.length > 0) {
|
|
176
|
+
if (onThreat) {
|
|
177
|
+
try {
|
|
178
|
+
onThreat({
|
|
179
|
+
phase: 'before_tool_call',
|
|
180
|
+
toolName: toolName || 'unknown',
|
|
181
|
+
threats: result.threats
|
|
182
|
+
});
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (result.blocked) {
|
|
188
|
+
throw new ShieldBlockError(
|
|
189
|
+
`Google ADK tool "${toolName || 'unknown'}" call blocked by Agent Shield`,
|
|
190
|
+
result.threats
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Scans tool results after a tool call.
|
|
198
|
+
*
|
|
199
|
+
* @param {string} toolName - Name of the tool that was called.
|
|
200
|
+
* @param {*} result - Tool execution result.
|
|
201
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
202
|
+
*/
|
|
203
|
+
function afterToolCall(toolName, toolResult) {
|
|
204
|
+
if (toolResult == null) return;
|
|
205
|
+
|
|
206
|
+
const text = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult);
|
|
207
|
+
const result = shield.scanOutput(text);
|
|
208
|
+
|
|
209
|
+
if (result.threats && result.threats.length > 0) {
|
|
210
|
+
if (onThreat) {
|
|
211
|
+
try {
|
|
212
|
+
onThreat({
|
|
213
|
+
phase: 'after_tool_call',
|
|
214
|
+
toolName: toolName || 'unknown',
|
|
215
|
+
threats: result.threats
|
|
216
|
+
});
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (result.blocked) {
|
|
222
|
+
throw new ShieldBlockError(
|
|
223
|
+
`Google ADK tool "${toolName || 'unknown'}" result blocked by Agent Shield`,
|
|
224
|
+
result.threats
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Scans a prompt before generation.
|
|
232
|
+
*
|
|
233
|
+
* @param {string|*} prompt - The prompt to scan.
|
|
234
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
235
|
+
*/
|
|
236
|
+
function beforeGenerate(prompt) {
|
|
237
|
+
if (prompt == null) return;
|
|
238
|
+
|
|
239
|
+
const text = typeof prompt === 'string' ? prompt : JSON.stringify(prompt);
|
|
240
|
+
const result = shield.scanInput(text);
|
|
241
|
+
|
|
242
|
+
if (result.threats && result.threats.length > 0) {
|
|
243
|
+
if (onThreat) {
|
|
244
|
+
try {
|
|
245
|
+
onThreat({
|
|
246
|
+
phase: 'before_generate',
|
|
247
|
+
threats: result.threats
|
|
248
|
+
});
|
|
249
|
+
} catch (e) {
|
|
250
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (result.blocked) {
|
|
254
|
+
throw new ShieldBlockError('Google ADK generation blocked by Agent Shield', result.threats);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { beforeToolCall, afterToolCall, beforeGenerate, shield };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// =========================================================================
|
|
263
|
+
// Microsoft Agent Framework Integration
|
|
264
|
+
// =========================================================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Creates Agent Shield middleware for the Microsoft Agent Framework pipeline.
|
|
268
|
+
*
|
|
269
|
+
* The MS Agent Framework uses a middleware pattern where each middleware
|
|
270
|
+
* receives a context and a next() function. This wrapper scans context.input
|
|
271
|
+
* before calling next(), then scans context.output after next() returns.
|
|
272
|
+
*
|
|
273
|
+
* Usage:
|
|
274
|
+
* const { shieldMSAgentFramework } = require('agentshield-sdk/src/integrations-frameworks');
|
|
275
|
+
* const { agentMiddleware } = shieldMSAgentFramework({ blockOnThreat: true });
|
|
276
|
+
*
|
|
277
|
+
* // Register in the MS Agent Framework pipeline:
|
|
278
|
+
* agent.use(agentMiddleware);
|
|
279
|
+
*
|
|
280
|
+
* @param {object} [options]
|
|
281
|
+
* @param {string} [options.sensitivity='high'] - Detection sensitivity level.
|
|
282
|
+
* @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
|
|
283
|
+
* @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
|
|
284
|
+
* @param {function} [options.onThreat] - Callback when a threat is detected.
|
|
285
|
+
* @returns {{ agentMiddleware: function, shield: AgentShield }}
|
|
286
|
+
*/
|
|
287
|
+
function shieldMSAgentFramework(options = {}) {
|
|
288
|
+
const shield = new AgentShield({
|
|
289
|
+
sensitivity: options.sensitivity || 'high',
|
|
290
|
+
blockOnThreat: options.blockOnThreat !== false,
|
|
291
|
+
blockThreshold: options.blockThreshold || 'high'
|
|
292
|
+
});
|
|
293
|
+
const onThreat = options.onThreat || null;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Middleware function for the MS Agent Framework pipeline.
|
|
297
|
+
* Scans context.input before next() and context.output after next().
|
|
298
|
+
*
|
|
299
|
+
* @param {object} context - Pipeline context with input/output properties.
|
|
300
|
+
* @param {function} next - Next middleware in the pipeline.
|
|
301
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
302
|
+
*/
|
|
303
|
+
async function agentMiddleware(context, next) {
|
|
304
|
+
// Scan input before passing to next middleware
|
|
305
|
+
if (context && context.input != null) {
|
|
306
|
+
const inputText = typeof context.input === 'string'
|
|
307
|
+
? context.input
|
|
308
|
+
: JSON.stringify(context.input);
|
|
309
|
+
|
|
310
|
+
const inputResult = shield.scanInput(inputText);
|
|
311
|
+
|
|
312
|
+
if (inputResult.threats && inputResult.threats.length > 0) {
|
|
313
|
+
if (onThreat) {
|
|
314
|
+
try {
|
|
315
|
+
onThreat({
|
|
316
|
+
phase: 'input',
|
|
317
|
+
threats: inputResult.threats,
|
|
318
|
+
text: inputText
|
|
319
|
+
});
|
|
320
|
+
} catch (e) {
|
|
321
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (inputResult.blocked) {
|
|
325
|
+
throw new ShieldBlockError(
|
|
326
|
+
'MS Agent Framework input blocked by Agent Shield',
|
|
327
|
+
inputResult.threats
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Call next middleware in the pipeline
|
|
334
|
+
await next();
|
|
335
|
+
|
|
336
|
+
// Scan output after pipeline execution
|
|
337
|
+
if (context && context.output != null) {
|
|
338
|
+
const outputText = typeof context.output === 'string'
|
|
339
|
+
? context.output
|
|
340
|
+
: JSON.stringify(context.output);
|
|
341
|
+
|
|
342
|
+
const outputResult = shield.scanOutput(outputText);
|
|
343
|
+
|
|
344
|
+
if (outputResult.threats && outputResult.threats.length > 0) {
|
|
345
|
+
if (onThreat) {
|
|
346
|
+
try {
|
|
347
|
+
onThreat({
|
|
348
|
+
phase: 'output',
|
|
349
|
+
threats: outputResult.threats,
|
|
350
|
+
text: outputText
|
|
351
|
+
});
|
|
352
|
+
} catch (e) {
|
|
353
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (outputResult.blocked) {
|
|
357
|
+
throw new ShieldBlockError(
|
|
358
|
+
'MS Agent Framework output blocked by Agent Shield',
|
|
359
|
+
outputResult.threats
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return { agentMiddleware, shield };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
module.exports = {
|
|
370
|
+
shieldCrewAI,
|
|
371
|
+
shieldGoogleADK,
|
|
372
|
+
shieldMSAgentFramework
|
|
373
|
+
};
|