@jishankai/solid-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,482 @@
1
+ import { ResourceAgent } from './agents/ResourceAgent.js';
2
+ import { SystemAgent } from './agents/SystemAgent.js';
3
+ import { PersistenceAgent } from './agents/PersistenceAgent.js';
4
+ import { ProcessAgent } from './agents/ProcessAgent.js';
5
+ import { NetworkAgent } from './agents/NetworkAgent.js';
6
+ import { PermissionAgent } from './agents/PermissionAgent.js';
7
+ import { BlockchainAgent } from './agents/BlockchainAgent.js';
8
+ import { DeFiSecurityAgent } from './agents/DeFiSecurityAgent.js';
9
+ import { executeShellCommand } from './utils/commander.js';
10
+ import { log } from './logging/Logger.js';
11
+ import ora from 'ora';
12
+ import chalk from 'chalk';
13
+
14
+ /**
15
+ * Orchestrator - Coordinates all agents and aggregates results
16
+ */
17
+ export class Orchestrator {
18
+ constructor(options = {}) {
19
+ this.enableGeoLookup = Boolean(options.enableGeoLookup);
20
+ this.geoLookupLimit = options.geoLookupLimit || 10;
21
+ this.analysisDepth = options.analysisDepth || 'comprehensive'; // 'fast', 'comprehensive', 'deep'
22
+ this.parallelExecution = options.parallelExecution !== false;
23
+ this.maxParallelAgents = options.maxParallelAgents || 3;
24
+ this.agents = {};
25
+ this.results = null;
26
+ }
27
+
28
+ /**
29
+ * Initialize base agents for Phase 1 analysis
30
+ */
31
+ initializeBaseAgents() {
32
+ return {
33
+ resource: new ResourceAgent(),
34
+ system: new SystemAgent(),
35
+ persistence: new PersistenceAgent(),
36
+ process: new ProcessAgent(),
37
+ network: new NetworkAgent({
38
+ enableGeoLookup: this.enableGeoLookup,
39
+ geoLookupLimit: this.geoLookupLimit
40
+ }),
41
+ permission: new PermissionAgent()
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Initialize conditional agents for extended analysis
47
+ */
48
+ initializeConditionalAgents() {
49
+ return {
50
+ blockchain: new BlockchainAgent(),
51
+ defi: new DeFiSecurityAgent()
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Check if blockchain indicators are present in initial results
57
+ */
58
+ hasBlockchainIndicators(initialResults) {
59
+ return this.extractBlockchainIndicators(initialResults).length > 0;
60
+ }
61
+
62
+ /**
63
+ * Extract specific blockchain indicators
64
+ */
65
+ extractBlockchainIndicators(initialResults) {
66
+ const indicators = [];
67
+
68
+ // Check process findings for blockchain indicators
69
+ const processResult = initialResults.process;
70
+ if (processResult && processResult.findings) {
71
+ const blockchainKeywords = [
72
+ 'bitcoin', 'ethereum', 'crypto', 'blockchain', 'wallet', 'mining',
73
+ 'metamask', 'phantom', 'coinbase', 'binance', 'uniswap', 'defi'
74
+ ];
75
+
76
+ for (const finding of processResult.findings) {
77
+ const searchText = `${finding.command || ''} ${finding.program || ''} ${finding.description || ''}`.toLowerCase();
78
+ const matchedKeywords = blockchainKeywords.filter(keyword => searchText.includes(keyword));
79
+
80
+ if (matchedKeywords.length > 0) {
81
+ indicators.push({
82
+ type: 'process',
83
+ source: finding,
84
+ keywords: matchedKeywords
85
+ });
86
+ }
87
+ }
88
+ }
89
+
90
+ // Check network connections for blockchain domains
91
+ const networkResult = initialResults.network;
92
+ if (networkResult && networkResult.findings) {
93
+ const blockchainDomains = [
94
+ 'etherscan', 'uniswap', 'opensea', 'pancakeswap', 'curve',
95
+ 'compound', 'aave', 'sushiswap', '1inch', 'metamask'
96
+ ];
97
+
98
+ for (const finding of networkResult.findings) {
99
+ if (finding.remoteAddress) {
100
+ const domain = finding.remoteAddress.toLowerCase();
101
+ const matchedDomains = blockchainDomains.filter(d => domain.includes(d));
102
+
103
+ if (matchedDomains.length > 0) {
104
+ indicators.push({
105
+ type: 'network',
106
+ source: finding,
107
+ domains: matchedDomains
108
+ });
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ return indicators;
115
+ }
116
+
117
+ /**
118
+ * Check if extended forensics analysis should be triggered
119
+ */
120
+ needsExtendedForensics(initialResults) {
121
+ const summary = this.generateSummary(initialResults);
122
+
123
+ // Trigger extended analysis if high risk findings or many medium risk findings
124
+ if ((summary?.highRiskFindings || 0) > 0) return true;
125
+ if ((summary?.mediumRiskFindings || 0) >= 5) return true;
126
+
127
+ return false;
128
+ }
129
+
130
+ /**
131
+ * Run unified adaptive analysis
132
+ */
133
+ async runAnalysis() {
134
+ console.log(chalk.cyan('\n🚀 Starting Unified Security Analysis...\n'));
135
+
136
+ const results = {};
137
+ const analysisPhases = [];
138
+
139
+ // Phase 1: Base agents always run
140
+ console.log(chalk.blue('Phase 1: Core Security Analysis'));
141
+ const baseAgents = this.initializeBaseAgents();
142
+ let totalAgents = Object.keys(baseAgents).length;
143
+ let completedAgents = 0;
144
+
145
+ if (this.parallelExecution) {
146
+ // Run agents in parallel with limit
147
+ const agentEntries = Object.entries(baseAgents);
148
+ const chunks = [];
149
+
150
+ for (let i = 0; i < agentEntries.length; i += this.maxParallelAgents) {
151
+ chunks.push(agentEntries.slice(i, i + this.maxParallelAgents));
152
+ }
153
+
154
+ for (const chunk of chunks) {
155
+ const chunkPromises = chunk.map(async ([key, agent]) => {
156
+ const spinner = ora(`Running ${agent.name}...`).start();
157
+ const startTime = Date.now();
158
+
159
+ log.agentStart(agent.name, { parallel: true });
160
+
161
+ try {
162
+ const result = await agent.analyze();
163
+ const duration = Date.now() - startTime;
164
+
165
+ results[key] = result;
166
+ log.agentComplete(agent.name, result, duration);
167
+
168
+ const normalizedRisk = (result.overallRisk || 'unknown').toLowerCase();
169
+ const riskColor = this.getRiskColor(normalizedRisk);
170
+ completedAgents += 1;
171
+ spinner.succeed(`${agent.name} completed - ${completedAgents}/${totalAgents} - Risk: ${riskColor(normalizedRisk.toUpperCase())}`);
172
+ return result;
173
+ } catch (error) {
174
+ const duration = Date.now() - startTime;
175
+
176
+ results[key] = {
177
+ agent: agent.name,
178
+ error: error.message,
179
+ overallRisk: 'unknown'
180
+ };
181
+
182
+ log.agentError(agent.name, error, duration);
183
+ completedAgents += 1;
184
+ spinner.fail(`${agent.name} failed - ${completedAgents}/${totalAgents}: ${error.message}`);
185
+ return results[key];
186
+ }
187
+ });
188
+
189
+ await Promise.all(chunkPromises);
190
+ }
191
+ } else {
192
+ // Run sequentially
193
+ for (const [key, agent] of Object.entries(baseAgents)) {
194
+ const spinner = ora(`Running ${agent.name}...`).start();
195
+ const startTime = Date.now();
196
+
197
+ log.agentStart(agent.name, { parallel: false });
198
+
199
+ try {
200
+ const result = await agent.analyze();
201
+ const duration = Date.now() - startTime;
202
+
203
+ results[key] = result;
204
+ log.agentComplete(agent.name, result, duration);
205
+
206
+ const normalizedRisk = (result.overallRisk || 'unknown').toLowerCase();
207
+ const riskColor = this.getRiskColor(normalizedRisk);
208
+ completedAgents += 1;
209
+ spinner.succeed(`${agent.name} completed - ${completedAgents}/${totalAgents} - Risk: ${riskColor(normalizedRisk.toUpperCase())}`);
210
+ } catch (error) {
211
+ const duration = Date.now() - startTime;
212
+
213
+ results[key] = {
214
+ agent: agent.name,
215
+ error: error.message,
216
+ overallRisk: 'unknown'
217
+ };
218
+
219
+ log.agentError(agent.name, error, duration);
220
+ completedAgents += 1;
221
+ spinner.fail(`${agent.name} failed - ${completedAgents}/${totalAgents}: ${error.message}`);
222
+ }
223
+ }
224
+ }
225
+
226
+ analysisPhases.push({
227
+ phase: 'Core Security Analysis',
228
+ agents: Object.keys(baseAgents),
229
+ completed: true
230
+ });
231
+
232
+ // Phase 2: Adaptive extension based on findings
233
+ console.log(chalk.blue('\nPhase 2: Adaptive Analysis'));
234
+
235
+ const extendedPhases = [];
236
+
237
+ // Check for blockchain indicators
238
+ if (this.hasBlockchainIndicators(results)) {
239
+ console.log(chalk.yellow('🔗 Blockchain indicators detected - activating blockchain analysis'));
240
+ extendedPhases.push('Blockchain Security Analysis');
241
+
242
+ const indicators = this.extractBlockchainIndicators(results);
243
+ log.blockchainDetection(indicators);
244
+
245
+ const conditionalAgents = this.initializeConditionalAgents();
246
+ totalAgents += Object.keys(conditionalAgents).length;
247
+
248
+ for (const [key, agent] of Object.entries(conditionalAgents)) {
249
+ const spinner = ora(`Running ${agent.name}...`).start();
250
+ const startTime = Date.now();
251
+
252
+ log.agentStart(agent.name, { triggered: 'blockchain_detection' });
253
+
254
+ try {
255
+ const result = await agent.analyze();
256
+ const duration = Date.now() - startTime;
257
+
258
+ results[key] = result;
259
+ log.agentComplete(agent.name, result, duration);
260
+
261
+ const normalizedRisk = (result.overallRisk || 'unknown').toLowerCase();
262
+ const riskColor = this.getRiskColor(normalizedRisk);
263
+ completedAgents += 1;
264
+ spinner.succeed(`${agent.name} completed - ${completedAgents}/${totalAgents} - Risk: ${riskColor(normalizedRisk.toUpperCase())}`);
265
+ } catch (error) {
266
+ const duration = Date.now() - startTime;
267
+
268
+ results[key] = {
269
+ agent: agent.name,
270
+ error: error.message,
271
+ overallRisk: 'unknown'
272
+ };
273
+
274
+ log.agentError(agent.name, error, duration);
275
+ completedAgents += 1;
276
+ spinner.fail(`${agent.name} failed - ${completedAgents}/${totalAgents}: ${error.message}`);
277
+ }
278
+ }
279
+ } else {
280
+ console.log(chalk.gray(' No blockchain indicators detected - skipping blockchain analysis'));
281
+ }
282
+
283
+ // Check for extended forensics need
284
+ if (this.needsExtendedForensics(results)) {
285
+ console.log(chalk.yellow('🔍 Risk indicators detected - enabling extended analysis'));
286
+ extendedPhases.push('Extended Forensics Analysis');
287
+
288
+ // Future: Add extended forensics logic here
289
+ console.log(chalk.gray(' Extended forensics capabilities will be added in future version'));
290
+ }
291
+
292
+ analysisPhases.push({
293
+ phase: 'Adaptive Analysis',
294
+ extensions: extendedPhases,
295
+ completed: true
296
+ });
297
+
298
+ // Phase 3: Integration and correlation
299
+ console.log(chalk.blue('\nPhase 3: Result Integration'));
300
+ const correlatedResults = this.correlateFindings(results);
301
+ this.updateProgress = () => {};
302
+ console.log(chalk.green('✅ Analysis completed successfully!'));
303
+
304
+ this.results = {
305
+ mode: 'unified',
306
+ analysisDepth: this.analysisDepth,
307
+ timestamp: new Date().toISOString(),
308
+ hostname: await this.getHostname(),
309
+ osVersion: await this.getOSVersion(),
310
+ agents: correlatedResults,
311
+ overallRisk: this.calculateOverallRisk(correlatedResults),
312
+ summary: this.generateSummary(correlatedResults),
313
+ analysisPhases,
314
+ adaptiveAnalysis: {
315
+ blockchainAnalysisEnabled: this.hasBlockchainIndicators(results),
316
+ extendedForensicsEnabled: this.needsExtendedForensics(results),
317
+ totalAgentsRan: Object.keys(correlatedResults).length
318
+ }
319
+ };
320
+
321
+ return this.results;
322
+ }
323
+
324
+ /**
325
+ * Correlate findings across agents to identify patterns
326
+ */
327
+ correlateFindings(results) {
328
+ const correlated = { ...results };
329
+
330
+ // Future: Add sophisticated correlation logic
331
+ // Example: Correlate process findings with network connections
332
+ // Example: Correlate persistence mechanisms with startup items
333
+
334
+ return correlated;
335
+ }
336
+
337
+ /**
338
+ * Get system hostname
339
+ */
340
+ async getHostname() {
341
+ return (await executeShellCommand('hostname')).trim();
342
+ }
343
+
344
+ /**
345
+ * Get macOS version
346
+ */
347
+ async getOSVersion() {
348
+ return (await executeShellCommand('sw_vers -productVersion')).trim();
349
+ }
350
+
351
+ /**
352
+ * Calculate overall risk across all agents
353
+ */
354
+ calculateOverallRisk(results) {
355
+ if (!results || typeof results !== 'object') {
356
+ return 'unknown';
357
+ }
358
+
359
+ const risks = Object.values(results)
360
+ .filter(result => result && result.overallRisk)
361
+ .map(r => r.overallRisk);
362
+
363
+ if (risks.length === 0) {
364
+ return 'unknown';
365
+ }
366
+
367
+ if (risks.includes('high')) return 'high';
368
+ if (risks.includes('medium')) return 'medium';
369
+ return 'low';
370
+ }
371
+
372
+ /**
373
+ * Generate summary of findings
374
+ */
375
+ generateSummary(results) {
376
+ // Ensure results is a valid object
377
+ if (!results || typeof results !== 'object') {
378
+ return {
379
+ totalFindings: 0,
380
+ highRiskFindings: 0,
381
+ mediumRiskFindings: 0,
382
+ lowRiskFindings: 0,
383
+ agentSummaries: {},
384
+ error: 'Invalid results data'
385
+ };
386
+ }
387
+
388
+ const summary = {
389
+ totalFindings: 0,
390
+ highRiskFindings: 0,
391
+ mediumRiskFindings: 0,
392
+ lowRiskFindings: 0,
393
+ agentSummaries: {}
394
+ };
395
+
396
+ try {
397
+ for (const [key, result] of Object.entries(results)) {
398
+ // Ensure result exists and is valid
399
+ if (!result) {
400
+ summary.agentSummaries[key] = { error: 'Result is null or undefined' };
401
+ continue;
402
+ }
403
+
404
+ if (result.error) {
405
+ summary.agentSummaries[key] = { error: result.error };
406
+ continue;
407
+ }
408
+
409
+ // Ensure findings is an array
410
+ const findings = Array.isArray(result.findings) ? result.findings : [];
411
+ const findingCount = findings.length;
412
+
413
+ summary.totalFindings += findingCount;
414
+
415
+ // Count by risk level
416
+ for (const finding of findings) {
417
+ if (finding && finding.risk === 'high') {
418
+ summary.highRiskFindings++;
419
+ } else if (finding && finding.risk === 'medium') {
420
+ summary.mediumRiskFindings++;
421
+ } else if (finding) {
422
+ summary.lowRiskFindings++;
423
+ }
424
+ }
425
+
426
+ const derivedRisk = this.deriveRiskFromFindings(findings, result.overallRisk);
427
+
428
+ summary.agentSummaries[key] = {
429
+ findings: findingCount,
430
+ risk: derivedRisk
431
+ };
432
+ }
433
+ } catch (error) {
434
+ console.error(`Error generating summary: ${error.message}`);
435
+ summary.error = error.message;
436
+ }
437
+
438
+ return summary;
439
+ }
440
+
441
+ /**
442
+ * Derive risk from findings to keep agent badges consistent
443
+ */
444
+ deriveRiskFromFindings(findings, reportedRisk = 'unknown') {
445
+ if (!Array.isArray(findings) || findings.length === 0) return 'low';
446
+ if (findings.some(f => f && f.risk === 'high')) return 'high';
447
+ if (findings.some(f => f && f.risk === 'medium')) return 'medium';
448
+ return 'low';
449
+ }
450
+
451
+ /**
452
+ * Get color function for risk level
453
+ */
454
+ getRiskColor(risk) {
455
+ const normalizedRisk = (risk || '').toLowerCase();
456
+ switch (normalizedRisk) {
457
+ case 'high':
458
+ return chalk.red.bold;
459
+ case 'medium':
460
+ return chalk.yellow.bold;
461
+ case 'low':
462
+ return chalk.green;
463
+ default:
464
+ return chalk.gray;
465
+ }
466
+ }
467
+
468
+ updateProgress(spinner, completed, total) {
469
+ const text = `Progress: ${completed}/${total}`;
470
+ if (spinner && spinner.isSpinning) {
471
+ spinner.text = text;
472
+ }
473
+ console.log(chalk.gray(text));
474
+ }
475
+
476
+ /**
477
+ * Get aggregated results
478
+ */
479
+ getResults() {
480
+ return this.results;
481
+ }
482
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Base Agent class that all specific agents extend
3
+ */
4
+ export class BaseAgent {
5
+ constructor(name) {
6
+ this.name = name;
7
+ this.results = null;
8
+ }
9
+
10
+ /**
11
+ * Execute the agent's analysis
12
+ * Must be implemented by subclasses
13
+ */
14
+ async analyze() {
15
+ throw new Error(`${this.name}: analyze() must be implemented`);
16
+ }
17
+
18
+ /**
19
+ * Get the analysis results
20
+ */
21
+ getResults() {
22
+ return this.results;
23
+ }
24
+
25
+ /**
26
+ * Determine risk level based on findings
27
+ * @param {Array} findings - Array of findings with risk levels
28
+ * @returns {string} - Overall risk level
29
+ */
30
+ calculateOverallRisk(findings) {
31
+ if (findings.some(f => f.risk === 'high')) return 'high';
32
+ if (findings.some(f => f.risk === 'medium')) return 'medium';
33
+ return 'low';
34
+ }
35
+ }