ideaco 1.1.5
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/.dockerignore +33 -0
- package/.nvmrc +1 -0
- package/ARCHITECTURE.md +394 -0
- package/Dockerfile +50 -0
- package/LICENSE +29 -0
- package/README.md +206 -0
- package/bin/i18n.js +46 -0
- package/bin/ideaco.js +494 -0
- package/deploy.sh +15 -0
- package/docker-compose.yml +30 -0
- package/electron/main.cjs +986 -0
- package/electron/preload.cjs +14 -0
- package/electron/web-backends.cjs +854 -0
- package/jsconfig.json +8 -0
- package/next.config.mjs +34 -0
- package/package.json +134 -0
- package/postcss.config.mjs +6 -0
- package/public/demo/dashboard.png +0 -0
- package/public/demo/employee.png +0 -0
- package/public/demo/messages.png +0 -0
- package/public/demo/office.png +0 -0
- package/public/demo/requirement.png +0 -0
- package/public/logo.jpeg +0 -0
- package/public/logo.png +0 -0
- package/scripts/prepare-electron.js +67 -0
- package/scripts/release.js +76 -0
- package/src/app/api/agents/[agentId]/chat/route.js +70 -0
- package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
- package/src/app/api/agents/[agentId]/route.js +106 -0
- package/src/app/api/avatar/route.js +104 -0
- package/src/app/api/browse-dir/route.js +44 -0
- package/src/app/api/chat/route.js +265 -0
- package/src/app/api/company/factory-reset/route.js +43 -0
- package/src/app/api/company/route.js +82 -0
- package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
- package/src/app/api/departments/route.js +92 -0
- package/src/app/api/group-chat-loop/events/route.js +70 -0
- package/src/app/api/group-chat-loop/route.js +94 -0
- package/src/app/api/mailbox/route.js +100 -0
- package/src/app/api/messages/route.js +14 -0
- package/src/app/api/providers/[id]/configure/route.js +21 -0
- package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
- package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
- package/src/app/api/providers/route.js +11 -0
- package/src/app/api/requirements/route.js +242 -0
- package/src/app/api/secretary/route.js +65 -0
- package/src/app/api/system/cli-backends/route.js +91 -0
- package/src/app/api/system/cron/route.js +110 -0
- package/src/app/api/system/knowledge/route.js +104 -0
- package/src/app/api/system/plugins/route.js +40 -0
- package/src/app/api/system/skills/route.js +46 -0
- package/src/app/api/system/status/route.js +46 -0
- package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
- package/src/app/api/talent-market/[profileId]/route.js +17 -0
- package/src/app/api/talent-market/route.js +26 -0
- package/src/app/api/teams/route.js +773 -0
- package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
- package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
- package/src/app/globals.css +130 -0
- package/src/app/layout.jsx +40 -0
- package/src/app/page.jsx +97 -0
- package/src/components/AgentChatModal.jsx +164 -0
- package/src/components/AgentDetailModal.jsx +425 -0
- package/src/components/AgentSpyModal.jsx +481 -0
- package/src/components/AvatarGrid.jsx +29 -0
- package/src/components/BossProfileModal.jsx +162 -0
- package/src/components/CachedAvatar.jsx +77 -0
- package/src/components/ChatPanel.jsx +219 -0
- package/src/components/ChatShared.jsx +255 -0
- package/src/components/DepartmentDetail.jsx +842 -0
- package/src/components/DepartmentView.jsx +367 -0
- package/src/components/FileReference.jsx +260 -0
- package/src/components/FilesView.jsx +465 -0
- package/src/components/GroupChatView.jsx +799 -0
- package/src/components/Mailbox.jsx +926 -0
- package/src/components/MessagesView.jsx +112 -0
- package/src/components/OnboardingGuide.jsx +209 -0
- package/src/components/OrgTree.jsx +151 -0
- package/src/components/Overview.jsx +391 -0
- package/src/components/PixelOffice.jsx +2281 -0
- package/src/components/ProviderGrid.jsx +551 -0
- package/src/components/ProvidersBoard.jsx +16 -0
- package/src/components/RequirementDetail.jsx +1279 -0
- package/src/components/RequirementsBoard.jsx +187 -0
- package/src/components/SecretarySettings.jsx +295 -0
- package/src/components/SetupWizard.jsx +388 -0
- package/src/components/Sidebar.jsx +169 -0
- package/src/components/SystemMonitor.jsx +808 -0
- package/src/components/TalentMarket.jsx +183 -0
- package/src/components/TeamDetail.jsx +697 -0
- package/src/core/agent/base-agent.js +104 -0
- package/src/core/agent/chat-store.js +602 -0
- package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
- package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
- package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
- package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
- package/src/core/agent/cli-agent/backends/index.js +27 -0
- package/src/core/agent/cli-agent/backends/registry.js +580 -0
- package/src/core/agent/cli-agent/index.js +154 -0
- package/src/core/agent/index.js +60 -0
- package/src/core/agent/llm-agent/client.js +320 -0
- package/src/core/agent/llm-agent/index.js +97 -0
- package/src/core/agent/message-bus.js +211 -0
- package/src/core/agent/session.js +608 -0
- package/src/core/agent/tools.js +596 -0
- package/src/core/agent/web-agent/backends/base-backend.js +180 -0
- package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
- package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
- package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
- package/src/core/agent/web-agent/backends/index.js +91 -0
- package/src/core/agent/web-agent/index.js +278 -0
- package/src/core/agent/web-agent/web-client.js +407 -0
- package/src/core/employee/base-employee.js +1088 -0
- package/src/core/employee/index.js +35 -0
- package/src/core/employee/knowledge.js +327 -0
- package/src/core/employee/lifecycle.js +990 -0
- package/src/core/employee/memory/index.js +642 -0
- package/src/core/employee/memory/store.js +143 -0
- package/src/core/employee/performance.js +224 -0
- package/src/core/employee/secretary.js +625 -0
- package/src/core/employee/skills.js +398 -0
- package/src/core/index.js +38 -0
- package/src/core/organization/company.js +2600 -0
- package/src/core/organization/department.js +737 -0
- package/src/core/organization/group-chat-loop.js +264 -0
- package/src/core/organization/index.js +8 -0
- package/src/core/organization/persistence.js +111 -0
- package/src/core/organization/team.js +267 -0
- package/src/core/organization/workforce/hr.js +377 -0
- package/src/core/organization/workforce/providers.js +468 -0
- package/src/core/organization/workforce/role-archetypes.js +805 -0
- package/src/core/organization/workforce/talent-market.js +205 -0
- package/src/core/prompts.js +532 -0
- package/src/core/requirement.js +1789 -0
- package/src/core/system/audit.js +483 -0
- package/src/core/system/cron.js +449 -0
- package/src/core/system/index.js +7 -0
- package/src/core/system/plugin.js +2183 -0
- package/src/core/utils/json-parse.js +188 -0
- package/src/core/workspace.js +239 -0
- package/src/lib/api-i18n.js +211 -0
- package/src/lib/avatar.js +268 -0
- package/src/lib/client-store.js +1025 -0
- package/src/lib/config-validator.js +483 -0
- package/src/lib/format-time.js +22 -0
- package/src/lib/hooks.js +414 -0
- package/src/lib/i18n.js +134 -0
- package/src/lib/paths.js +23 -0
- package/src/lib/store.js +72 -0
- package/src/locales/de.js +393 -0
- package/src/locales/en.js +1054 -0
- package/src/locales/es.js +393 -0
- package/src/locales/fr.js +393 -0
- package/src/locales/ja.js +501 -0
- package/src/locales/ko.js +513 -0
- package/src/locales/zh.js +828 -0
- package/tailwind.config.mjs +11 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Audit System - Enterprise-grade operation logging and access control
|
|
3
|
+
*
|
|
4
|
+
* Distilled from OpenClaw's security module (vendor/openclaw/src/security/)
|
|
5
|
+
* Re-implemented for the AI enterprise simulation context
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Audit trail for all tool calls and agent actions
|
|
9
|
+
* - Dangerous command detection and approval workflow
|
|
10
|
+
* - Secret scanning in agent outputs
|
|
11
|
+
* - Permission policies per agent/department
|
|
12
|
+
*/
|
|
13
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { AUDIT_DIR } from '../../lib/paths.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Audit event severity levels
|
|
20
|
+
*/
|
|
21
|
+
export const AuditLevel = {
|
|
22
|
+
INFO: 'info',
|
|
23
|
+
WARN: 'warn',
|
|
24
|
+
CRITICAL: 'critical',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Audit event categories
|
|
29
|
+
*/
|
|
30
|
+
export const AuditCategory = {
|
|
31
|
+
TOOL_CALL: 'tool_call',
|
|
32
|
+
LLM_REQUEST: 'llm_request',
|
|
33
|
+
FILE_ACCESS: 'file_access',
|
|
34
|
+
SHELL_EXEC: 'shell_exec',
|
|
35
|
+
AGENT_ACTION: 'agent_action',
|
|
36
|
+
AUTH: 'auth',
|
|
37
|
+
CONFIG_CHANGE: 'config_change',
|
|
38
|
+
SECRET_DETECTED: 'secret_detected',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Dangerous patterns in shell commands
|
|
43
|
+
*/
|
|
44
|
+
const DANGEROUS_PATTERNS = [
|
|
45
|
+
/\brm\s+-rf?\s+\//, // rm -rf /
|
|
46
|
+
/\bsudo\b/, // sudo commands
|
|
47
|
+
/\bcurl\b.*\|\s*bash/, // curl | bash
|
|
48
|
+
/\bwget\b.*\|\s*sh/, // wget | sh
|
|
49
|
+
/\bchmod\s+777\b/, // chmod 777
|
|
50
|
+
/\bdd\s+if=/, // dd disk operations
|
|
51
|
+
/\bmkfs\b/, // filesystem creation
|
|
52
|
+
/\b(shutdown|reboot|halt)\b/, // system shutdown
|
|
53
|
+
/\bkill\s+-9\s+1\b/, // kill init
|
|
54
|
+
/>\s*\/dev\/sd[a-z]/, // write to disk device
|
|
55
|
+
/\beval\b.*\$\(/, // eval with command substitution
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Secret patterns for scanning
|
|
60
|
+
*/
|
|
61
|
+
const SECRET_PATTERNS = [
|
|
62
|
+
{ name: 'AWS Key', pattern: /AKIA[0-9A-Z]{16}/ },
|
|
63
|
+
{ name: 'Generic API Key', pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"][a-zA-Z0-9]{20,}['"]/i },
|
|
64
|
+
{ name: 'JWT Token', pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/ },
|
|
65
|
+
{ name: 'Private Key', pattern: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/ },
|
|
66
|
+
{ name: 'GitHub Token', pattern: /gh[ps]_[a-zA-Z0-9]{36}/ },
|
|
67
|
+
{ name: 'OpenAI Key', pattern: /sk-[a-zA-Z0-9]{20,}/ },
|
|
68
|
+
{ name: 'Password in URL', pattern: /:\/\/[^:]+:[^@]+@/ },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Audit Logger - Records all security-relevant events
|
|
73
|
+
*/
|
|
74
|
+
export class AuditLogger {
|
|
75
|
+
/**
|
|
76
|
+
* @param {object} options
|
|
77
|
+
* @param {string} options.logDir - Directory for audit log files
|
|
78
|
+
* @param {number} options.maxEvents - Max events to keep in memory
|
|
79
|
+
* @param {boolean} options.persistToDisk - Whether to write logs to disk
|
|
80
|
+
* @param {Function} options.onCritical - Callback for critical events
|
|
81
|
+
*/
|
|
82
|
+
constructor(options = {}) {
|
|
83
|
+
this.logDir = options.logDir || AUDIT_DIR;
|
|
84
|
+
this.maxEvents = options.maxEvents ?? 1000;
|
|
85
|
+
this.persistToDisk = options.persistToDisk ?? true;
|
|
86
|
+
this.onCritical = options.onCritical || null;
|
|
87
|
+
|
|
88
|
+
// In-memory event buffer
|
|
89
|
+
this.events = [];
|
|
90
|
+
|
|
91
|
+
// Ensure log directory exists
|
|
92
|
+
if (this.persistToDisk && !fs.existsSync(this.logDir)) {
|
|
93
|
+
fs.mkdirSync(this.logDir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Log an audit event
|
|
99
|
+
* @param {object} event
|
|
100
|
+
* @param {string} event.category - Event category (AuditCategory)
|
|
101
|
+
* @param {string} event.level - Severity level (AuditLevel)
|
|
102
|
+
* @param {string} event.agentId - Agent that triggered the event
|
|
103
|
+
* @param {string} event.agentName - Agent name
|
|
104
|
+
* @param {string} event.action - Description of the action
|
|
105
|
+
* @param {object} event.details - Additional event details
|
|
106
|
+
* @param {boolean} event.blocked - Whether the action was blocked
|
|
107
|
+
* @returns {object} The logged event
|
|
108
|
+
*/
|
|
109
|
+
log(event) {
|
|
110
|
+
const auditEvent = {
|
|
111
|
+
id: uuidv4(),
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
category: event.category || AuditCategory.AGENT_ACTION,
|
|
114
|
+
level: event.level || AuditLevel.INFO,
|
|
115
|
+
agentId: event.agentId || 'system',
|
|
116
|
+
agentName: event.agentName || 'System',
|
|
117
|
+
action: event.action || '',
|
|
118
|
+
details: event.details || {},
|
|
119
|
+
blocked: event.blocked || false,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Add to in-memory buffer
|
|
123
|
+
this.events.push(auditEvent);
|
|
124
|
+
if (this.events.length > this.maxEvents) {
|
|
125
|
+
this.events.shift(); // Remove oldest
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Persist to disk
|
|
129
|
+
if (this.persistToDisk) {
|
|
130
|
+
this._persistEvent(auditEvent);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Critical event callback
|
|
134
|
+
if (auditEvent.level === AuditLevel.CRITICAL && this.onCritical) {
|
|
135
|
+
try { this.onCritical(auditEvent); } catch {}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Console output for critical/warn
|
|
139
|
+
if (auditEvent.level === AuditLevel.CRITICAL) {
|
|
140
|
+
console.error(`🚨 [AUDIT CRITICAL] ${auditEvent.action}`, auditEvent.details);
|
|
141
|
+
} else if (auditEvent.level === AuditLevel.WARN) {
|
|
142
|
+
console.warn(`⚠️ [AUDIT WARN] ${auditEvent.action}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return auditEvent;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Persist event to daily log file
|
|
150
|
+
*/
|
|
151
|
+
_persistEvent(event) {
|
|
152
|
+
try {
|
|
153
|
+
const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
154
|
+
const logFile = path.join(this.logDir, `audit-${date}.jsonl`);
|
|
155
|
+
fs.appendFileSync(logFile, JSON.stringify(event) + '\n', 'utf-8');
|
|
156
|
+
} catch (err) {
|
|
157
|
+
console.error('[AuditLogger] Failed to persist event:', err.message);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Query events with filters
|
|
163
|
+
* @param {object} filters
|
|
164
|
+
* @param {string} filters.category
|
|
165
|
+
* @param {string} filters.level
|
|
166
|
+
* @param {string} filters.agentId
|
|
167
|
+
* @param {boolean} filters.blocked
|
|
168
|
+
* @param {number} filters.limit
|
|
169
|
+
* @returns {Array} Matching events
|
|
170
|
+
*/
|
|
171
|
+
query(filters = {}) {
|
|
172
|
+
let results = [...this.events];
|
|
173
|
+
|
|
174
|
+
if (filters.category) results = results.filter(e => e.category === filters.category);
|
|
175
|
+
if (filters.level) results = results.filter(e => e.level === filters.level);
|
|
176
|
+
if (filters.agentId) results = results.filter(e => e.agentId === filters.agentId);
|
|
177
|
+
if (filters.blocked !== undefined) results = results.filter(e => e.blocked === filters.blocked);
|
|
178
|
+
|
|
179
|
+
// Most recent first
|
|
180
|
+
results.reverse();
|
|
181
|
+
|
|
182
|
+
if (filters.limit) results = results.slice(0, filters.limit);
|
|
183
|
+
|
|
184
|
+
return results;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get audit summary statistics
|
|
189
|
+
* @returns {object}
|
|
190
|
+
*/
|
|
191
|
+
getSummary() {
|
|
192
|
+
const total = this.events.length;
|
|
193
|
+
const byLevel = { info: 0, warn: 0, critical: 0 };
|
|
194
|
+
const byCategory = {};
|
|
195
|
+
let blocked = 0;
|
|
196
|
+
|
|
197
|
+
for (const event of this.events) {
|
|
198
|
+
byLevel[event.level] = (byLevel[event.level] || 0) + 1;
|
|
199
|
+
byCategory[event.category] = (byCategory[event.category] || 0) + 1;
|
|
200
|
+
if (event.blocked) blocked++;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { total, byLevel, byCategory, blocked };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Clear in-memory events
|
|
208
|
+
*/
|
|
209
|
+
clear() {
|
|
210
|
+
this.events = [];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Security Guard - Validates actions before execution
|
|
216
|
+
*/
|
|
217
|
+
export class SecurityGuard {
|
|
218
|
+
/**
|
|
219
|
+
* @param {AuditLogger} auditLogger
|
|
220
|
+
* @param {object} options
|
|
221
|
+
* @param {boolean} options.blockDangerous - Whether to block dangerous commands
|
|
222
|
+
* @param {boolean} options.scanSecrets - Whether to scan for secrets
|
|
223
|
+
* @param {Array} options.allowedCommands - Whitelist of allowed shell commands
|
|
224
|
+
*/
|
|
225
|
+
constructor(auditLogger, options = {}) {
|
|
226
|
+
this.audit = auditLogger;
|
|
227
|
+
this.blockDangerous = options.blockDangerous ?? true;
|
|
228
|
+
this.scanSecrets = options.scanSecrets ?? true;
|
|
229
|
+
this.allowedCommands = options.allowedCommands || [
|
|
230
|
+
'ls', 'cat', 'head', 'tail', 'grep', 'find', 'wc',
|
|
231
|
+
'node', 'npm', 'npx', 'echo', 'mkdir', 'cp', 'mv',
|
|
232
|
+
'tree', 'pwd', 'which', 'git',
|
|
233
|
+
'curl', 'wget', 'date', 'python', 'python3', 'env', 'sort', 'uniq', 'awk', 'sed', 'jq',
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
// Per-agent permission policies
|
|
237
|
+
// { agentId: { canExecShell, canWriteFiles, canAccessNetwork, ... } }
|
|
238
|
+
this.policies = new Map();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Set permission policy for an agent
|
|
243
|
+
* @param {string} agentId
|
|
244
|
+
* @param {object} policy
|
|
245
|
+
*/
|
|
246
|
+
setPolicy(agentId, policy) {
|
|
247
|
+
this.policies.set(agentId, {
|
|
248
|
+
canExecShell: true,
|
|
249
|
+
canWriteFiles: true,
|
|
250
|
+
canDeleteFiles: false,
|
|
251
|
+
canAccessNetwork: false,
|
|
252
|
+
canSendMessages: true,
|
|
253
|
+
maxFileSize: 1024 * 1024, // 1MB
|
|
254
|
+
...policy,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get policy for an agent (default permissive)
|
|
260
|
+
* @param {string} agentId
|
|
261
|
+
* @returns {object}
|
|
262
|
+
*/
|
|
263
|
+
getPolicy(agentId) {
|
|
264
|
+
return this.policies.get(agentId) || {
|
|
265
|
+
canExecShell: true,
|
|
266
|
+
canWriteFiles: true,
|
|
267
|
+
canDeleteFiles: false,
|
|
268
|
+
canAccessNetwork: false,
|
|
269
|
+
canSendMessages: true,
|
|
270
|
+
maxFileSize: 1024 * 1024,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Validate a shell command before execution
|
|
276
|
+
* @param {string} command
|
|
277
|
+
* @param {string} agentId
|
|
278
|
+
* @param {string} agentName
|
|
279
|
+
* @returns {{ allowed: boolean, reason?: string }}
|
|
280
|
+
*/
|
|
281
|
+
validateShellCommand(command, agentId, agentName = '') {
|
|
282
|
+
const policy = this.getPolicy(agentId);
|
|
283
|
+
|
|
284
|
+
// Check policy permission
|
|
285
|
+
if (!policy.canExecShell) {
|
|
286
|
+
this.audit.log({
|
|
287
|
+
category: AuditCategory.SHELL_EXEC,
|
|
288
|
+
level: AuditLevel.WARN,
|
|
289
|
+
agentId,
|
|
290
|
+
agentName,
|
|
291
|
+
action: `Shell execution denied by policy: ${command}`,
|
|
292
|
+
details: { command },
|
|
293
|
+
blocked: true,
|
|
294
|
+
});
|
|
295
|
+
return { allowed: false, reason: 'Agent does not have shell execution permission' };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Check command whitelist
|
|
299
|
+
const cmdName = command.trim().split(/\s+/)[0];
|
|
300
|
+
if (!this.allowedCommands.includes(cmdName)) {
|
|
301
|
+
this.audit.log({
|
|
302
|
+
category: AuditCategory.SHELL_EXEC,
|
|
303
|
+
level: AuditLevel.WARN,
|
|
304
|
+
agentId,
|
|
305
|
+
agentName,
|
|
306
|
+
action: `Command not in whitelist: ${cmdName}`,
|
|
307
|
+
details: { command, cmdName },
|
|
308
|
+
blocked: true,
|
|
309
|
+
});
|
|
310
|
+
return { allowed: false, reason: `Command "${cmdName}" is not in the allowed list` };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Check dangerous patterns
|
|
314
|
+
if (this.blockDangerous) {
|
|
315
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
316
|
+
if (pattern.test(command)) {
|
|
317
|
+
this.audit.log({
|
|
318
|
+
category: AuditCategory.SHELL_EXEC,
|
|
319
|
+
level: AuditLevel.CRITICAL,
|
|
320
|
+
agentId,
|
|
321
|
+
agentName,
|
|
322
|
+
action: `Dangerous command blocked: ${command}`,
|
|
323
|
+
details: { command, matchedPattern: pattern.toString() },
|
|
324
|
+
blocked: true,
|
|
325
|
+
});
|
|
326
|
+
return { allowed: false, reason: `Dangerous command pattern detected` };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Log approved command
|
|
332
|
+
this.audit.log({
|
|
333
|
+
category: AuditCategory.SHELL_EXEC,
|
|
334
|
+
level: AuditLevel.INFO,
|
|
335
|
+
agentId,
|
|
336
|
+
agentName,
|
|
337
|
+
action: `Shell command approved: ${command}`,
|
|
338
|
+
details: { command },
|
|
339
|
+
blocked: false,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return { allowed: true };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Validate file write operation
|
|
347
|
+
* @param {string} filePath
|
|
348
|
+
* @param {string} content
|
|
349
|
+
* @param {string} agentId
|
|
350
|
+
* @param {string} agentName
|
|
351
|
+
* @returns {{ allowed: boolean, reason?: string }}
|
|
352
|
+
*/
|
|
353
|
+
validateFileWrite(filePath, content, agentId, agentName = '') {
|
|
354
|
+
const policy = this.getPolicy(agentId);
|
|
355
|
+
|
|
356
|
+
if (!policy.canWriteFiles) {
|
|
357
|
+
this.audit.log({
|
|
358
|
+
category: AuditCategory.FILE_ACCESS,
|
|
359
|
+
level: AuditLevel.WARN,
|
|
360
|
+
agentId,
|
|
361
|
+
agentName,
|
|
362
|
+
action: `File write denied by policy: ${filePath}`,
|
|
363
|
+
details: { filePath },
|
|
364
|
+
blocked: true,
|
|
365
|
+
});
|
|
366
|
+
return { allowed: false, reason: 'Agent does not have file write permission' };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Check file size
|
|
370
|
+
const size = Buffer.byteLength(content, 'utf-8');
|
|
371
|
+
if (size > policy.maxFileSize) {
|
|
372
|
+
this.audit.log({
|
|
373
|
+
category: AuditCategory.FILE_ACCESS,
|
|
374
|
+
level: AuditLevel.WARN,
|
|
375
|
+
agentId,
|
|
376
|
+
agentName,
|
|
377
|
+
action: `File too large: ${filePath} (${size} bytes)`,
|
|
378
|
+
details: { filePath, size, maxSize: policy.maxFileSize },
|
|
379
|
+
blocked: true,
|
|
380
|
+
});
|
|
381
|
+
return { allowed: false, reason: `File size (${size} bytes) exceeds limit (${policy.maxFileSize} bytes)` };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
this.audit.log({
|
|
385
|
+
category: AuditCategory.FILE_ACCESS,
|
|
386
|
+
level: AuditLevel.INFO,
|
|
387
|
+
agentId,
|
|
388
|
+
agentName,
|
|
389
|
+
action: `File write: ${filePath} (${size} bytes)`,
|
|
390
|
+
details: { filePath, size },
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return { allowed: true };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Scan text for secrets/credentials
|
|
398
|
+
* @param {string} text - Text to scan
|
|
399
|
+
* @param {string} context - Where the text came from
|
|
400
|
+
* @param {string} agentId
|
|
401
|
+
* @returns {{ clean: boolean, findings: Array }}
|
|
402
|
+
*/
|
|
403
|
+
scanForSecrets(text, context = '', agentId = 'system') {
|
|
404
|
+
if (!this.scanSecrets || !text) return { clean: true, findings: [] };
|
|
405
|
+
|
|
406
|
+
const findings = [];
|
|
407
|
+
|
|
408
|
+
for (const { name, pattern } of SECRET_PATTERNS) {
|
|
409
|
+
const match = pattern.exec(text);
|
|
410
|
+
if (match) {
|
|
411
|
+
findings.push({
|
|
412
|
+
type: name,
|
|
413
|
+
position: match.index,
|
|
414
|
+
preview: match[0].slice(0, 8) + '***',
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (findings.length > 0) {
|
|
420
|
+
this.audit.log({
|
|
421
|
+
category: AuditCategory.SECRET_DETECTED,
|
|
422
|
+
level: AuditLevel.CRITICAL,
|
|
423
|
+
agentId,
|
|
424
|
+
action: `Secrets detected in ${context}: ${findings.map(f => f.type).join(', ')}`,
|
|
425
|
+
details: { context, findings },
|
|
426
|
+
blocked: false, // Log but don't block - leave decision to caller
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return { clean: findings.length === 0, findings };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Log a tool call
|
|
435
|
+
* @param {string} toolName
|
|
436
|
+
* @param {object} args
|
|
437
|
+
* @param {string} agentId
|
|
438
|
+
* @param {string} agentName
|
|
439
|
+
*/
|
|
440
|
+
logToolCall(toolName, args, agentId, agentName = '') {
|
|
441
|
+
this.audit.log({
|
|
442
|
+
category: AuditCategory.TOOL_CALL,
|
|
443
|
+
level: AuditLevel.INFO,
|
|
444
|
+
agentId,
|
|
445
|
+
agentName,
|
|
446
|
+
action: `Tool call: ${toolName}`,
|
|
447
|
+
details: { toolName, args: this._sanitizeArgs(args) },
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Log an LLM API request
|
|
453
|
+
*/
|
|
454
|
+
logLLMRequest(providerId, model, tokenCount, agentId, agentName = '') {
|
|
455
|
+
this.audit.log({
|
|
456
|
+
category: AuditCategory.LLM_REQUEST,
|
|
457
|
+
level: AuditLevel.INFO,
|
|
458
|
+
agentId,
|
|
459
|
+
agentName,
|
|
460
|
+
action: `LLM request: ${providerId} (${model})`,
|
|
461
|
+
details: { providerId, model, tokenCount },
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Sanitize arguments for logging (remove sensitive data)
|
|
467
|
+
*/
|
|
468
|
+
_sanitizeArgs(args) {
|
|
469
|
+
if (!args || typeof args !== 'object') return args;
|
|
470
|
+
const sanitized = { ...args };
|
|
471
|
+
const sensitiveKeys = ['apiKey', 'password', 'token', 'secret', 'credential'];
|
|
472
|
+
for (const key of Object.keys(sanitized)) {
|
|
473
|
+
if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
|
|
474
|
+
sanitized[key] = '***REDACTED***';
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return sanitized;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Global singletons
|
|
482
|
+
export const auditLogger = new AuditLogger();
|
|
483
|
+
export const securityGuard = new SecurityGuard(auditLogger);
|