mark-improving-agent 2.2.9 ā 2.3.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/VERSION +1 -1
- package/dist/core/security/agent-shield.js +545 -0
- package/dist/core/security/index.js +1 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.3.0
|
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Shield - AI Agent Security Scanner
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive security scanning for AI agent configurations based on:
|
|
5
|
+
* - OWASP Agentic Top 10 (2026)
|
|
6
|
+
* - AgentShield best practices (affaan-m/agentshield)
|
|
7
|
+
* - Microsoft Agent Governance Toolkit patterns
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - MCP Server Security Scanning
|
|
11
|
+
* - Permission Configuration Analysis
|
|
12
|
+
* - Hook Injection Detection
|
|
13
|
+
* - Tool Poisoning Detection
|
|
14
|
+
* - OWASP Compliance Checking
|
|
15
|
+
*
|
|
16
|
+
* @module core/security
|
|
17
|
+
* @fileoverview AI Agent Security Scanner - protects against configuration vulnerabilities
|
|
18
|
+
*/
|
|
19
|
+
import { createLogger } from '../../utils/logger.js';
|
|
20
|
+
const logger = createLogger('[AgentShield]');
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// OWASP Agentic Top 10 Mappings (2026)
|
|
23
|
+
// ============================================================================
|
|
24
|
+
const OWASP_MAPPINGS = {
|
|
25
|
+
'prompt_injection': { title: 'Prompt Injection', cwe: 'CWE-1366', description: 'Malicious instructions injected via external content' },
|
|
26
|
+
'information_disclosure': { title: 'Information Disclosure', cwe: 'CWE-200', description: 'Sensitive data exposed through agent outputs' },
|
|
27
|
+
'tool_poisoning': { title: 'Tool Poisoning', cwe: 'CWE-1392', description: 'Compromised or malicious tool definitions' },
|
|
28
|
+
'permission_creep': { title: 'Permission Creep', cwe: 'CWE-1395', description: 'Excessive tool permissions beyond requirements' },
|
|
29
|
+
'sandbox_escape': { title: 'Sandbox Escape', cwe: 'CWE-1394', description: 'Agent breaking out of execution constraints' },
|
|
30
|
+
'dependency_confusion': { title: 'Dependency Confusion', cwe: 'CWE-1393', description: 'Malicious packages replacing legitimate dependencies' },
|
|
31
|
+
'model_dos': { title: 'Model Denial of Service', cwe: 'CWE-1333', description: 'Excessive resource consumption via prompt manipulation' },
|
|
32
|
+
'agent_hijacking': { title: 'Agent Hijacking', cwe: 'CWE-1398', description: 'Unauthorized control of agent behavior' }
|
|
33
|
+
};
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Default Configuration
|
|
36
|
+
// ============================================================================
|
|
37
|
+
const DEFAULT_CONFIG = {
|
|
38
|
+
enableAutoFix: false,
|
|
39
|
+
enableSupplyChain: true,
|
|
40
|
+
enableNetworkChecks: true,
|
|
41
|
+
owaspLevel: 'standard'
|
|
42
|
+
};
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Secret Patterns
|
|
45
|
+
// ============================================================================
|
|
46
|
+
const SECRET_PATTERNS = [
|
|
47
|
+
{ name: 'Anthropic API Key', pattern: /sk-ant-[a-zA-Z0-9_-]{20,}/gi, severity: 'critical' },
|
|
48
|
+
{ name: 'OpenAI API Key', pattern: /sk-(?:proj-)?[a-zA-Z0-9_-]{40,}/gi, severity: 'critical' },
|
|
49
|
+
{ name: 'GitHub Token (Classic)', pattern: /ghp_[a-zA-Z0-9]{36}/gi, severity: 'critical' },
|
|
50
|
+
{ name: 'GitHub PAT', pattern: /github_pat_[a-zA-Z0-9_]{82}/gi, severity: 'critical' },
|
|
51
|
+
{ name: 'AWS Access Key', pattern: /(?:AKIA|A3T|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}/gi, severity: 'critical' },
|
|
52
|
+
{ name: 'Google API Key', pattern: /AIza[0-9A-Za-z_-]{35}/gi, severity: 'critical' },
|
|
53
|
+
{ name: 'Stripe Key', pattern: /sk_live_[0-9a-zA-Z_-]{24,}/gi, severity: 'critical' },
|
|
54
|
+
{ name: 'Slack Token', pattern: /xox[bprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*/gi, severity: 'high' },
|
|
55
|
+
{ name: 'JWT Token', pattern: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/gi, severity: 'high' },
|
|
56
|
+
{ name: 'Private Key', pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/gi, severity: 'critical' },
|
|
57
|
+
{ name: 'Hardcoded Password', pattern: /(?:password|passwd|pwd)[=:\s]*['"]?([^\s'"]{8,})['"]?/gi, severity: 'high' },
|
|
58
|
+
{ name: 'API Key Generic', pattern: /(?:api[_-]?key|apikey)[=:\s]*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi, severity: 'high' },
|
|
59
|
+
{ name: 'Bearer Token', pattern: /Bearer\s+[a-zA-Z0-9_-]{20,}/gi, severity: 'high' },
|
|
60
|
+
];
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Permission Risk Patterns
|
|
63
|
+
// ============================================================================
|
|
64
|
+
const PERMISSION_PATTERNS = [
|
|
65
|
+
{ tool: 'Bash', pattern: /Bash\(\*\)/g, risk: 'critical', suggestion: 'Restrict to specific commands: Bash(git *), Bash(npm *), Bash(node *)', owasp: 'permission_creep' },
|
|
66
|
+
{ tool: 'Write', pattern: /Write\(\*\)/g, risk: 'critical', suggestion: 'Restrict to project directories: Write(./src/*), Write(./tests/*)', owasp: 'permission_creep' },
|
|
67
|
+
{ tool: 'Edit', pattern: /Edit\(\*\)/g, risk: 'critical', suggestion: 'Restrict to specific files: Edit(*.ts), Edit(*.js)', owasp: 'permission_creep' },
|
|
68
|
+
{ tool: 'Read', pattern: /Read\(\*\)/g, risk: 'high', suggestion: 'Restrict to project files: Read(./**/*)', owasp: 'permission_creep' },
|
|
69
|
+
{ tool: 'Bash', pattern: /Bash\(rm\s+-[rf]+\s+\*\)/g, risk: 'critical', suggestion: 'Never allow recursive rm without specific paths', owasp: 'sandbox_escape' },
|
|
70
|
+
{ tool: 'Bash', pattern: /Bash\(sudo\s+\*\)/g, risk: 'critical', suggestion: 'Avoid sudo access, use specific allowed commands', owasp: 'sandbox_escape' },
|
|
71
|
+
{ tool: 'Bash', pattern: /Bash\(chmod\s+777\)/g, risk: 'critical', suggestion: 'Never use chmod 777, use minimal permissions', owasp: 'sandbox_escape' },
|
|
72
|
+
{ tool: 'Bash', pattern: /Bash\(--dangerously-skip-permissions\)/g, risk: 'critical', suggestion: 'Do not use dangerously-skip-permissions', owasp: 'permission_creep' },
|
|
73
|
+
];
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Hook Injection Patterns
|
|
76
|
+
// ============================================================================
|
|
77
|
+
const HOOK_INJECTION_PATTERNS = [
|
|
78
|
+
{ name: 'Command Injection via Interpolation', pattern: /\${(?:file|filename|path)}/g, severity: 'critical', description: 'File variables in shell commands can be manipulated for command injection', owasp: 'prompt_injection' },
|
|
79
|
+
{ name: 'Data Exfiltration', pattern: /curl\s+.+-X\s+POST.+\$\{/g, severity: 'critical', description: 'Variable interpolation in POST requests can exfiltrate data', owasp: 'information_disclosure' },
|
|
80
|
+
{ name: 'Silent Error Suppression', pattern: /(?:2\s*>\s*\/dev\/null|\|\|\s*true)\s*$/gm, severity: 'high', description: 'Error suppression can hide security failures', owasp: 'tool_poisoning' },
|
|
81
|
+
{ name: 'Remote Script Download', pattern: /(?:curl|wget|npm|cargo)\s+(?:install|-y)\s+(?:http|ftp|\.\.\/)/gi, severity: 'critical', description: 'Downloading and executing scripts from remote sources', owasp: 'dependency_confusion' },
|
|
82
|
+
{ name: 'Package Install Hook', pattern: /(?:npm\s+install|cargo\s+install|gem\s+install)\s+(?:-g|--global)/g, severity: 'high', description: 'Global package installation in hooks can escalate privileges', owasp: 'dependency_confusion' },
|
|
83
|
+
{ name: 'Container Escape', pattern: /--(?:privileged|pid=host|network=host)/g, severity: 'critical', description: 'Container escape vectors in Docker commands', owasp: 'sandbox_escape' },
|
|
84
|
+
{ name: 'Credential Access', pattern: /(?:macOS\s+Keychain|keychain|gnome-keyring|\/etc\/shadow)/gi, severity: 'critical', description: 'Attempting to access system credential stores', owasp: 'information_disclosure' },
|
|
85
|
+
{ name: 'Reverse Shell', pattern: /\/(?:dev\/tcp|\.sock\/)/gi, severity: 'critical', description: 'Network socket access patterns indicating reverse shell', owasp: 'agent_hijacking' },
|
|
86
|
+
{ name: 'Clipboard Exfiltration', pattern: /(?:pbcopy|xclip|xsel|wl-copy)/gi, severity: 'high', description: 'Clipboard access can exfiltrate sensitive data', owasp: 'information_disclosure' },
|
|
87
|
+
{ name: 'Log Tampering', pattern: /(?:history\s+-c|journalctl\s+--vacuum|rm\s+\/var\/log)/gi, severity: 'critical', description: 'Log deletion attempts indicate anti-forensics', owasp: 'agent_hijacking' },
|
|
88
|
+
];
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// MCP Server Risk Patterns
|
|
91
|
+
// ============================================================================
|
|
92
|
+
const MCP_SERVER_PATTERNS = [
|
|
93
|
+
{ name: 'Shell MCP', pattern: /(?:shell|bash|cmd|exec)/gi, severity: 'critical', description: 'Shell MCP servers provide unrestricted command execution', owasp: 'sandbox_escape' },
|
|
94
|
+
{ name: 'Filesystem with Root', pattern: /(?:filesystem|filesys).*root/gi, severity: 'critical', description: 'Filesystem access with root privileges is highly dangerous', owasp: 'sandbox_escape' },
|
|
95
|
+
{ name: 'Database MCP', pattern: /(?:postgres|mysql|mongodb|redis|sql)/gi, severity: 'high', description: 'Direct database access MCP servers should be carefully scoped', owasp: 'permission_creep' },
|
|
96
|
+
{ name: 'Browser Automation', pattern: /(?:browser|chrome|playwright|selenium)/gi, severity: 'high', description: 'Browser automation can access sensitive web data', owasp: 'information_disclosure' },
|
|
97
|
+
{ name: 'Remote Transport', pattern: /(?:sse|streamable|http|https):\/\//gi, severity: 'high', description: 'Remote MCP transports increase attack surface', owasp: 'tool_poisoning' },
|
|
98
|
+
{ name: 'Auto-Approve', pattern: /autoApprove:\s*true/gi, severity: 'critical', description: 'Auto-approve skips security confirmation', owasp: 'permission_creep' },
|
|
99
|
+
{ name: 'No Timeout', pattern: /timeout:\s*(?:null|0|infinite)/gi, severity: 'medium', description: 'Missing timeout can cause resource exhaustion', owasp: 'model_dos' },
|
|
100
|
+
{ name: 'Network Binding', pattern: /0\.0\.0\.0|::/gi, severity: 'high', description: 'Binding to all interfaces exposes services publicly', owasp: 'sandbox_escape' },
|
|
101
|
+
{ name: 'Sensitive File Args', pattern: /(?:--|\.)(?:env|pem|key|credentials|secret)/gi, severity: 'high', description: 'Passing sensitive files as arguments', owasp: 'information_disclosure' },
|
|
102
|
+
];
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Network Exposure Patterns
|
|
105
|
+
// ============================================================================
|
|
106
|
+
const NETWORK_PATTERNS = [
|
|
107
|
+
{ name: 'Unrestricted Curl', pattern: /curl\s+\*/g, severity: 'high', owasp: 'network_exposure' },
|
|
108
|
+
{ name: 'Unrestricted Wget', pattern: /wget\s+\*/g, severity: 'high', owasp: 'network_exposure' },
|
|
109
|
+
{ name: 'Unrestricted SSH', pattern: /ssh\s+\*/g, severity: 'critical', owasp: 'sandbox_escape' },
|
|
110
|
+
{ name: 'Unrestricted SCP', pattern: /scp\s+\*/g, severity: 'critical', owasp: 'sandbox_escape' },
|
|
111
|
+
{ name: 'Port Scanning', pattern: /(?:nc|netcat|nmap)/gi, severity: 'high', owasp: 'reconnaissance' },
|
|
112
|
+
];
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// Supply Chain Patterns
|
|
115
|
+
// ============================================================================
|
|
116
|
+
const SUPPLY_CHAIN_PATTERNS = [
|
|
117
|
+
{ name: 'Typosquatting Vector', pattern: /npx\s+-[ly]\s+(?!-|node\b)/gi, severity: 'high', description: 'Auto-install without confirmation is a typosquatting vector', owasp: 'dependency_confusion' },
|
|
118
|
+
{ name: 'Unpinned Dependency', pattern: /(?:npm\s+install|cargo\s+add)\s+(?!--exact|--pin|-E|-P)/gi, severity: 'medium', description: 'Dependencies should be pinned to specific versions', owasp: 'dependency_confusion' },
|
|
119
|
+
{ name: 'Git Clone Recursive', pattern: /git\s+clone\s+--recursive/gi, severity: 'high', description: 'Recursive clone can pull malicious submodules', owasp: 'dependency_confusion' },
|
|
120
|
+
];
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// Utility Functions
|
|
123
|
+
// ============================================================================
|
|
124
|
+
function generateFindingId() {
|
|
125
|
+
return `HF-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
126
|
+
}
|
|
127
|
+
function calculateScore(findings) {
|
|
128
|
+
const weights = {
|
|
129
|
+
critical: 40,
|
|
130
|
+
high: 25,
|
|
131
|
+
medium: 10,
|
|
132
|
+
low: 5,
|
|
133
|
+
info: 0
|
|
134
|
+
};
|
|
135
|
+
const totalDeduction = findings.reduce((sum, f) => sum + weights[f.severity], 0);
|
|
136
|
+
return Math.max(0, 100 - totalDeduction);
|
|
137
|
+
}
|
|
138
|
+
function calculateGrade(score) {
|
|
139
|
+
if (score >= 90)
|
|
140
|
+
return 'A';
|
|
141
|
+
if (score >= 70)
|
|
142
|
+
return 'B';
|
|
143
|
+
if (score >= 50)
|
|
144
|
+
return 'C';
|
|
145
|
+
if (score >= 30)
|
|
146
|
+
return 'D';
|
|
147
|
+
return 'F';
|
|
148
|
+
}
|
|
149
|
+
function countFindingsByCategory(findings) {
|
|
150
|
+
const breakdown = {
|
|
151
|
+
secrets: 0,
|
|
152
|
+
permissions: 0,
|
|
153
|
+
hooks: 0,
|
|
154
|
+
mcp_servers: 0,
|
|
155
|
+
agents: 0,
|
|
156
|
+
network: 0,
|
|
157
|
+
supply_chain: 0
|
|
158
|
+
};
|
|
159
|
+
for (const f of findings) {
|
|
160
|
+
breakdown[f.category]++;
|
|
161
|
+
}
|
|
162
|
+
return breakdown;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Create an Agent Shield security scanner
|
|
166
|
+
*
|
|
167
|
+
* @param config - Optional configuration overrides
|
|
168
|
+
* @returns AgentShield scanner instance
|
|
169
|
+
*/
|
|
170
|
+
export function createAgentShield(config = {}) {
|
|
171
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
172
|
+
logger.info('Creating AgentShield scanner', { config: cfg });
|
|
173
|
+
// Track statistics
|
|
174
|
+
let totalScans = 0;
|
|
175
|
+
let findingsCount = 0;
|
|
176
|
+
/**
|
|
177
|
+
* Scan content for security issues
|
|
178
|
+
*/
|
|
179
|
+
function scanContent(content, filename) {
|
|
180
|
+
totalScans++;
|
|
181
|
+
const findings = [];
|
|
182
|
+
// 1. Scan for secrets
|
|
183
|
+
for (const { name, pattern, severity } of SECRET_PATTERNS) {
|
|
184
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
185
|
+
let match;
|
|
186
|
+
while ((match = regex.exec(content)) !== null) {
|
|
187
|
+
const evidence = match[0].substring(0, 80) + (match[0].length > 80 ? '...' : '');
|
|
188
|
+
findings.push({
|
|
189
|
+
id: generateFindingId(),
|
|
190
|
+
category: 'secrets',
|
|
191
|
+
severity,
|
|
192
|
+
title: `Sensitive ${name} detected`,
|
|
193
|
+
description: `Hardcoded ${name} found in configuration`,
|
|
194
|
+
evidence,
|
|
195
|
+
file: filename,
|
|
196
|
+
autoFixable: true,
|
|
197
|
+
fixSuggestion: `Replace hardcoded ${name} with environment variable reference`,
|
|
198
|
+
cwe: 'CWE-798',
|
|
199
|
+
owaspMapping: 'information_disclosure'
|
|
200
|
+
});
|
|
201
|
+
findingsCount++;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// 2. Scan for permission issues
|
|
205
|
+
for (const { tool, pattern, risk, suggestion, owasp } of PERMISSION_PATTERNS) {
|
|
206
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
207
|
+
let match;
|
|
208
|
+
while ((match = regex.exec(content)) !== null) {
|
|
209
|
+
const evidence = match[0].substring(0, 80);
|
|
210
|
+
findings.push({
|
|
211
|
+
id: generateFindingId(),
|
|
212
|
+
category: 'permissions',
|
|
213
|
+
severity: risk,
|
|
214
|
+
title: `Overly permissive ${tool} access`,
|
|
215
|
+
description: `${tool} has unrestricted wildcard permissions`,
|
|
216
|
+
evidence,
|
|
217
|
+
file: filename,
|
|
218
|
+
autoFixable: false,
|
|
219
|
+
fixSuggestion: suggestion,
|
|
220
|
+
owaspMapping: owasp
|
|
221
|
+
});
|
|
222
|
+
findingsCount++;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// 3. Scan for hook injection
|
|
226
|
+
for (const { name, pattern, severity, description, owasp } of HOOK_INJECTION_PATTERNS) {
|
|
227
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
228
|
+
let match;
|
|
229
|
+
while ((match = regex.exec(content)) !== null) {
|
|
230
|
+
const evidence = match[0].substring(0, 80);
|
|
231
|
+
findings.push({
|
|
232
|
+
id: generateFindingId(),
|
|
233
|
+
category: 'hooks',
|
|
234
|
+
severity,
|
|
235
|
+
title: name,
|
|
236
|
+
description,
|
|
237
|
+
evidence,
|
|
238
|
+
file: filename,
|
|
239
|
+
autoFixable: false,
|
|
240
|
+
owaspMapping: owasp
|
|
241
|
+
});
|
|
242
|
+
findingsCount++;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// 4. Scan for MCP server issues
|
|
246
|
+
for (const { name, pattern, severity, description, owasp } of MCP_SERVER_PATTERNS) {
|
|
247
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
248
|
+
let match;
|
|
249
|
+
while ((match = regex.exec(content)) !== null) {
|
|
250
|
+
const evidence = match[0].substring(0, 80);
|
|
251
|
+
findings.push({
|
|
252
|
+
id: generateFindingId(),
|
|
253
|
+
category: 'mcp_servers',
|
|
254
|
+
severity,
|
|
255
|
+
title: `Risky MCP server: ${name}`,
|
|
256
|
+
description,
|
|
257
|
+
evidence,
|
|
258
|
+
file: filename,
|
|
259
|
+
autoFixable: false,
|
|
260
|
+
owaspMapping: owasp
|
|
261
|
+
});
|
|
262
|
+
findingsCount++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// 5. Scan for network exposure
|
|
266
|
+
if (cfg.enableNetworkChecks) {
|
|
267
|
+
for (const { name, pattern, severity, owasp } of NETWORK_PATTERNS) {
|
|
268
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
269
|
+
let match;
|
|
270
|
+
while ((match = regex.exec(content)) !== null) {
|
|
271
|
+
const evidence = match[0].substring(0, 80);
|
|
272
|
+
findings.push({
|
|
273
|
+
id: generateFindingId(),
|
|
274
|
+
category: 'network',
|
|
275
|
+
severity,
|
|
276
|
+
title: `Network exposure: ${name}`,
|
|
277
|
+
description: `Unrestricted network command detected`,
|
|
278
|
+
evidence,
|
|
279
|
+
file: filename,
|
|
280
|
+
autoFixable: false,
|
|
281
|
+
owaspMapping: owasp
|
|
282
|
+
});
|
|
283
|
+
findingsCount++;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// 6. Scan for supply chain issues
|
|
288
|
+
if (cfg.enableSupplyChain) {
|
|
289
|
+
for (const { name, pattern, severity, description, owasp } of SUPPLY_CHAIN_PATTERNS) {
|
|
290
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
291
|
+
let match;
|
|
292
|
+
while ((match = regex.exec(content)) !== null) {
|
|
293
|
+
const evidence = match[0].substring(0, 80);
|
|
294
|
+
findings.push({
|
|
295
|
+
id: generateFindingId(),
|
|
296
|
+
category: 'supply_chain',
|
|
297
|
+
severity,
|
|
298
|
+
title: `Supply chain risk: ${name}`,
|
|
299
|
+
description,
|
|
300
|
+
evidence,
|
|
301
|
+
file: filename,
|
|
302
|
+
autoFixable: false,
|
|
303
|
+
owaspMapping: owasp
|
|
304
|
+
});
|
|
305
|
+
findingsCount++;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
logger.debug(`Scanned ${filename || 'content'}: ${findings.length} findings`);
|
|
310
|
+
return findings;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Scan a file for security issues
|
|
314
|
+
*/
|
|
315
|
+
async function scanFile(filePath) {
|
|
316
|
+
const fs = await import('fs/promises');
|
|
317
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
318
|
+
const findings = scanContent(content, filePath);
|
|
319
|
+
const score = calculateScore(findings);
|
|
320
|
+
return {
|
|
321
|
+
file: filePath,
|
|
322
|
+
findings,
|
|
323
|
+
grade: calculateGrade(score),
|
|
324
|
+
score
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Scan multiple files
|
|
329
|
+
*/
|
|
330
|
+
async function scanMultipleFiles(filePaths) {
|
|
331
|
+
const allFindings = [];
|
|
332
|
+
for (const filePath of filePaths) {
|
|
333
|
+
try {
|
|
334
|
+
const result = await scanFile(filePath);
|
|
335
|
+
allFindings.push(...result.findings);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
logger.warn(`Failed to scan file: ${filePath}`, { error });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return generateReport(allFindings);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Analyze MCP server configuration
|
|
345
|
+
*/
|
|
346
|
+
function analyzeMCPServer(config) {
|
|
347
|
+
const checks = [];
|
|
348
|
+
// Parse MCP server name
|
|
349
|
+
const nameMatch = config.match(/(?:name|name:)\s*['"]?([^'",\n]+)['"]?/i);
|
|
350
|
+
const commandMatch = config.match(/(?:command|cmd|executable):\s*['"]?([^'",\n]+)['"]?/i);
|
|
351
|
+
const name = nameMatch?.[1] || 'Unknown';
|
|
352
|
+
const command = commandMatch?.[1] || '';
|
|
353
|
+
const risks = [];
|
|
354
|
+
let isLocal = true;
|
|
355
|
+
let hasTimeout = true;
|
|
356
|
+
let permissionScope = 'unknown';
|
|
357
|
+
// Check for local vs remote
|
|
358
|
+
if (/https?:\/\/|sse:|streamable:/i.test(command)) {
|
|
359
|
+
isLocal = false;
|
|
360
|
+
risks.push('Remote transport - increased attack surface');
|
|
361
|
+
}
|
|
362
|
+
// Check for shell commands
|
|
363
|
+
if (/shell|bash|cmd|exec/i.test(command)) {
|
|
364
|
+
risks.push('Shell execution - highest risk');
|
|
365
|
+
}
|
|
366
|
+
// Check for timeout
|
|
367
|
+
if (/timeout:\s*(?:null|0|infinite)/i.test(config)) {
|
|
368
|
+
hasTimeout = false;
|
|
369
|
+
risks.push('No timeout configured - resource exhaustion risk');
|
|
370
|
+
}
|
|
371
|
+
// Check for auto-approve
|
|
372
|
+
if (/autoApprove:\s*true/i.test(config)) {
|
|
373
|
+
risks.push('Auto-approve enabled - no user confirmation');
|
|
374
|
+
}
|
|
375
|
+
// Check for network binding
|
|
376
|
+
if (/0\.0\.0\.0|::/i.test(config)) {
|
|
377
|
+
risks.push('Network binding to all interfaces');
|
|
378
|
+
}
|
|
379
|
+
checks.push({
|
|
380
|
+
name,
|
|
381
|
+
command,
|
|
382
|
+
risks,
|
|
383
|
+
isLocal,
|
|
384
|
+
hasTimeout,
|
|
385
|
+
permissionScope
|
|
386
|
+
});
|
|
387
|
+
return checks;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Analyze permissions configuration
|
|
391
|
+
*/
|
|
392
|
+
function analyzePermissions(config) {
|
|
393
|
+
const analyses = [];
|
|
394
|
+
for (const { tool, pattern, risk, suggestion } of PERMISSION_PATTERNS) {
|
|
395
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
396
|
+
if (regex.test(config)) {
|
|
397
|
+
const match = config.match(regex);
|
|
398
|
+
analyses.push({
|
|
399
|
+
tool,
|
|
400
|
+
pattern: match?.[0] || pattern.source,
|
|
401
|
+
risk,
|
|
402
|
+
suggestion
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return analyses;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Generate a comprehensive security report
|
|
410
|
+
*/
|
|
411
|
+
function generateReport(findings) {
|
|
412
|
+
const score = calculateScore(findings);
|
|
413
|
+
const criticalCount = findings.filter(f => f.severity === 'critical').length;
|
|
414
|
+
const highCount = findings.filter(f => f.severity === 'high').length;
|
|
415
|
+
const mediumCount = findings.filter(f => f.severity === 'medium').length;
|
|
416
|
+
const lowCount = findings.filter(f => f.severity === 'low').length;
|
|
417
|
+
const infoCount = findings.filter(f => f.severity === 'info').length;
|
|
418
|
+
const autoFixableCount = findings.filter(f => f.autoFixable).length;
|
|
419
|
+
// Generate summary
|
|
420
|
+
let summary = '';
|
|
421
|
+
if (criticalCount > 0) {
|
|
422
|
+
summary = `šØ CRITICAL: ${criticalCount} critical security issues found. Immediate action required.`;
|
|
423
|
+
}
|
|
424
|
+
else if (highCount > 0) {
|
|
425
|
+
summary = `ā ļø HIGH: ${highCount} high-risk issues found. Review and fix soon.`;
|
|
426
|
+
}
|
|
427
|
+
else if (mediumCount > 0) {
|
|
428
|
+
summary = `ā” MEDIUM: ${mediumCount} medium-risk issues found. Consider addressing.`;
|
|
429
|
+
}
|
|
430
|
+
else if (lowCount > 0) {
|
|
431
|
+
summary = `ā¹ļø LOW: ${lowCount} low-risk issues found. Mostly informational.`;
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
summary = `ā
No security issues found. Configuration appears secure.`;
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
timestamp: new Date().toISOString(),
|
|
438
|
+
filesScanned: new Set(findings.map(f => f.file).filter(Boolean)).size,
|
|
439
|
+
totalFindings: findings.length,
|
|
440
|
+
grade: calculateGrade(score),
|
|
441
|
+
score,
|
|
442
|
+
scoreBreakdown: countFindingsByCategory(findings),
|
|
443
|
+
findings,
|
|
444
|
+
criticalCount,
|
|
445
|
+
highCount,
|
|
446
|
+
mediumCount,
|
|
447
|
+
lowCount,
|
|
448
|
+
infoCount,
|
|
449
|
+
autoFixableCount,
|
|
450
|
+
summary
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Get OWASP compliance status
|
|
455
|
+
*/
|
|
456
|
+
function getOWASPCompliance() {
|
|
457
|
+
const gaps = [];
|
|
458
|
+
const requiredCategories = ['secrets', 'permissions', 'hooks', 'mcp_servers'];
|
|
459
|
+
// Check if all required categories have checks
|
|
460
|
+
for (const category of requiredCategories) {
|
|
461
|
+
let hasChecks = false;
|
|
462
|
+
switch (category) {
|
|
463
|
+
case 'secrets':
|
|
464
|
+
hasChecks = SECRET_PATTERNS.length > 0;
|
|
465
|
+
break;
|
|
466
|
+
case 'permissions':
|
|
467
|
+
hasChecks = PERMISSION_PATTERNS.length > 0;
|
|
468
|
+
break;
|
|
469
|
+
case 'hooks':
|
|
470
|
+
hasChecks = HOOK_INJECTION_PATTERNS.length > 0;
|
|
471
|
+
break;
|
|
472
|
+
case 'mcp_servers':
|
|
473
|
+
hasChecks = MCP_SERVER_PATTERNS.length > 0;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
if (!hasChecks) {
|
|
477
|
+
gaps.push(`Missing checks for ${category}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
compliant: gaps.length === 0,
|
|
482
|
+
gaps
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
scanContent,
|
|
487
|
+
scanFile,
|
|
488
|
+
scanMultipleFiles,
|
|
489
|
+
analyzeMCPServer,
|
|
490
|
+
analyzePermissions,
|
|
491
|
+
generateReport,
|
|
492
|
+
getOWASPCompliance
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Quick scan - scan a string content for security issues
|
|
497
|
+
*/
|
|
498
|
+
export function quickScan(content) {
|
|
499
|
+
const shield = createAgentShield();
|
|
500
|
+
return shield.scanContent(content);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Generate a formatted security report
|
|
504
|
+
*/
|
|
505
|
+
export function generateSecurityReport(findings) {
|
|
506
|
+
const shield = createAgentShield();
|
|
507
|
+
const report = shield.generateReport(findings);
|
|
508
|
+
let output = 'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n';
|
|
509
|
+
output += 'ā HeartFlow Agent Shield Security Report ā\n';
|
|
510
|
+
output += 'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n\n';
|
|
511
|
+
output += `š Overall Grade: ${report.grade} (${report.score}/100)\n`;
|
|
512
|
+
output += `š
Timestamp: ${report.timestamp}\n`;
|
|
513
|
+
output += `š Files Scanned: ${report.filesScanned}\n`;
|
|
514
|
+
output += `š Total Findings: ${report.totalFindings}\n\n`;
|
|
515
|
+
output += 'š Score Breakdown:\n';
|
|
516
|
+
for (const [category, count] of Object.entries(report.scoreBreakdown)) {
|
|
517
|
+
if (count > 0) {
|
|
518
|
+
output += ` ${category}: ${count}\n`;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
output += '\nš Severity Summary:\n';
|
|
522
|
+
output += ` Critical: ${report.criticalCount}\n`;
|
|
523
|
+
output += ` High: ${report.highCount}\n`;
|
|
524
|
+
output += ` Medium: ${report.mediumCount}\n`;
|
|
525
|
+
output += ` Low: ${report.lowCount}\n`;
|
|
526
|
+
output += ` Info: ${report.infoCount}\n`;
|
|
527
|
+
if (report.autoFixableCount > 0) {
|
|
528
|
+
output += `\nš§ Auto-fixable Issues: ${report.autoFixableCount}\n`;
|
|
529
|
+
}
|
|
530
|
+
output += `\n${report.summary}\n`;
|
|
531
|
+
if (report.findings.length > 0 && report.findings.length <= 20) {
|
|
532
|
+
output += '\nš Top Findings:\n';
|
|
533
|
+
for (const finding of report.findings.slice(0, 20)) {
|
|
534
|
+
const severityIcon = finding.severity === 'critical' ? 'š“' :
|
|
535
|
+
finding.severity === 'high' ? 'š ' :
|
|
536
|
+
finding.severity === 'medium' ? 'š”' : 'šµ';
|
|
537
|
+
output += ` ${severityIcon} [${finding.severity.toUpperCase()}] ${finding.title}\n`;
|
|
538
|
+
output += ` Evidence: ${finding.evidence.substring(0, 60)}...\n`;
|
|
539
|
+
if (finding.fixSuggestion) {
|
|
540
|
+
output += ` Fix: ${finding.fixSuggestion}\n`;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return output;
|
|
545
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '2.
|
|
1
|
+
export const VERSION = '2.3.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mark-improving-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Self-evolving AI agent with permanent memory, identity continuity, and self-evolution ā for AI agents that need to remember, learn, and evolve across sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|