agentshield-sdk 7.2.0 → 7.2.1
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/README.md +5 -4
- package/package.json +4 -3
- package/src/circuit-breaker.js +321 -321
- package/src/detector-core.js +3 -3
- package/src/distributed.js +402 -359
- package/src/fuzzer.js +764 -764
- package/src/index.js +23 -7
- package/src/main.js +6 -2
- package/src/mcp-security-runtime.js +30 -5
- package/src/mcp-server.js +12 -8
- package/src/middleware.js +303 -208
- package/src/multi-agent.js +421 -404
- package/src/pii.js +401 -390
- package/src/stream-scanner.js +34 -4
- package/src/testing.js +505 -505
- package/src/utils.js +199 -83
- package/types/index.d.ts +374 -0
package/src/utils.js
CHANGED
|
@@ -1,83 +1,199 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Agent Shield — Shared Utilities
|
|
5
|
-
*
|
|
6
|
-
* Common helpers used across multiple modules to avoid duplication.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Calculate a letter grade from a numeric score (0-100).
|
|
11
|
-
*/
|
|
12
|
-
function getGrade(score) {
|
|
13
|
-
if (score >= 95) return 'A+';
|
|
14
|
-
if (score >= 90) return 'A';
|
|
15
|
-
if (score >= 85) return 'A-';
|
|
16
|
-
if (score >= 80) return 'B+';
|
|
17
|
-
if (score >= 75) return 'B';
|
|
18
|
-
if (score >= 70) return 'B-';
|
|
19
|
-
if (score >= 65) return 'C+';
|
|
20
|
-
if (score >= 60) return 'C';
|
|
21
|
-
if (score >= 55) return 'C-';
|
|
22
|
-
if (score >= 50) return 'D';
|
|
23
|
-
return 'F';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Get a human-readable grade label.
|
|
28
|
-
*/
|
|
29
|
-
function getGradeLabel(score) {
|
|
30
|
-
if (score >= 95) return 'A+ — Excellent';
|
|
31
|
-
if (score >= 90) return 'A — Strong';
|
|
32
|
-
if (score >= 80) return 'B — Good';
|
|
33
|
-
if (score >= 70) return 'C — Moderate';
|
|
34
|
-
if (score >= 60) return 'D — Weak';
|
|
35
|
-
return 'F — Critical gaps';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Render a progress bar using block characters.
|
|
40
|
-
*/
|
|
41
|
-
function makeBar(filled, total, width) {
|
|
42
|
-
const ratio = total > 0 ? filled / total : 0;
|
|
43
|
-
const filledCount = Math.round(ratio * width);
|
|
44
|
-
return '█'.repeat(filledCount) + '░'.repeat(width - filledCount);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Truncate text to a maximum length with an optional suffix.
|
|
49
|
-
*/
|
|
50
|
-
function truncate(text, maxLength = 200, suffix = '') {
|
|
51
|
-
if (!text || text.length <= maxLength) return text || '';
|
|
52
|
-
return text.substring(0, maxLength) + suffix;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Format a boxed console header.
|
|
57
|
-
*/
|
|
58
|
-
function formatHeader(title, width = 54) {
|
|
59
|
-
const padded = title.length < width - 4
|
|
60
|
-
? ' '.repeat(Math.floor((width - 2 - title.length) / 2)) + title + ' '.repeat(Math.ceil((width - 2 - title.length) / 2))
|
|
61
|
-
: title;
|
|
62
|
-
return [
|
|
63
|
-
'╔' + '═'.repeat(width) + '╗',
|
|
64
|
-
'║' + padded + '║',
|
|
65
|
-
'╚' + '═'.repeat(width) + '╝'
|
|
66
|
-
].join('\n');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Generate a unique event ID.
|
|
71
|
-
*/
|
|
72
|
-
function generateId(prefix = 'evt') {
|
|
73
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8).padEnd(6, '0')}`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Shield — Shared Utilities
|
|
5
|
+
*
|
|
6
|
+
* Common helpers used across multiple modules to avoid duplication.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Calculate a letter grade from a numeric score (0-100).
|
|
11
|
+
*/
|
|
12
|
+
function getGrade(score) {
|
|
13
|
+
if (score >= 95) return 'A+';
|
|
14
|
+
if (score >= 90) return 'A';
|
|
15
|
+
if (score >= 85) return 'A-';
|
|
16
|
+
if (score >= 80) return 'B+';
|
|
17
|
+
if (score >= 75) return 'B';
|
|
18
|
+
if (score >= 70) return 'B-';
|
|
19
|
+
if (score >= 65) return 'C+';
|
|
20
|
+
if (score >= 60) return 'C';
|
|
21
|
+
if (score >= 55) return 'C-';
|
|
22
|
+
if (score >= 50) return 'D';
|
|
23
|
+
return 'F';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get a human-readable grade label.
|
|
28
|
+
*/
|
|
29
|
+
function getGradeLabel(score) {
|
|
30
|
+
if (score >= 95) return 'A+ — Excellent';
|
|
31
|
+
if (score >= 90) return 'A — Strong';
|
|
32
|
+
if (score >= 80) return 'B — Good';
|
|
33
|
+
if (score >= 70) return 'C — Moderate';
|
|
34
|
+
if (score >= 60) return 'D — Weak';
|
|
35
|
+
return 'F — Critical gaps';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Render a progress bar using block characters.
|
|
40
|
+
*/
|
|
41
|
+
function makeBar(filled, total, width) {
|
|
42
|
+
const ratio = total > 0 ? filled / total : 0;
|
|
43
|
+
const filledCount = Math.round(ratio * width);
|
|
44
|
+
return '█'.repeat(filledCount) + '░'.repeat(width - filledCount);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Truncate text to a maximum length with an optional suffix.
|
|
49
|
+
*/
|
|
50
|
+
function truncate(text, maxLength = 200, suffix = '') {
|
|
51
|
+
if (!text || text.length <= maxLength) return text || '';
|
|
52
|
+
return text.substring(0, maxLength) + suffix;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Format a boxed console header.
|
|
57
|
+
*/
|
|
58
|
+
function formatHeader(title, width = 54) {
|
|
59
|
+
const padded = title.length < width - 4
|
|
60
|
+
? ' '.repeat(Math.floor((width - 2 - title.length) / 2)) + title + ' '.repeat(Math.ceil((width - 2 - title.length) / 2))
|
|
61
|
+
: title;
|
|
62
|
+
return [
|
|
63
|
+
'╔' + '═'.repeat(width) + '╗',
|
|
64
|
+
'║' + padded + '║',
|
|
65
|
+
'╚' + '═'.repeat(width) + '╝'
|
|
66
|
+
].join('\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate a unique event ID.
|
|
71
|
+
*/
|
|
72
|
+
function generateId(prefix = 'evt') {
|
|
73
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8).padEnd(6, '0')}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Graceful shutdown helper with drain handling and timeout enforcement.
|
|
78
|
+
* Runs cleanup functions with a hard deadline to prevent hanging.
|
|
79
|
+
*
|
|
80
|
+
* @param {object} [options]
|
|
81
|
+
* @param {number} [options.timeoutMs=10000] - Maximum time to wait for cleanup before forced exit.
|
|
82
|
+
* @param {Function[]} [options.cleanupFns] - Array of cleanup functions (sync or async).
|
|
83
|
+
* @param {Function} [options.logger] - Log function (defaults to console.error).
|
|
84
|
+
* @returns {{ shutdown: Function, onShutdown: Function }}
|
|
85
|
+
*/
|
|
86
|
+
function createGracefulShutdown(options = {}) {
|
|
87
|
+
const timeoutMs = options.timeoutMs || 10000;
|
|
88
|
+
const cleanupFns = options.cleanupFns || [];
|
|
89
|
+
const logger = options.logger || console.error;
|
|
90
|
+
let shuttingDown = false;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Register an additional cleanup function.
|
|
94
|
+
* @param {Function} fn
|
|
95
|
+
*/
|
|
96
|
+
function onShutdown(fn) {
|
|
97
|
+
if (typeof fn === 'function') cleanupFns.push(fn);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Execute shutdown sequence with timeout enforcement.
|
|
102
|
+
* @param {string} [signal] - Signal that triggered shutdown.
|
|
103
|
+
* @returns {Promise<void>}
|
|
104
|
+
*/
|
|
105
|
+
async function shutdown(signal) {
|
|
106
|
+
if (shuttingDown) return;
|
|
107
|
+
shuttingDown = true;
|
|
108
|
+
logger(`[Agent Shield] Shutdown initiated (${signal || 'manual'}), timeout: ${timeoutMs}ms`);
|
|
109
|
+
|
|
110
|
+
const forceTimer = setTimeout(() => {
|
|
111
|
+
logger('[Agent Shield] Shutdown timeout exceeded, forcing exit');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}, timeoutMs);
|
|
114
|
+
// Do not keep process alive just for the force timer
|
|
115
|
+
if (forceTimer.unref) forceTimer.unref();
|
|
116
|
+
|
|
117
|
+
for (const fn of cleanupFns) {
|
|
118
|
+
try {
|
|
119
|
+
const result = fn();
|
|
120
|
+
if (result && typeof result.then === 'function') {
|
|
121
|
+
await result;
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
logger(`[Agent Shield] Cleanup error: ${err.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
clearTimeout(forceTimer);
|
|
129
|
+
logger('[Agent Shield] Shutdown complete');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { shutdown, onShutdown };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Load environment variables from a .env file into process.env.
|
|
137
|
+
* Zero-dependency alternative to the dotenv package.
|
|
138
|
+
* Does not overwrite existing env vars unless overwrite is true.
|
|
139
|
+
*
|
|
140
|
+
* @param {object} [options]
|
|
141
|
+
* @param {string} [options.path] - Path to the .env file (defaults to cwd/.env).
|
|
142
|
+
* @param {boolean} [options.overwrite=false] - Whether to overwrite existing vars.
|
|
143
|
+
* @returns {{ loaded: number, errors: string[] }}
|
|
144
|
+
*/
|
|
145
|
+
function loadEnvFile(options = {}) {
|
|
146
|
+
const fs = require('fs');
|
|
147
|
+
const pathMod = require('path');
|
|
148
|
+
const envPath = options.path || pathMod.resolve(process.cwd(), '.env');
|
|
149
|
+
const overwrite = options.overwrite === true;
|
|
150
|
+
const result = { loaded: 0, errors: [] };
|
|
151
|
+
|
|
152
|
+
let content;
|
|
153
|
+
try {
|
|
154
|
+
content = fs.readFileSync(envPath, 'utf8');
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (err.code === 'ENOENT') return result; // No .env file, not an error
|
|
157
|
+
result.errors.push(`Failed to read ${envPath}: ${err.message}`);
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const lines = content.split('\n');
|
|
162
|
+
for (let i = 0; i < lines.length; i++) {
|
|
163
|
+
const line = lines[i].trim();
|
|
164
|
+
// Skip empty lines and comments
|
|
165
|
+
if (!line || line.startsWith('#')) continue;
|
|
166
|
+
|
|
167
|
+
const eqIndex = line.indexOf('=');
|
|
168
|
+
if (eqIndex === -1) continue;
|
|
169
|
+
|
|
170
|
+
const key = line.substring(0, eqIndex).trim();
|
|
171
|
+
let value = line.substring(eqIndex + 1).trim();
|
|
172
|
+
|
|
173
|
+
// Strip surrounding quotes (single or double)
|
|
174
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
175
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
176
|
+
value = value.slice(1, -1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!key) continue;
|
|
180
|
+
|
|
181
|
+
if (overwrite || process.env[key] === undefined) {
|
|
182
|
+
process.env[key] = value;
|
|
183
|
+
result.loaded++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
getGrade,
|
|
192
|
+
getGradeLabel,
|
|
193
|
+
makeBar,
|
|
194
|
+
truncate,
|
|
195
|
+
formatHeader,
|
|
196
|
+
generateId,
|
|
197
|
+
createGracefulShutdown,
|
|
198
|
+
loadEnvFile
|
|
199
|
+
};
|