mark-improving-agent 2.2.9 → 2.3.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/VERSION CHANGED
@@ -1 +1 @@
1
- 2.2.9
1
+ 2.3.1
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Behavior Playbook - Pattern-Based Playbook Extraction System
3
+ *
4
+ * Inspired by ReflexioAI's self-improvement framework:
5
+ * - Extracts actionable playbooks from user correction patterns
6
+ * - Clusters similar behaviors and aggregates into reusable playbooks
7
+ * - Tracks playbook effectiveness through success/failure metrics
8
+ * - Enables cross-session learning (what one user teaches, all sessions benefit)
9
+ *
10
+ * @module core/behavior-playbook
11
+ * @fileoverview Playbook extraction from interaction patterns
12
+ */
13
+ import { createLogger } from '../utils/logger.js';
14
+ import { atomicWriteJSON, readJSON, ensureDir } from '../storage/archive.js';
15
+ import { randomUUID } from 'crypto';
16
+ const logger = createLogger('[BehaviorPlaybook]');
17
+ const DEFAULT_CONFIG = {
18
+ minConfidence: 0.6,
19
+ maxPlaybooks: 100,
20
+ clusterThreshold: 0.8,
21
+ autoExtract: true,
22
+ };
23
+ const PLAYBOOKS_FILE = 'behavior-playbooks.json';
24
+ const CORRECTIONS_FILE = 'correction-events.json';
25
+ function extractContext(text) {
26
+ if (text.length <= 50)
27
+ return text;
28
+ return text.slice(0, 47) + '...';
29
+ }
30
+ function extractPlaybookFromCorrection(event) {
31
+ const { userMessage, correction } = event;
32
+ if (correction.length < 5 || userMessage.length < 5)
33
+ return null;
34
+ let trigger = '';
35
+ let action = '';
36
+ const tags = [];
37
+ const lowerCorrection = correction.toLowerCase();
38
+ if (lowerCorrection.startsWith("don't") || lowerCorrection.startsWith("do not") || lowerCorrection.startsWith("never")) {
39
+ const match = correction.match(/(?:don't|do not|never)\s+(.+)/i);
40
+ if (match) {
41
+ const avoided = match[1].trim();
42
+ trigger = `When about to: ${avoided}`;
43
+ action = `Avoid: ${avoided}`;
44
+ tags.push('avoid', 'user-warning');
45
+ }
46
+ }
47
+ else if (lowerCorrection.startsWith("you should") || lowerCorrection.startsWith("you need to") || lowerCorrection.startsWith("always")) {
48
+ const match = correction.match(/(?:you should|you need to|always)\s+(.+)/i);
49
+ if (match) {
50
+ const required = match[1].trim();
51
+ trigger = `When encountering relevant situation`;
52
+ action = `Always: ${required}`;
53
+ tags.push('positive', 'user-guidance');
54
+ }
55
+ }
56
+ else {
57
+ // Generic extraction
58
+ trigger = extractContext(userMessage);
59
+ action = correction;
60
+ tags.push('general', 'user-correction');
61
+ }
62
+ if (!trigger || !action)
63
+ return null;
64
+ return { trigger, action, tags, source: 'user-correction' };
65
+ }
66
+ function cosineSimilarity(a, b) {
67
+ const wordsA = new Set(a.toLowerCase().split(/\s+/));
68
+ const wordsB = new Set(b.toLowerCase().split(/\s+/));
69
+ const intersection = new Set([...wordsA].filter(x => wordsB.has(x)));
70
+ const union = new Set([...wordsA, ...wordsB]);
71
+ if (union.size === 0)
72
+ return 0;
73
+ return intersection.size / union.size;
74
+ }
75
+ export function createBehaviorPlaybook(dataDir, config = {}) {
76
+ const cfg = { ...DEFAULT_CONFIG, ...config };
77
+ const playbooks = [];
78
+ const corrections = [];
79
+ let dirty = false;
80
+ async function persist() {
81
+ if (!dirty)
82
+ return;
83
+ await Promise.all([
84
+ atomicWriteJSON(`${dataDir}/${PLAYBOOKS_FILE}`, playbooks),
85
+ atomicWriteJSON(`${dataDir}/${CORRECTIONS_FILE}`, corrections),
86
+ ]);
87
+ dirty = false;
88
+ }
89
+ async function boot() {
90
+ await ensureDir(dataDir);
91
+ const [loadedPlaybooks, loadedCorrections] = await Promise.all([
92
+ readJSON(`${dataDir}/${PLAYBOOKS_FILE}`, []),
93
+ readJSON(`${dataDir}/${CORRECTIONS_FILE}`, []),
94
+ ]);
95
+ if (loadedPlaybooks?.length)
96
+ playbooks.push(...loadedPlaybooks);
97
+ if (loadedCorrections?.length)
98
+ corrections.push(...loadedCorrections);
99
+ logger.info(`Booted with ${playbooks.length} playbooks, ${corrections.length} corrections`);
100
+ }
101
+ function addCorrection(event) {
102
+ const full = { ...event, id: randomUUID(), timestamp: Date.now() };
103
+ corrections.push(full);
104
+ if (corrections.length > 500)
105
+ corrections.splice(0, corrections.length - 500);
106
+ if (cfg.autoExtract)
107
+ extractPlaybook(full);
108
+ return full;
109
+ }
110
+ function extractPlaybook(event) {
111
+ const partial = extractPlaybookFromCorrection(event);
112
+ if (!partial)
113
+ return null;
114
+ // Check for similar existing playbook
115
+ for (const existing of playbooks) {
116
+ if (cosineSimilarity(existing.action, partial.action) > cfg.clusterThreshold) {
117
+ // Update existing playbook's confidence
118
+ existing.successCount++;
119
+ existing.confidence = Math.min(0.95, existing.confidence + 0.05);
120
+ existing.lastUsed = Date.now();
121
+ dirty = true;
122
+ return existing;
123
+ }
124
+ }
125
+ const playbook = {
126
+ id: randomUUID(),
127
+ trigger: partial.trigger,
128
+ action: partial.action,
129
+ confidence: 0.5,
130
+ successCount: 0,
131
+ failureCount: 0,
132
+ source: partial.source,
133
+ createdAt: Date.now(),
134
+ lastUsed: Date.now(),
135
+ tags: partial.tags,
136
+ version: 1,
137
+ };
138
+ playbooks.push(playbook);
139
+ if (playbooks.length > cfg.maxPlaybooks) {
140
+ playbooks.splice(0, playbooks.length - cfg.maxPlaybooks);
141
+ }
142
+ dirty = true;
143
+ logger.info(`Extracted playbook: ${playbook.action.slice(0, 50)}`);
144
+ return playbook;
145
+ }
146
+ function getPlaybook(id) {
147
+ return playbooks.find(p => p.id === id) ?? null;
148
+ }
149
+ function searchPlaybooks(query, limit = 5) {
150
+ const results = [];
151
+ for (const p of playbooks) {
152
+ if (p.confidence < cfg.minConfidence)
153
+ continue;
154
+ const queryLower = query.toLowerCase();
155
+ const triggerMatch = p.trigger.toLowerCase().includes(queryLower);
156
+ const actionMatch = p.action.toLowerCase().includes(queryLower);
157
+ const tagMatch = p.tags.some(t => t.toLowerCase().includes(queryLower));
158
+ if (!triggerMatch && !actionMatch && !tagMatch)
159
+ continue;
160
+ let score = 0;
161
+ let matchType = 'fuzzy';
162
+ if (actionMatch) {
163
+ score += 0.6;
164
+ matchType = 'exact';
165
+ }
166
+ if (triggerMatch)
167
+ score += 0.3;
168
+ if (tagMatch) {
169
+ score += 0.2;
170
+ if (matchType !== 'exact')
171
+ matchType = 'tag';
172
+ }
173
+ score *= p.confidence;
174
+ results.push({ playbook: p, relevanceScore: score, matchType });
175
+ }
176
+ return results.sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, limit);
177
+ }
178
+ function getPlaybooksByTag(tag) {
179
+ return playbooks.filter(p => p.tags.some(t => t.toLowerCase().includes(tag.toLowerCase())));
180
+ }
181
+ function markPlaybookHit(id, success) {
182
+ const p = playbooks.find(p => p.id === id);
183
+ if (!p)
184
+ return;
185
+ if (success) {
186
+ p.successCount++;
187
+ p.confidence = Math.min(0.95, p.confidence + (0.95 - p.confidence) * 0.1);
188
+ }
189
+ else {
190
+ p.failureCount++;
191
+ p.confidence = Math.max(0.3, p.confidence - 0.05);
192
+ }
193
+ p.lastUsed = Date.now();
194
+ dirty = true;
195
+ }
196
+ function getActivePlaybooks() {
197
+ return playbooks.filter(p => p.confidence >= cfg.minConfidence);
198
+ }
199
+ function getStats() {
200
+ const avgConfidence = playbooks.length ? playbooks.reduce((s, p) => s + p.confidence, 0) / playbooks.length : 0;
201
+ const tagCounts = new Map();
202
+ for (const p of playbooks) {
203
+ for (const t of p.tags) {
204
+ tagCounts.set(t, (tagCounts.get(t) ?? 0) + 1);
205
+ }
206
+ }
207
+ const topTags = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t]) => t);
208
+ return { totalPlaybooks: playbooks.length, avgConfidence, topTags };
209
+ }
210
+ return { addCorrection, extractPlaybook, getPlaybook, searchPlaybooks, getPlaybooksByTag, markPlaybookHit, getActivePlaybooks, getStats, boot, persist };
211
+ }
@@ -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
+ }
@@ -1 +1,2 @@
1
1
  export * from './privacy.js';
2
+ export * from './agent-shield.js';
package/dist/index.js CHANGED
@@ -10,3 +10,4 @@ export { createMCPProtocol } from './core/collaboration/mcp-protocol.js';
10
10
  export { createTruthTeller, formatTruthStatement } from './core/truth-teller.js';
11
11
  export { createActiveInferenceEngine, formatFreeEnergyMetrics, formatBeliefState } from './core/cognition/active-inference.js';
12
12
  export { createExpertModelsEngine } from './core/expert-models/index.js';
13
+ export { createBehaviorPlaybook } from './core/behavior-playbook.js';
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '2.2.9';
1
+ export const VERSION = '2.3.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mark-improving-agent",
3
- "version": "2.2.9",
3
+ "version": "2.3.1",
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",