@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,217 @@
1
+ import { BaseAgent } from './BaseAgent.js';
2
+ import { executeShellCommand } from '../utils/commander.js';
3
+
4
+ /**
5
+ * ResourceAgent - Analyzes CPU, Memory, and IO usage
6
+ */
7
+ export class ResourceAgent extends BaseAgent {
8
+ constructor() {
9
+ super('ResourceAgent');
10
+ }
11
+
12
+ async analyze() {
13
+ const processes = await this.getProcessInfo();
14
+ const memory = await this.getMemoryInfo();
15
+ const findings = this.analyzeFindings(processes);
16
+
17
+ this.results = {
18
+ agent: this.name,
19
+ timestamp: new Date().toISOString(),
20
+ topCpuProcesses: processes.cpu.slice(0, 10),
21
+ topMemoryProcesses: processes.memory.slice(0, 10),
22
+ memoryStats: memory,
23
+ suspiciousProcesses: findings,
24
+ overallRisk: this.calculateOverallRisk(findings)
25
+ };
26
+
27
+ return this.results;
28
+ }
29
+
30
+ /**
31
+ * Get process information using ps and top
32
+ */
33
+ async getProcessInfo() {
34
+ const psOutput = await executeShellCommand('ps -axo pid,ppid,comm,%cpu,rss,etime');
35
+ const lines = psOutput.split('\n').slice(1); // Skip header
36
+
37
+ const processes = [];
38
+ for (const line of lines) {
39
+ if (!line.trim()) continue;
40
+
41
+ const parts = line.trim().split(/\s+/);
42
+ if (parts.length >= 6) {
43
+ const pid = parseInt(parts[0]);
44
+ const ppid = parseInt(parts[1]);
45
+ const command = parts[2];
46
+ const cpu = parseFloat(parts[3]);
47
+ const rss = parseInt(parts[4]); // KB
48
+ const etime = parts[5];
49
+ const uptimeSeconds = this.parseElapsedTime(etime);
50
+
51
+ processes.push({
52
+ pid,
53
+ ppid,
54
+ command,
55
+ cpu,
56
+ memory: Math.round(rss / 1024), // Convert to MB
57
+ uptime: etime,
58
+ uptimeSeconds
59
+ });
60
+ }
61
+ }
62
+
63
+ const cpuSorted = [...processes].sort((a, b) => b.cpu - a.cpu);
64
+ const memorySorted = [...processes].sort((a, b) => b.memory - a.memory);
65
+
66
+ return {
67
+ cpu: cpuSorted,
68
+ memory: memorySorted,
69
+ all: processes
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Get memory statistics using vm_stat
75
+ */
76
+ async getMemoryInfo() {
77
+ const vmOutput = await executeShellCommand('vm_stat');
78
+ const lines = vmOutput.split('\n');
79
+
80
+ const stats = {};
81
+ for (const line of lines) {
82
+ if (line.includes(':')) {
83
+ const [key, value] = line.split(':');
84
+ const cleanKey = key.trim().replace(/[^a-zA-Z0-9]/g, '_');
85
+ const numValue = parseInt(value.trim().replace('.', ''));
86
+ if (!isNaN(numValue)) {
87
+ stats[cleanKey] = numValue;
88
+ }
89
+ }
90
+ }
91
+
92
+ // Convert pages to MB (page size is typically 4096 bytes)
93
+ const pageSize = 4096;
94
+ const toMB = (pages) => Math.round((pages * pageSize) / (1024 * 1024));
95
+
96
+ return {
97
+ free: toMB(stats.Pages_free || 0),
98
+ active: toMB(stats.Pages_active || 0),
99
+ inactive: toMB(stats.Pages_inactive || 0),
100
+ wired: toMB(stats.Pages_wired_down || 0),
101
+ compressed: toMB(stats.Pages_occupied_by_compressor || 0)
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Analyze processes for suspicious activity
107
+ */
108
+ analyzeFindings(processes) {
109
+ const findings = [];
110
+ const systemPaths = ['/Applications', '/System', '/usr/bin', '/usr/sbin', '/bin', '/sbin'];
111
+ const longRunningReported = new Set();
112
+
113
+ // Check high CPU processes not in system paths
114
+ for (const proc of processes.cpu.slice(0, 20)) {
115
+ if (proc.cpu > 50) {
116
+ const isSystemPath = systemPaths.some(path => proc.command.startsWith(path));
117
+
118
+ if (!isSystemPath && !proc.command.startsWith('/Library')) {
119
+ findings.push({
120
+ type: 'high_cpu_unusual_path',
121
+ pid: proc.pid,
122
+ command: proc.command,
123
+ cpu: proc.cpu,
124
+ memory: proc.memory,
125
+ risk: proc.cpu > 80 ? 'high' : 'medium',
126
+ description: `High CPU usage (${proc.cpu}%) from non-system path`
127
+ });
128
+ }
129
+ }
130
+ }
131
+
132
+ // Check high memory processes
133
+ for (const proc of processes.memory.slice(0, 20)) {
134
+ if (proc.memory > 1000) { // > 1GB
135
+ const isSystemPath = systemPaths.some(path => proc.command.startsWith(path));
136
+
137
+ if (!isSystemPath) {
138
+ findings.push({
139
+ type: 'high_memory_unusual_path',
140
+ pid: proc.pid,
141
+ command: proc.command,
142
+ cpu: proc.cpu,
143
+ memory: proc.memory,
144
+ risk: proc.memory > 2000 ? 'high' : 'medium',
145
+ description: `High memory usage (${proc.memory}MB) from non-system path`
146
+ });
147
+ }
148
+ }
149
+ }
150
+
151
+ // Check for processes with suspicious names
152
+ const suspiciousNames = ['miner', 'crypto', 'xmrig', 'coinhive', 'malware', 'trojan'];
153
+ for (const proc of processes.all) {
154
+ const commandLower = proc.command.toLowerCase();
155
+ for (const suspName of suspiciousNames) {
156
+ if (commandLower.includes(suspName)) {
157
+ findings.push({
158
+ type: 'suspicious_name',
159
+ pid: proc.pid,
160
+ command: proc.command,
161
+ cpu: proc.cpu,
162
+ memory: proc.memory,
163
+ risk: 'high',
164
+ description: `Process name contains suspicious keyword: ${suspName}`
165
+ });
166
+ }
167
+ }
168
+ }
169
+
170
+ // Check for long-running background processes from non-system paths
171
+ for (const proc of processes.all) {
172
+ if (!proc.uptimeSeconds || proc.uptimeSeconds < 86400) continue; // > 24h
173
+
174
+ const isSystemPath = systemPaths.some(path => proc.command.startsWith(path));
175
+ if (isSystemPath || proc.command.startsWith('/Library')) continue;
176
+
177
+ const key = `${proc.pid}-long`;
178
+ if (longRunningReported.has(key)) continue;
179
+
180
+ longRunningReported.add(key);
181
+
182
+ const hours = Math.round(proc.uptimeSeconds / 3600);
183
+ findings.push({
184
+ type: 'long_running_background',
185
+ pid: proc.pid,
186
+ command: proc.command,
187
+ cpu: proc.cpu,
188
+ memory: proc.memory,
189
+ uptime: proc.uptime,
190
+ risk: proc.uptimeSeconds > 259200 ? 'high' : 'medium', // >3d high
191
+ description: `Process running ${hours}h from non-system path`
192
+ });
193
+ }
194
+
195
+ return findings;
196
+ }
197
+
198
+ /**
199
+ * Convert ps etime ([[dd-]hh:]mm:ss) to seconds
200
+ */
201
+ parseElapsedTime(etime) {
202
+ if (!etime) return 0;
203
+
204
+ const [dayPart, timePartRaw] = etime.includes('-') ? etime.split('-') : [null, etime];
205
+ const timePart = timePartRaw || etime;
206
+ const segments = timePart.split(':').map(seg => parseInt(seg, 10) || 0);
207
+
208
+ while (segments.length < 3) {
209
+ segments.unshift(0);
210
+ }
211
+
212
+ const [hours, minutes, seconds] = segments.slice(-3);
213
+ const days = dayPart ? parseInt(dayPart, 10) || 0 : 0;
214
+
215
+ return (days * 86400) + (hours * 3600) + (minutes * 60) + seconds;
216
+ }
217
+ }
@@ -0,0 +1,173 @@
1
+ import { BaseAgent } from './BaseAgent.js';
2
+ import { executeShellCommand } from '../utils/commander.js';
3
+ import { existsSync, readFileSync } from 'fs';
4
+
5
+ /**
6
+ * SystemAgent - Checks system integrity and security settings
7
+ */
8
+ export class SystemAgent extends BaseAgent {
9
+ constructor() {
10
+ super('SystemAgent');
11
+ }
12
+
13
+ async analyze() {
14
+ const sip = await this.checkSIP();
15
+ const gatekeeper = await this.checkGatekeeper();
16
+ const updates = await this.checkSystemUpdates();
17
+ const sudoers = await this.checkSudoers();
18
+
19
+ const findings = [];
20
+
21
+ // Evaluate SIP
22
+ if (!sip.enabled) {
23
+ findings.push({
24
+ type: 'sip_disabled',
25
+ risk: 'high',
26
+ description: 'System Integrity Protection (SIP) is disabled'
27
+ });
28
+ }
29
+
30
+ // Evaluate Gatekeeper
31
+ if (!gatekeeper.enabled) {
32
+ findings.push({
33
+ type: 'gatekeeper_disabled',
34
+ risk: 'medium',
35
+ description: 'Gatekeeper is disabled, allowing unsigned apps'
36
+ });
37
+ }
38
+
39
+ // Evaluate updates
40
+ if (updates.available > 0) {
41
+ findings.push({
42
+ type: 'updates_available',
43
+ risk: 'medium',
44
+ description: `${updates.available} system update(s) available`
45
+ });
46
+ }
47
+
48
+ // Evaluate sudoers
49
+ if (sudoers.risks.length > 0) {
50
+ findings.push(...sudoers.risks);
51
+ }
52
+
53
+ this.results = {
54
+ agent: this.name,
55
+ timestamp: new Date().toISOString(),
56
+ sip,
57
+ gatekeeper,
58
+ updates,
59
+ sudoers: sudoers.rules,
60
+ findings,
61
+ overallRisk: this.calculateOverallRisk(findings)
62
+ };
63
+
64
+ return this.results;
65
+ }
66
+
67
+ /**
68
+ * Check System Integrity Protection status
69
+ */
70
+ async checkSIP() {
71
+ const output = await executeShellCommand('csrutil status');
72
+ const enabled = output.toLowerCase().includes('enabled');
73
+
74
+ return {
75
+ enabled,
76
+ status: output.trim()
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Check Gatekeeper status
82
+ */
83
+ async checkGatekeeper() {
84
+ const output = await executeShellCommand('spctl --status');
85
+ const enabled = output.toLowerCase().includes('assessments enabled');
86
+
87
+ return {
88
+ enabled,
89
+ status: output.trim()
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Check for available system updates
95
+ */
96
+ async checkSystemUpdates() {
97
+ const output = await executeShellCommand('softwareupdate -l 2>&1');
98
+
99
+ // Parse update count
100
+ const lines = output.split('\n');
101
+ let updateCount = 0;
102
+ const updates = [];
103
+
104
+ for (const line of lines) {
105
+ if (line.includes('*') || line.includes('Label:')) {
106
+ updateCount++;
107
+ updates.push(line.trim());
108
+ }
109
+ }
110
+
111
+ // If output says "No new software available"
112
+ if (output.toLowerCase().includes('no new software available')) {
113
+ updateCount = 0;
114
+ }
115
+
116
+ return {
117
+ available: updateCount,
118
+ updates: updates.slice(0, 5), // Limit to first 5
119
+ lastCheck: new Date().toISOString()
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Check sudoers configuration for security issues
125
+ */
126
+ async checkSudoers() {
127
+ const rules = [];
128
+ const risks = [];
129
+
130
+ // Check main sudoers file
131
+ const sudoersPath = '/etc/sudoers';
132
+ const sudoersDPath = '/etc/sudoers.d';
133
+
134
+ try {
135
+ // Read sudoers file (may require permissions)
136
+ if (existsSync(sudoersPath)) {
137
+ const content = readFileSync(sudoersPath, 'utf-8');
138
+ const lines = content.split('\n');
139
+
140
+ for (const line of lines) {
141
+ if (line.trim() && !line.startsWith('#')) {
142
+ rules.push(line.trim());
143
+
144
+ // Check for NOPASSWD rules
145
+ if (line.includes('NOPASSWD')) {
146
+ risks.push({
147
+ type: 'sudoers_nopasswd',
148
+ rule: line.trim(),
149
+ risk: 'medium',
150
+ description: 'Sudoers rule allows passwordless sudo'
151
+ });
152
+ }
153
+
154
+ // Check for overly permissive ALL rules
155
+ if (line.includes('ALL=(ALL)') || line.includes('ALL = (ALL) ALL')) {
156
+ risks.push({
157
+ type: 'sudoers_all_permissions',
158
+ rule: line.trim(),
159
+ risk: 'high',
160
+ description: 'Sudoers rule grants full permissions'
161
+ });
162
+ }
163
+ }
164
+ }
165
+ }
166
+ } catch (error) {
167
+ // Permission denied or file doesn't exist
168
+ rules.push('Unable to read sudoers file (permission denied)');
169
+ }
170
+
171
+ return { rules, risks };
172
+ }
173
+ }