50c 3.9.1 → 3.9.3
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/bin/50c.js +15 -1
- package/lib/backdoor-checker.js +736 -0
- package/lib/subagent.js +13 -7
- package/lib/tools-registry.js +39 -0
- package/package.json +39 -39
package/bin/50c.js
CHANGED
|
@@ -1012,7 +1012,7 @@ const LOCAL_TOOLS = [
|
|
|
1012
1012
|
|
|
1013
1013
|
// 50c Team utilities - for power users who want granular control
|
|
1014
1014
|
{ name: "team_http", description: "50c Team: HTTP/HTTPS fetch. Bypasses IDE restrictions. FREE.", inputSchema: { type: "object", properties: { url: { type: "string", description: "Full URL to fetch" }, method: { type: "string", description: "HTTP method (default GET)" }, headers: { type: "object", description: "Request headers" }, body: { type: "string", description: "Request body for POST/PUT" }, timeout: { type: "number", description: "Timeout in ms (default 30000)" } }, required: ["url"] } },
|
|
1015
|
-
{ name: "team_ssh", description: "50c Team: SSH remote command.
|
|
1015
|
+
{ name: "team_ssh", description: "50c Team: SSH remote command. Configure servers in ~/.50c/servers.json. FREE.", inputSchema: { type: "object", properties: { server: { type: "string", description: "Server alias (nj/zurich/chicago) or {host,user,port}" }, command: { type: "string", description: "Command to execute on remote server" }, timeout: { type: "number", description: "Timeout in ms (default 30000)" } }, required: ["server", "command"] } },
|
|
1016
1016
|
{ name: "team_exec", description: "50c Team: Local command execution. Bypasses shell restrictions. FREE.", inputSchema: { type: "object", properties: { command: { type: "string", description: "Command to execute locally" }, cwd: { type: "string", description: "Working directory" }, timeout: { type: "number", description: "Timeout in ms (default 30000)" } }, required: ["command"] } },
|
|
1017
1017
|
{ name: "team_multi", description: "50c Team: Execute multiple tasks in parallel. FREE.", inputSchema: { type: "object", properties: { tasks: { type: "array", description: "Array of task objects [{type, ...params}]", items: { type: "object" } } }, required: ["tasks"] } },
|
|
1018
1018
|
{ name: "team_servers", description: "50c Team: List known SSH servers. FREE.", inputSchema: { type: "object", properties: {} } },
|
|
@@ -1020,6 +1020,9 @@ const LOCAL_TOOLS = [
|
|
|
1020
1020
|
{ name: "team_chain", description: "50c Team: Chain multiple tools together. Output flows via $prev placeholder.", inputSchema: { type: "object", properties: { steps: { type: "array", description: "Array of {tool, args} - use $prev for previous result", items: { type: "object" } }, input: { type: "string", description: "Initial input (optional)" } }, required: ["steps"] } },
|
|
1021
1021
|
{ name: "pre_publish", description: "Pre-publish verification. Thorough checks before npm/github/arxiv/medical publish. Profiles: npm, github, arxiv, medical, science, math. Uses AI tools (bCalc, genius+, web_search) for academic verification. FREE.", inputSchema: { type: "object", properties: { profile: { type: "string", description: "Verification profile: npm, github, arxiv, medical, science, math", enum: ["npm", "github", "arxiv", "medical", "science", "math"] }, cwd: { type: "string", description: "Directory to check (default: current)" }, save_receipt: { type: "boolean", description: "Save receipt as markdown file" } } } },
|
|
1022
1022
|
|
|
1023
|
+
// Security audit tool - runs locally, FREE
|
|
1024
|
+
{ name: "backdoor_check", description: "Security audit: check for backdoors, unauthorized RDP/SSH, persistence, suspicious connections. Geo-IP tags foreign IPs. Cross-platform. FREE.", inputSchema: { type: "object", properties: { skipGeo: { type: "boolean", description: "Skip geo-IP lookups (faster, offline)" } } } },
|
|
1025
|
+
|
|
1023
1026
|
// ENTERPRISE PRESET - Auto-Invent Swarm Pipeline ($2.00)
|
|
1024
1027
|
{ name: "auto_invent", description: "ENTERPRISE ($2.00): Full invention pipeline. Chains mind_opener → idea_fold → bcalc → genius_plus → compute → cvi_verify. Produces provable, verified solutions. Requires Enterprise tier.", inputSchema: { type: "object", properties: { problem: { type: "string", description: "Problem or hypothesis to solve/prove" }, constraints: { type: "array", items: { type: "string" }, description: "Hard constraints the solution must satisfy" }, domain: { type: "string", description: "Domain hint: math, physics, engineering, business, code", enum: ["math", "physics", "engineering", "business", "code"] }, rigor: { type: "string", description: "Rigor level: fast, standard, deep, exhaustive (all $2.00)", enum: ["fast", "standard", "deep", "exhaustive"], default: "deep" } }, required: ["problem"] } },
|
|
1025
1028
|
|
|
@@ -1796,6 +1799,17 @@ async function handleLocalTools(request) {
|
|
|
1796
1799
|
if (name === 'team_servers') {
|
|
1797
1800
|
return mcpResult(id, { ok: true, servers: KNOWN_SERVERS });
|
|
1798
1801
|
}
|
|
1802
|
+
|
|
1803
|
+
if (name === 'backdoor_check') {
|
|
1804
|
+
try {
|
|
1805
|
+
const { runAudit } = require('../lib/backdoor-checker.js');
|
|
1806
|
+
const result = await runAudit({ skipGeo: args.skipGeo });
|
|
1807
|
+
return mcpResult(id, result);
|
|
1808
|
+
} catch (e) {
|
|
1809
|
+
return mcpResult(id, { ok: false, error: e.message });
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1799
1813
|
|
|
1800
1814
|
// Pre-publish verification - thorough checks before npm/arxiv/github/medical publish
|
|
1801
1815
|
if (name === 'pre_publish') {
|
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 50c Backdoor Checker - Local security audit tool
|
|
3
|
+
* Runs entirely on the user's machine. FREE.
|
|
4
|
+
*
|
|
5
|
+
* Detects:
|
|
6
|
+
* - Unauthorized RDP/SSH logins (with geo-IP)
|
|
7
|
+
* - Persistence mechanisms (scheduled tasks, startup, registry, services)
|
|
8
|
+
* - Suspicious network connections to foreign IPs
|
|
9
|
+
* - Rogue certificates and proxy settings
|
|
10
|
+
* - IDE artifacts (Verdant, etc.)
|
|
11
|
+
* - Unauthorized user accounts
|
|
12
|
+
* - SSH authorized_keys anomalies
|
|
13
|
+
*
|
|
14
|
+
* Cross-platform: Windows (PowerShell), Linux (bash), macOS (bash)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { exec, execSync } = require('child_process');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const PLATFORM = os.platform(); // win32, linux, darwin
|
|
23
|
+
const HOME = os.homedir();
|
|
24
|
+
|
|
25
|
+
// Suspicious country indicators in IP geolocation
|
|
26
|
+
const SUSPICIOUS_GEOS = ['CN', 'RU', 'KP', 'IR'];
|
|
27
|
+
|
|
28
|
+
// Known suspicious IDE directories
|
|
29
|
+
const SUSPICIOUS_IDE_DIRS = [
|
|
30
|
+
'.verdent', '.verdant', '.verd',
|
|
31
|
+
'.trae', // ByteDance IDE
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Known suspicious process names
|
|
35
|
+
const SUSPICIOUS_PROCESSES = [
|
|
36
|
+
'cryptominer', 'xmrig', 'minerd', 'cgminer',
|
|
37
|
+
'nc.exe', 'ncat', 'netcat',
|
|
38
|
+
'mimikatz', 'lazagne', 'procdump',
|
|
39
|
+
'psexec', 'wmic', // lateral movement
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Run a command and return stdout (or error message)
|
|
44
|
+
*/
|
|
45
|
+
function run(cmd, timeout = 30000) {
|
|
46
|
+
try {
|
|
47
|
+
return execSync(cmd, {
|
|
48
|
+
timeout,
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
51
|
+
windowsHide: true,
|
|
52
|
+
maxBuffer: 10 * 1024 * 1024
|
|
53
|
+
}).trim();
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return `[ERROR] ${e.message}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Run PowerShell command (Windows)
|
|
61
|
+
*/
|
|
62
|
+
function ps(script, timeout = 30000) {
|
|
63
|
+
const escaped = script.replace(/"/g, '\\"');
|
|
64
|
+
return run(`powershell -NoProfile -ExecutionPolicy Bypass -Command "${escaped}"`, timeout);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Geo-IP lookup using free API (ip-api.com)
|
|
69
|
+
* Returns { country, countryCode, city, isp, org }
|
|
70
|
+
*/
|
|
71
|
+
async function geoIP(ip) {
|
|
72
|
+
try {
|
|
73
|
+
const http = require('http');
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const req = http.get(`http://ip-api.com/json/${ip}?fields=country,countryCode,city,isp,org`, { timeout: 5000 }, (res) => {
|
|
76
|
+
let data = '';
|
|
77
|
+
res.on('data', c => data += c);
|
|
78
|
+
res.on('end', () => {
|
|
79
|
+
try { resolve(JSON.parse(data)); }
|
|
80
|
+
catch { resolve({ country: 'unknown', countryCode: '??' }); }
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
req.on('error', () => resolve({ country: 'unknown', countryCode: '??' }));
|
|
84
|
+
req.on('timeout', () => { req.destroy(); resolve({ country: 'unknown', countryCode: '??' }); });
|
|
85
|
+
});
|
|
86
|
+
} catch {
|
|
87
|
+
return { country: 'unknown', countryCode: '??' };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Batch geo-IP lookup (ip-api.com supports batch of up to 100)
|
|
93
|
+
*/
|
|
94
|
+
async function batchGeoIP(ips) {
|
|
95
|
+
if (ips.length === 0) return {};
|
|
96
|
+
const uniqueIPs = [...new Set(ips)].slice(0, 100);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const http = require('http');
|
|
100
|
+
const body = JSON.stringify(uniqueIPs.map(ip => ({ query: ip, fields: 'query,country,countryCode,city,isp,org' })));
|
|
101
|
+
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
const req = http.request({
|
|
104
|
+
hostname: 'ip-api.com',
|
|
105
|
+
path: '/batch',
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
108
|
+
timeout: 10000
|
|
109
|
+
}, (res) => {
|
|
110
|
+
let data = '';
|
|
111
|
+
res.on('data', c => data += c);
|
|
112
|
+
res.on('end', () => {
|
|
113
|
+
try {
|
|
114
|
+
const results = JSON.parse(data);
|
|
115
|
+
const map = {};
|
|
116
|
+
for (const r of results) { map[r.query] = r; }
|
|
117
|
+
resolve(map);
|
|
118
|
+
} catch { resolve({}); }
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
req.on('error', () => resolve({}));
|
|
122
|
+
req.on('timeout', () => { req.destroy(); resolve({}); });
|
|
123
|
+
req.write(body);
|
|
124
|
+
req.end();
|
|
125
|
+
});
|
|
126
|
+
} catch {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================
|
|
132
|
+
// WINDOWS CHECKS
|
|
133
|
+
// ============================================
|
|
134
|
+
|
|
135
|
+
function windowsChecks() {
|
|
136
|
+
const findings = [];
|
|
137
|
+
|
|
138
|
+
// 1. RDP Login History (Event ID 4624 Type 10 = RemoteInteractive)
|
|
139
|
+
findings.push({ check: 'RDP Login History', data: getRDPLogins() });
|
|
140
|
+
|
|
141
|
+
// 2. Failed RDP attempts (Event ID 4625)
|
|
142
|
+
findings.push({ check: 'Failed Login Attempts', data: getFailedLogins() });
|
|
143
|
+
|
|
144
|
+
// 3. Local user accounts
|
|
145
|
+
findings.push({ check: 'Local User Accounts', data: getLocalUsers() });
|
|
146
|
+
|
|
147
|
+
// 4. Scheduled tasks (persistence)
|
|
148
|
+
findings.push({ check: 'Scheduled Tasks', data: getScheduledTasks() });
|
|
149
|
+
|
|
150
|
+
// 5. Startup programs (registry Run keys)
|
|
151
|
+
findings.push({ check: 'Startup Registry Keys', data: getStartupKeys() });
|
|
152
|
+
|
|
153
|
+
// 6. Running services (unusual ones)
|
|
154
|
+
findings.push({ check: 'Services', data: getServices() });
|
|
155
|
+
|
|
156
|
+
// 7. Active network connections
|
|
157
|
+
findings.push({ check: 'Active Network Connections', data: getNetConnections() });
|
|
158
|
+
|
|
159
|
+
// 8. Firewall rules
|
|
160
|
+
findings.push({ check: 'Firewall Rules', data: getFirewallRules() });
|
|
161
|
+
|
|
162
|
+
// 9. Certificates
|
|
163
|
+
findings.push({ check: 'Root Certificates', data: getCertificates() });
|
|
164
|
+
|
|
165
|
+
// 10. Proxy settings
|
|
166
|
+
findings.push({ check: 'Proxy Settings', data: getProxySettings() });
|
|
167
|
+
|
|
168
|
+
// 11. WinRM / Remote Management
|
|
169
|
+
findings.push({ check: 'Remote Management (WinRM)', data: getWinRM() });
|
|
170
|
+
|
|
171
|
+
// 12. SSH authorized_keys
|
|
172
|
+
findings.push({ check: 'SSH Authorized Keys', data: getSSHKeys() });
|
|
173
|
+
|
|
174
|
+
// 13. Suspicious IDE artifacts
|
|
175
|
+
findings.push({ check: 'Suspicious IDE Artifacts', data: getIDEArtifacts() });
|
|
176
|
+
|
|
177
|
+
// 14. PowerShell history (may reveal attacker commands)
|
|
178
|
+
findings.push({ check: 'PowerShell History', data: getPSHistory() });
|
|
179
|
+
|
|
180
|
+
// 15. DNS cache (suspicious domains)
|
|
181
|
+
findings.push({ check: 'DNS Cache', data: getDNSCache() });
|
|
182
|
+
|
|
183
|
+
// 16. Recently installed programs
|
|
184
|
+
findings.push({ check: 'Recently Installed Programs', data: getRecentInstalls() });
|
|
185
|
+
|
|
186
|
+
// 17. Hidden/suspicious processes
|
|
187
|
+
findings.push({ check: 'Suspicious Processes', data: getSuspiciousProcesses() });
|
|
188
|
+
|
|
189
|
+
return findings;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getRDPLogins() {
|
|
193
|
+
const result = ps(`
|
|
194
|
+
try {
|
|
195
|
+
$events = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624} -MaxEvents 200 -ErrorAction Stop |
|
|
196
|
+
Where-Object { $_.Properties[8].Value -eq 10 } |
|
|
197
|
+
Select-Object -First 50 |
|
|
198
|
+
ForEach-Object {
|
|
199
|
+
$ip = $_.Properties[18].Value
|
|
200
|
+
$user = $_.Properties[5].Value
|
|
201
|
+
$domain = $_.Properties[6].Value
|
|
202
|
+
$time = $_.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
|
|
203
|
+
"$time | $domain\\\\$user | $ip"
|
|
204
|
+
}
|
|
205
|
+
if ($events) { $events -join '\\n' } else { 'No RDP logins found in Security log' }
|
|
206
|
+
} catch { 'Access denied or Security log unavailable - run as Administrator' }
|
|
207
|
+
`, 60000);
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getFailedLogins() {
|
|
212
|
+
const result = ps(`
|
|
213
|
+
try {
|
|
214
|
+
$events = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625} -MaxEvents 100 -ErrorAction Stop |
|
|
215
|
+
Select-Object -First 30 |
|
|
216
|
+
ForEach-Object {
|
|
217
|
+
$ip = $_.Properties[19].Value
|
|
218
|
+
$user = $_.Properties[5].Value
|
|
219
|
+
$time = $_.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
|
|
220
|
+
"$time | $user | $ip"
|
|
221
|
+
}
|
|
222
|
+
if ($events) { $events -join '\\n' } else { 'No failed login attempts found' }
|
|
223
|
+
} catch { 'Access denied or Security log unavailable - run as Administrator' }
|
|
224
|
+
`, 60000);
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getLocalUsers() {
|
|
229
|
+
return ps(`
|
|
230
|
+
Get-LocalUser | ForEach-Object {
|
|
231
|
+
$name = $_.Name
|
|
232
|
+
$enabled = $_.Enabled
|
|
233
|
+
$lastLogon = $_.LastLogon
|
|
234
|
+
$desc = $_.Description
|
|
235
|
+
"$name | Enabled=$enabled | LastLogon=$lastLogon | $desc"
|
|
236
|
+
} | Out-String
|
|
237
|
+
`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function getScheduledTasks() {
|
|
241
|
+
return ps(`
|
|
242
|
+
Get-ScheduledTask | Where-Object { $_.State -ne 'Disabled' -and $_.TaskPath -notlike '\\\\Microsoft\\\\*' } |
|
|
243
|
+
ForEach-Object {
|
|
244
|
+
$name = $_.TaskName
|
|
245
|
+
$path = $_.TaskPath
|
|
246
|
+
$actions = ($_.Actions | ForEach-Object { $_.Execute + ' ' + $_.Arguments }) -join '; '
|
|
247
|
+
"$path$name | $actions"
|
|
248
|
+
} | Out-String
|
|
249
|
+
`, 60000);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function getStartupKeys() {
|
|
253
|
+
const keys = [
|
|
254
|
+
'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run',
|
|
255
|
+
'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce',
|
|
256
|
+
'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run',
|
|
257
|
+
'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce',
|
|
258
|
+
'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run',
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
return ps(`
|
|
262
|
+
$keys = @(${keys.map(k => `'${k}'`).join(',')})
|
|
263
|
+
foreach ($key in $keys) {
|
|
264
|
+
if (Test-Path $key) {
|
|
265
|
+
"=== $key ==="
|
|
266
|
+
Get-ItemProperty $key -ErrorAction SilentlyContinue |
|
|
267
|
+
ForEach-Object { $_.PSObject.Properties | Where-Object { $_.Name -notlike 'PS*' } |
|
|
268
|
+
ForEach-Object { " $($_.Name) = $($_.Value)" }
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function getServices() {
|
|
276
|
+
return ps(`
|
|
277
|
+
Get-Service | Where-Object { $_.Status -eq 'Running' -and $_.StartType -eq 'Automatic' } |
|
|
278
|
+
Where-Object { $_.DisplayName -notlike 'Windows*' -and $_.DisplayName -notlike 'Microsoft*' -and $_.DisplayName -notlike 'DCOM*' -and $_.DisplayName -notlike 'Plug*' } |
|
|
279
|
+
Select-Object Name, DisplayName, StartType |
|
|
280
|
+
Format-Table -AutoSize | Out-String
|
|
281
|
+
`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getNetConnections() {
|
|
285
|
+
return ps(`
|
|
286
|
+
Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue |
|
|
287
|
+
Where-Object { $_.RemoteAddress -ne '127.0.0.1' -and $_.RemoteAddress -ne '::1' -and $_.RemoteAddress -ne '0.0.0.0' } |
|
|
288
|
+
Select-Object LocalPort, RemoteAddress, RemotePort, OwningProcess,
|
|
289
|
+
@{N='Process';E={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} |
|
|
290
|
+
Sort-Object RemoteAddress |
|
|
291
|
+
Format-Table -AutoSize | Out-String
|
|
292
|
+
`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getFirewallRules() {
|
|
296
|
+
return ps(`
|
|
297
|
+
Get-NetFirewallRule -Enabled True -Direction Inbound -Action Allow -ErrorAction SilentlyContinue |
|
|
298
|
+
Where-Object { $_.DisplayName -notlike 'Core Networking*' -and $_.DisplayName -notlike 'Windows*' } |
|
|
299
|
+
Select-Object -First 30 DisplayName, Profile, Direction |
|
|
300
|
+
Format-Table -AutoSize | Out-String
|
|
301
|
+
`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function getCertificates() {
|
|
305
|
+
return ps(`
|
|
306
|
+
Get-ChildItem Cert:\\LocalMachine\\Root |
|
|
307
|
+
Where-Object { $_.NotAfter -gt (Get-Date) } |
|
|
308
|
+
Select-Object Subject, Issuer, NotAfter, Thumbprint |
|
|
309
|
+
Format-Table -AutoSize -Wrap | Out-String
|
|
310
|
+
`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function getProxySettings() {
|
|
314
|
+
return ps(`
|
|
315
|
+
$proxy = Get-ItemProperty 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings' -ErrorAction SilentlyContinue
|
|
316
|
+
"ProxyEnable: $($proxy.ProxyEnable)"
|
|
317
|
+
"ProxyServer: $($proxy.ProxyServer)"
|
|
318
|
+
"ProxyOverride: $($proxy.ProxyOverride)"
|
|
319
|
+
"AutoConfigURL: $($proxy.AutoConfigURL)"
|
|
320
|
+
""
|
|
321
|
+
"Environment:"
|
|
322
|
+
"HTTP_PROXY: $env:HTTP_PROXY"
|
|
323
|
+
"HTTPS_PROXY: $env:HTTPS_PROXY"
|
|
324
|
+
"ALL_PROXY: $env:ALL_PROXY"
|
|
325
|
+
`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function getWinRM() {
|
|
329
|
+
return ps(`
|
|
330
|
+
try {
|
|
331
|
+
$status = Get-Service WinRM -ErrorAction Stop
|
|
332
|
+
"WinRM Status: $($status.Status)"
|
|
333
|
+
if ($status.Status -eq 'Running') {
|
|
334
|
+
"WARNING: WinRM is running - remote management is enabled"
|
|
335
|
+
winrm get winrm/config/client 2>$null
|
|
336
|
+
}
|
|
337
|
+
} catch {
|
|
338
|
+
"WinRM: Not found or access denied"
|
|
339
|
+
}
|
|
340
|
+
`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function getSSHKeys() {
|
|
344
|
+
const results = [];
|
|
345
|
+
const sshDir = path.join(HOME, '.ssh');
|
|
346
|
+
|
|
347
|
+
if (fs.existsSync(sshDir)) {
|
|
348
|
+
const authKeys = path.join(sshDir, 'authorized_keys');
|
|
349
|
+
if (fs.existsSync(authKeys)) {
|
|
350
|
+
results.push('=== authorized_keys ===');
|
|
351
|
+
const content = fs.readFileSync(authKeys, 'utf8');
|
|
352
|
+
const keys = content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
|
|
353
|
+
for (const key of keys) {
|
|
354
|
+
const parts = key.trim().split(/\s+/);
|
|
355
|
+
const comment = parts.length >= 3 ? parts.slice(2).join(' ') : 'no-comment';
|
|
356
|
+
const type = parts[0];
|
|
357
|
+
results.push(` ${type} ... ${comment}`);
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
results.push('No authorized_keys file found');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Check for unusual files in .ssh
|
|
364
|
+
const files = fs.readdirSync(sshDir);
|
|
365
|
+
const unusual = files.filter(f => !['authorized_keys', 'known_hosts', 'config', 'id_rsa', 'id_rsa.pub', 'id_ed25519', 'id_ed25519.pub', 'id_ecdsa', 'id_ecdsa.pub', 'id_ed25519_automation', 'id_ed25519_automation.pub'].includes(f));
|
|
366
|
+
if (unusual.length > 0) {
|
|
367
|
+
results.push(`\nUnusual files in .ssh/: ${unusual.join(', ')}`);
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
results.push('No .ssh directory found');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return results.join('\n');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function getIDEArtifacts() {
|
|
377
|
+
const results = [];
|
|
378
|
+
|
|
379
|
+
for (const dir of SUSPICIOUS_IDE_DIRS) {
|
|
380
|
+
const fullPath = path.join(HOME, dir);
|
|
381
|
+
if (fs.existsSync(fullPath)) {
|
|
382
|
+
results.push(`[!] FOUND: ${fullPath}`);
|
|
383
|
+
try {
|
|
384
|
+
const files = fs.readdirSync(fullPath);
|
|
385
|
+
results.push(` Contents: ${files.slice(0, 20).join(', ')}`);
|
|
386
|
+
|
|
387
|
+
// Check for MCP config with embedded secrets
|
|
388
|
+
const mcpPath = path.join(fullPath, 'mcp.json');
|
|
389
|
+
if (fs.existsSync(mcpPath)) {
|
|
390
|
+
results.push(` [!!] MCP config found: ${mcpPath}`);
|
|
391
|
+
try {
|
|
392
|
+
const mcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
393
|
+
const servers = Object.keys(mcp.mcpServers || {});
|
|
394
|
+
results.push(` MCP servers configured: ${servers.join(', ')}`);
|
|
395
|
+
// Check for embedded API keys
|
|
396
|
+
for (const [name, srv] of Object.entries(mcp.mcpServers || {})) {
|
|
397
|
+
if (srv.env) {
|
|
398
|
+
const envKeys = Object.keys(srv.env);
|
|
399
|
+
results.push(` [!] ${name} has env vars: ${envKeys.join(', ')}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} catch {}
|
|
403
|
+
}
|
|
404
|
+
} catch {}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Also check AppData for Verdant
|
|
409
|
+
const appDataPaths = [
|
|
410
|
+
path.join(process.env.APPDATA || '', 'Verdent'),
|
|
411
|
+
path.join(process.env.APPDATA || '', 'Verdant'),
|
|
412
|
+
path.join(process.env.LOCALAPPDATA || '', 'Verdent'),
|
|
413
|
+
path.join(process.env.LOCALAPPDATA || '', 'Verdant'),
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
for (const p of appDataPaths) {
|
|
417
|
+
if (p && fs.existsSync(p)) {
|
|
418
|
+
results.push(`[!] FOUND AppData: ${p}`);
|
|
419
|
+
try {
|
|
420
|
+
const files = fs.readdirSync(p);
|
|
421
|
+
results.push(` Contents: ${files.slice(0, 20).join(', ')}`);
|
|
422
|
+
} catch {}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (results.length === 0) {
|
|
427
|
+
results.push('No suspicious IDE artifacts found');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return results.join('\n');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function getPSHistory() {
|
|
434
|
+
const histPath = path.join(HOME, 'AppData', 'Roaming', 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt');
|
|
435
|
+
if (fs.existsSync(histPath)) {
|
|
436
|
+
try {
|
|
437
|
+
const content = fs.readFileSync(histPath, 'utf8');
|
|
438
|
+
const lines = content.split('\n').slice(-100); // last 100 commands
|
|
439
|
+
const suspicious = lines.filter(l => {
|
|
440
|
+
const lower = l.toLowerCase();
|
|
441
|
+
return lower.includes('invoke-webrequest') || lower.includes('downloadstring') ||
|
|
442
|
+
lower.includes('downloadfile') || lower.includes('net.webclient') ||
|
|
443
|
+
lower.includes('iex') || lower.includes('invoke-expression') ||
|
|
444
|
+
lower.includes('bypass') || lower.includes('hidden') ||
|
|
445
|
+
lower.includes('-enc ') || lower.includes('encodedcommand') ||
|
|
446
|
+
lower.includes('certutil') || lower.includes('bitsadmin');
|
|
447
|
+
});
|
|
448
|
+
if (suspicious.length > 0) {
|
|
449
|
+
return `[!] Suspicious PowerShell commands found:\n${suspicious.join('\n')}`;
|
|
450
|
+
}
|
|
451
|
+
return `Last 100 commands checked - no suspicious patterns found (${lines.length} total lines in history)`;
|
|
452
|
+
} catch {
|
|
453
|
+
return 'Could not read PowerShell history';
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return 'No PowerShell history file found';
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function getDNSCache() {
|
|
460
|
+
return ps(`
|
|
461
|
+
$cache = Get-DnsClientCache -ErrorAction SilentlyContinue |
|
|
462
|
+
Select-Object -First 100 Entry, Data |
|
|
463
|
+
Where-Object { $_.Entry -match '\\.(cn|ru|ir|kp)$' -or $_.Entry -match '(baidu|qq|163|aliyun|tencent|weibo|bytedance|douyin)' }
|
|
464
|
+
if ($cache) {
|
|
465
|
+
"Suspicious DNS entries found:"
|
|
466
|
+
$cache | Format-Table -AutoSize | Out-String
|
|
467
|
+
} else {
|
|
468
|
+
"No suspicious DNS entries (.cn/.ru/.ir/.kp or known Chinese services)"
|
|
469
|
+
}
|
|
470
|
+
`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function getRecentInstalls() {
|
|
474
|
+
return ps(`
|
|
475
|
+
Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* |
|
|
476
|
+
Where-Object { $_.InstallDate -and $_.InstallDate -gt (Get-Date).AddDays(-90).ToString('yyyyMMdd') } |
|
|
477
|
+
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
|
|
478
|
+
Sort-Object InstallDate -Descending |
|
|
479
|
+
Format-Table -AutoSize | Out-String
|
|
480
|
+
`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function getSuspiciousProcesses() {
|
|
484
|
+
return ps(`
|
|
485
|
+
$procs = Get-Process -ErrorAction SilentlyContinue |
|
|
486
|
+
Select-Object Name, Id, Path, Company |
|
|
487
|
+
Where-Object {
|
|
488
|
+
$suspicious = @(${SUSPICIOUS_PROCESSES.map(p => `'${p}'`).join(',')})
|
|
489
|
+
$_.Name -in $suspicious -or
|
|
490
|
+
($_.Path -and ($_.Path -match '\\\\Temp\\\\' -or $_.Path -match '\\\\tmp\\\\' -or $_.Path -match '\\\\Downloads\\\\'))
|
|
491
|
+
}
|
|
492
|
+
if ($procs) {
|
|
493
|
+
"[!] Suspicious processes found:"
|
|
494
|
+
$procs | Format-Table -AutoSize | Out-String
|
|
495
|
+
} else {
|
|
496
|
+
"No known suspicious processes found"
|
|
497
|
+
}
|
|
498
|
+
`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ============================================
|
|
502
|
+
// LINUX CHECKS
|
|
503
|
+
// ============================================
|
|
504
|
+
|
|
505
|
+
function linuxChecks() {
|
|
506
|
+
const findings = [];
|
|
507
|
+
|
|
508
|
+
findings.push({ check: 'SSH Login History', data: run('last -50 2>/dev/null || echo "last command not available"') });
|
|
509
|
+
findings.push({ check: 'Failed SSH Attempts', data: run('grep "Failed password" /var/log/auth.log 2>/dev/null | tail -30 || grep "Failed password" /var/log/secure 2>/dev/null | tail -30 || journalctl -u sshd --no-pager -n 30 2>/dev/null | grep -i "failed" || echo "No auth logs accessible"') });
|
|
510
|
+
findings.push({ check: 'User Accounts', data: run('cat /etc/passwd | grep -v nologin | grep -v /false') });
|
|
511
|
+
findings.push({ check: 'Sudoers', data: run('cat /etc/sudoers 2>/dev/null; ls -la /etc/sudoers.d/ 2>/dev/null') });
|
|
512
|
+
findings.push({ check: 'Crontabs', data: run('for user in $(cut -f1 -d: /etc/passwd); do crontab -l -u $user 2>/dev/null && echo "=== $user ==="; done; cat /etc/crontab 2>/dev/null; ls -la /etc/cron.d/ 2>/dev/null') });
|
|
513
|
+
findings.push({ check: 'SSH Authorized Keys', data: getSSHKeys() });
|
|
514
|
+
findings.push({ check: 'Active Connections', data: run('ss -tunp 2>/dev/null || netstat -tunp 2>/dev/null') });
|
|
515
|
+
findings.push({ check: 'Listening Ports', data: run('ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null') });
|
|
516
|
+
findings.push({ check: 'Running Services', data: run('systemctl list-units --type=service --state=running 2>/dev/null | head -40') });
|
|
517
|
+
findings.push({ check: 'Suspicious IDE Artifacts', data: getIDEArtifacts() });
|
|
518
|
+
findings.push({ check: 'Unusual SUID Binaries', data: run('find / -perm -4000 -type f 2>/dev/null | grep -v -E "(sudo|passwd|ping|mount|su|chsh|chfn|newgrp|gpasswd|pkexec)" | head -20') });
|
|
519
|
+
findings.push({ check: 'Startup Scripts', data: run('ls -la /etc/init.d/ 2>/dev/null; ls -la /etc/rc.local 2>/dev/null; systemctl list-unit-files --state=enabled 2>/dev/null | head -30') });
|
|
520
|
+
findings.push({ check: 'Bash History (suspicious)', data: run('grep -E "curl.*\\|.*sh|wget.*\\|.*sh|python.*-c.*import|nc\\s+-|/dev/tcp|base64.*-d" ~/.bash_history 2>/dev/null | tail -20 || echo "No suspicious bash history patterns"') });
|
|
521
|
+
|
|
522
|
+
return findings;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ============================================
|
|
526
|
+
// macOS CHECKS
|
|
527
|
+
// ============================================
|
|
528
|
+
|
|
529
|
+
function macChecks() {
|
|
530
|
+
const findings = [];
|
|
531
|
+
|
|
532
|
+
// 1. Login history (last works on macOS)
|
|
533
|
+
findings.push({ check: 'Login History', data: run('last -50 2>/dev/null || echo "last command not available"') });
|
|
534
|
+
|
|
535
|
+
// 2. Failed SSH/login attempts via unified log
|
|
536
|
+
findings.push({ check: 'Failed Login Attempts', data: run('log show --predicate \'eventMessage contains "failed" AND eventMessage contains "ssh"\' --style syslog --last 7d 2>/dev/null | tail -30 || echo "No failed SSH in unified log"', 60000) });
|
|
537
|
+
|
|
538
|
+
// 3. Remote login (SSH) status
|
|
539
|
+
findings.push({ check: 'Remote Login (SSH) Status', data: run('systemsetup -getremotelogin 2>/dev/null || echo "Cannot check remote login status"') });
|
|
540
|
+
|
|
541
|
+
// 4. Screen sharing / VNC / ARD
|
|
542
|
+
findings.push({ check: 'Screen Sharing / VNC / ARD', data: run('launchctl list 2>/dev/null | grep -iE "vnc|screensharing|ARD|remote" || echo "No screen sharing services found"') });
|
|
543
|
+
|
|
544
|
+
// 5. User accounts
|
|
545
|
+
findings.push({ check: 'User Accounts', data: run('dscl . list /Users | grep -v "^_" | grep -v daemon | grep -v nobody') });
|
|
546
|
+
|
|
547
|
+
// 6. Admin users
|
|
548
|
+
findings.push({ check: 'Admin Users', data: run('dscl . -read /Groups/admin GroupMembership 2>/dev/null || echo "Cannot read admin group"') });
|
|
549
|
+
|
|
550
|
+
// 7. LaunchAgents (user-level persistence - PRIMARY backdoor vector on macOS)
|
|
551
|
+
findings.push({ check: 'User LaunchAgents', data: getMacLaunchItems(path.join(HOME, 'Library', 'LaunchAgents')) });
|
|
552
|
+
|
|
553
|
+
// 8. System LaunchAgents
|
|
554
|
+
findings.push({ check: 'System LaunchAgents', data: getMacLaunchItems('/Library/LaunchAgents') });
|
|
555
|
+
|
|
556
|
+
// 9. LaunchDaemons (root-level persistence)
|
|
557
|
+
findings.push({ check: 'LaunchDaemons', data: getMacLaunchItems('/Library/LaunchDaemons') });
|
|
558
|
+
|
|
559
|
+
// 10. Login Items (GUI persistence)
|
|
560
|
+
findings.push({ check: 'Login Items', data: run('osascript -e \'tell application "System Events" to get the name of every login item\' 2>/dev/null || echo "Cannot read login items"') });
|
|
561
|
+
|
|
562
|
+
// 11. Crontabs
|
|
563
|
+
findings.push({ check: 'Crontabs', data: run('crontab -l 2>/dev/null || echo "No user crontab"; echo "---"; cat /etc/crontab 2>/dev/null || echo "No /etc/crontab"') });
|
|
564
|
+
|
|
565
|
+
// 12. SSH authorized_keys
|
|
566
|
+
findings.push({ check: 'SSH Authorized Keys', data: getSSHKeys() });
|
|
567
|
+
|
|
568
|
+
// 13. Active connections
|
|
569
|
+
findings.push({ check: 'Active Connections', data: run('netstat -an 2>/dev/null | grep ESTABLISHED | head -40 || lsof -i -P -n 2>/dev/null | grep ESTABLISHED | head -40') });
|
|
570
|
+
|
|
571
|
+
// 14. Listening ports
|
|
572
|
+
findings.push({ check: 'Listening Ports', data: run('lsof -i -P -n 2>/dev/null | grep LISTEN | head -30') });
|
|
573
|
+
|
|
574
|
+
// 15. Profiles (MDM / configuration profiles - can enforce proxy, certs, etc.)
|
|
575
|
+
findings.push({ check: 'Configuration Profiles (MDM)', data: run('profiles list 2>/dev/null || profiles -L 2>/dev/null || echo "No profiles command or no profiles installed"') });
|
|
576
|
+
|
|
577
|
+
// 16. Keychain - look for suspicious root certs
|
|
578
|
+
findings.push({ check: 'Custom Root Certificates', data: run('security find-certificate -a -p /Library/Keychains/System.keychain 2>/dev/null | openssl x509 -noout -subject -issuer 2>/dev/null | head -20 || echo "Cannot enumerate system keychain"') });
|
|
579
|
+
|
|
580
|
+
// 17. Proxy settings
|
|
581
|
+
findings.push({ check: 'Proxy Settings', data: run('networksetup -getwebproxy Wi-Fi 2>/dev/null; networksetup -getsecurewebproxy Wi-Fi 2>/dev/null; networksetup -getautoproxyurl Wi-Fi 2>/dev/null; echo "HTTP_PROXY=$HTTP_PROXY"; echo "HTTPS_PROXY=$HTTPS_PROXY"') });
|
|
582
|
+
|
|
583
|
+
// 18. Firewall status
|
|
584
|
+
findings.push({ check: 'Firewall Status', data: run('/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>/dev/null || echo "Cannot check firewall"') });
|
|
585
|
+
|
|
586
|
+
// 19. Suspicious IDE artifacts
|
|
587
|
+
findings.push({ check: 'Suspicious IDE Artifacts', data: getIDEArtifacts() });
|
|
588
|
+
|
|
589
|
+
// 20. Shell history (suspicious patterns)
|
|
590
|
+
findings.push({ check: 'Shell History (suspicious)', data: run('grep -hE "curl.*\\|.*sh|wget.*\\|.*sh|python.*-c.*import|nc\\s+-|/dev/tcp|base64.*-d|osascript.*-e" ~/.bash_history ~/.zsh_history 2>/dev/null | tail -20 || echo "No suspicious shell history patterns"') });
|
|
591
|
+
|
|
592
|
+
// 21. Gatekeeper and SIP status
|
|
593
|
+
findings.push({ check: 'Gatekeeper & SIP Status', data: run('spctl --status 2>/dev/null; csrutil status 2>/dev/null') });
|
|
594
|
+
|
|
595
|
+
// 22. TCC (privacy permissions) - what apps have accessibility/screen recording/etc.
|
|
596
|
+
findings.push({ check: 'Privacy Permissions (TCC)', data: run('sqlite3 "$HOME/Library/Application Support/com.apple.TCC/TCC.db" "SELECT client,service,auth_value FROM access WHERE auth_value=2" 2>/dev/null || echo "Cannot read TCC database (may need Full Disk Access)"') });
|
|
597
|
+
|
|
598
|
+
return findings;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Read macOS LaunchAgent/LaunchDaemon plist files for suspicious entries
|
|
603
|
+
*/
|
|
604
|
+
function getMacLaunchItems(dirPath) {
|
|
605
|
+
const results = [];
|
|
606
|
+
try {
|
|
607
|
+
if (!fs.existsSync(dirPath)) {
|
|
608
|
+
return `Directory not found: ${dirPath}`;
|
|
609
|
+
}
|
|
610
|
+
const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.plist'));
|
|
611
|
+
if (files.length === 0) {
|
|
612
|
+
return `No plist files in ${dirPath}`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
for (const file of files) {
|
|
616
|
+
const fullPath = path.join(dirPath, file);
|
|
617
|
+
// Use plutil to convert plist to readable format
|
|
618
|
+
const content = run(`plutil -p "${fullPath}" 2>/dev/null`);
|
|
619
|
+
|
|
620
|
+
// Flag non-Apple items
|
|
621
|
+
const isApple = file.startsWith('com.apple.') || file.startsWith('com.openssh.');
|
|
622
|
+
const label = isApple ? ' ' : '[!] ';
|
|
623
|
+
|
|
624
|
+
// Extract program/command from plist output
|
|
625
|
+
const programMatch = content.match(/"Program(?:Arguments)?" => (?:\[[\s\S]*?\]|"[^"]*")/);
|
|
626
|
+
const program = programMatch ? programMatch[0].substring(0, 120) : '';
|
|
627
|
+
|
|
628
|
+
results.push(`${label}${file}`);
|
|
629
|
+
if (program) results.push(` ${program}`);
|
|
630
|
+
}
|
|
631
|
+
} catch (e) {
|
|
632
|
+
results.push(`[ERROR] Cannot read ${dirPath}: ${e.message}`);
|
|
633
|
+
}
|
|
634
|
+
return results.join('\n');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ============================================
|
|
638
|
+
// MAIN AUDIT FUNCTION
|
|
639
|
+
// ============================================
|
|
640
|
+
|
|
641
|
+
async function runAudit(options = {}) {
|
|
642
|
+
const startTime = Date.now();
|
|
643
|
+
const result = {
|
|
644
|
+
ok: true,
|
|
645
|
+
platform: PLATFORM,
|
|
646
|
+
hostname: os.hostname(),
|
|
647
|
+
timestamp: new Date().toISOString(),
|
|
648
|
+
user: os.userInfo().username,
|
|
649
|
+
findings: [],
|
|
650
|
+
alerts: [],
|
|
651
|
+
summary: {}
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// Run platform-specific checks
|
|
655
|
+
if (PLATFORM === 'win32') {
|
|
656
|
+
result.findings = windowsChecks();
|
|
657
|
+
} else if (PLATFORM === 'darwin') {
|
|
658
|
+
result.findings = macChecks();
|
|
659
|
+
} else {
|
|
660
|
+
result.findings = linuxChecks();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Extract unique IPs from findings for geo-lookup
|
|
664
|
+
const allIPs = new Set();
|
|
665
|
+
for (const f of result.findings) {
|
|
666
|
+
if (typeof f.data === 'string') {
|
|
667
|
+
const ipMatches = f.data.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g) || [];
|
|
668
|
+
for (const ip of ipMatches) {
|
|
669
|
+
if (!ip.startsWith('127.') && !ip.startsWith('0.') && !ip.startsWith('10.') &&
|
|
670
|
+
!ip.startsWith('192.168.') && !ip.startsWith('169.254.') &&
|
|
671
|
+
!ip.match(/^172\.(1[6-9]|2\d|3[01])\./)) {
|
|
672
|
+
allIPs.add(ip);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Batch geo-IP lookup
|
|
679
|
+
if (allIPs.size > 0 && !options.skipGeo) {
|
|
680
|
+
const geoResults = await batchGeoIP([...allIPs]);
|
|
681
|
+
result.geoIP = {};
|
|
682
|
+
|
|
683
|
+
for (const [ip, geo] of Object.entries(geoResults)) {
|
|
684
|
+
result.geoIP[ip] = {
|
|
685
|
+
country: geo.country,
|
|
686
|
+
countryCode: geo.countryCode,
|
|
687
|
+
city: geo.city,
|
|
688
|
+
isp: geo.isp,
|
|
689
|
+
org: geo.org
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
// Flag suspicious countries
|
|
693
|
+
if (SUSPICIOUS_GEOS.includes(geo.countryCode)) {
|
|
694
|
+
result.alerts.push({
|
|
695
|
+
severity: 'HIGH',
|
|
696
|
+
message: `Connection from suspicious country: ${ip} → ${geo.country} (${geo.city}) [${geo.isp}]`,
|
|
697
|
+
ip,
|
|
698
|
+
geo
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Analyze findings for alerts
|
|
705
|
+
for (const f of result.findings) {
|
|
706
|
+
if (typeof f.data === 'string') {
|
|
707
|
+
if (f.data.includes('[!]') || f.data.includes('[!!]')) {
|
|
708
|
+
result.alerts.push({
|
|
709
|
+
severity: f.data.includes('[!!]') ? 'HIGH' : 'MEDIUM',
|
|
710
|
+
message: `${f.check}: ${f.data.split('\n').find(l => l.includes('[!]')) || f.data.substring(0, 200)}`
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Summary
|
|
717
|
+
result.summary = {
|
|
718
|
+
totalChecks: result.findings.length,
|
|
719
|
+
alerts: result.alerts.length,
|
|
720
|
+
highSeverity: result.alerts.filter(a => a.severity === 'HIGH').length,
|
|
721
|
+
mediumSeverity: result.alerts.filter(a => a.severity === 'MEDIUM').length,
|
|
722
|
+
uniqueExternalIPs: allIPs.size,
|
|
723
|
+
suspiciousGeoIPs: result.alerts.filter(a => a.ip).length,
|
|
724
|
+
duration: Date.now() - startTime
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
result.verdict = result.summary.highSeverity > 0
|
|
728
|
+
? 'INVESTIGATE - High severity alerts found'
|
|
729
|
+
: result.summary.mediumSeverity > 0
|
|
730
|
+
? 'REVIEW - Medium severity items found'
|
|
731
|
+
: 'CLEAN - No suspicious findings';
|
|
732
|
+
|
|
733
|
+
return result;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
module.exports = { runAudit, windowsChecks, linuxChecks, macChecks, batchGeoIP, geoIP };
|
package/lib/subagent.js
CHANGED
|
@@ -16,12 +16,18 @@ const http = require('http');
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
18
|
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
// User-configured servers loaded from ~/.50c/servers.json
|
|
20
|
+
// Run: 50c servers add <alias> <host> <user> to configure
|
|
21
|
+
function loadUserServers() {
|
|
22
|
+
try {
|
|
23
|
+
const serversPath = path.join(require('os').homedir(), '.50c', 'servers.json');
|
|
24
|
+
if (fs.existsSync(serversPath)) {
|
|
25
|
+
return JSON.parse(fs.readFileSync(serversPath, 'utf8'));
|
|
26
|
+
}
|
|
27
|
+
} catch (e) {}
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
const KNOWN_SERVERS = loadUserServers();
|
|
25
31
|
|
|
26
32
|
/**
|
|
27
33
|
* HTTP/HTTPS fetch - no shell, pure Node
|
|
@@ -43,7 +49,7 @@ async function httpFetch(url, options = {}) {
|
|
|
43
49
|
...(bodyData ? { 'Content-Length': Buffer.byteLength(bodyData) } : {})
|
|
44
50
|
},
|
|
45
51
|
timeout: options.timeout || 30000,
|
|
46
|
-
|
|
52
|
+
// TLS certificate validation enabled for security
|
|
47
53
|
};
|
|
48
54
|
|
|
49
55
|
const req = lib.request(reqOptions, (res) => {
|
package/lib/tools-registry.js
CHANGED
|
@@ -76,6 +76,45 @@ const FALLBACK_TOOLS = [
|
|
|
76
76
|
{ slug: 'magnum_number_theory', name: 'magnum_number_theory', description: 'Number theory specialist with mandatory numerical verification. Fixed hallucination problem.', price: 0.35, tier: 'enterprise', category: 'math_verification' },
|
|
77
77
|
{ slug: 'magnum_find_theorem', name: 'magnum_find_theorem', description: 'Find relevant theorems and literature with citations.', price: 0.30, tier: 'enterprise', category: 'math_verification' },
|
|
78
78
|
{ slug: 'magnum_test_conjecture', name: 'magnum_test_conjecture', description: 'Adversarial conjecture testing. Finds counterexamples or estimates plausibility.', price: 0.35, tier: 'enterprise', category: 'math_verification' },
|
|
79
|
+
|
|
80
|
+
// Titan Deep Solvers (Foundation/Enterprise)
|
|
81
|
+
{ slug: 'titan_1', name: 'titan_1', description: 'Deep orchestrated solver. 7 phases: Diverge, Ground-truth, Explore, Attack, Resolve, Repair, Verify.', price: 10.00, tier: 'enterprise', category: 'deep_ai' },
|
|
82
|
+
{ slug: 'titan_2', name: 'titan_2', description: 'Compute-anchored deep solver. Ground-truth, Analysis, Synthesis, Scoring.', price: 5.00, tier: 'enterprise', category: 'deep_ai' },
|
|
83
|
+
{ slug: 'titan_3', name: 'titan_3', description: 'Proof-audited deep solver. 6 phases: understand, construct, verify, audit, fix, synthesize.', price: 10.00, tier: 'enterprise', category: 'deep_ai' },
|
|
84
|
+
{ slug: 'titan_4', name: 'titan_4', description: 'Compute-before-assert deep solver. 5 phases: blast, verify, fix, synthesize, claim_audit.', price: 2.00, tier: 'enterprise', category: 'deep_ai' },
|
|
85
|
+
|
|
86
|
+
// Clarity Mode
|
|
87
|
+
{ slug: 'clarity', name: 'clarity', description: 'Clarity Mode: tools self-activate based on input relevance. Modes: on/off/auto.', price: 0, tier: 'free', category: 'deep_ai' },
|
|
88
|
+
|
|
89
|
+
// Skeptic POV - adversarial verification
|
|
90
|
+
{ slug: 'skeptic_pov', name: 'skeptic_pov', description: '5-lens adversarial verification (Factual, Logical, Computational, Constructive, STEM).', price: 0, tier: 'free', category: 'deep_ai' },
|
|
91
|
+
|
|
92
|
+
// Solve - iterative deep solver
|
|
93
|
+
{ slug: 'solve', name: 'solve', description: 'Deep iterative solver. Researches, audits, verifies computationally, and self-corrects.', price: 0, tier: 'free', category: 'deep_ai' },
|
|
94
|
+
|
|
95
|
+
// Justice - ethical reasoning
|
|
96
|
+
{ slug: 'justice', name: 'justice', description: 'Multi-framework ethical reasoning engine. 5+ frameworks with internal objections.', price: 0.10, tier: 'pro', category: 'deep_ai' },
|
|
97
|
+
|
|
98
|
+
// Artist Mode - creative construction
|
|
99
|
+
{ slug: 'artist_mode', name: 'artist_mode', description: 'Divergent creative construction engine. Novel artifacts under constraints.', price: 0.10, tier: 'pro', category: 'deep_ai' },
|
|
100
|
+
|
|
101
|
+
// Self Modeling - epistemic analysis
|
|
102
|
+
{ slug: 'self_modeling', name: 'self_modeling', description: 'Epistemic self-analysis. Calibrates confidence, distinguishes retrieval vs computation.', price: 0.08, tier: 'pro', category: 'deep_ai' },
|
|
103
|
+
|
|
104
|
+
// Magnum Advanced Tools
|
|
105
|
+
{ slug: 'magnum_dependency_dag', name: 'magnum_dependency_dag', description: 'Build dependency DAG for proofs. Track theorem/axiom dependencies, find circular refs.', price: 0.25, tier: 'enterprise', category: 'math_verification' },
|
|
106
|
+
{ slug: 'magnum_symbolic_computational_hybrid', name: 'magnum_symbolic_computational_hybrid', description: 'Hybrid solver bridging symbolic and computational verification.', price: 0.40, tier: 'enterprise', category: 'math_verification' },
|
|
107
|
+
{ slug: 'mcp_conductor', name: 'mcp_conductor', description: 'Meta-orchestrator ensuring Magnum tools consistency. Resolves conflicts.', price: 0.50, tier: 'enterprise', category: 'math_verification' },
|
|
108
|
+
|
|
109
|
+
// Caz dedup
|
|
110
|
+
{ slug: 'caz_dedup', name: 'caz_dedup', description: 'Semantic deduplication of items.', price: 0.03, tier: 'starter', category: 'context' },
|
|
111
|
+
|
|
112
|
+
// SEO Tools
|
|
113
|
+
{ slug: 'seo_audit', name: 'seo_audit', description: 'Quick SEO audit of a URL.', price: 0.10, tier: 'pro', category: 'web_seo' },
|
|
114
|
+
{ slug: 'traffic_optimizer', name: 'traffic_optimizer', description: 'Traffic optimization strategies.', price: 0.10, tier: 'pro', category: 'web_seo' },
|
|
115
|
+
{ slug: 'schema_generator', name: 'schema_generator', description: 'Generate JSON-LD structured data.', price: 0.05, tier: 'pro', category: 'web_seo' },
|
|
116
|
+
{ slug: 'faq_generator', name: 'faq_generator', description: 'Generate FAQ content with schema.', price: 0.05, tier: 'pro', category: 'web_seo' },
|
|
117
|
+
|
|
79
118
|
{ slug: 'magnum_design_computation', name: 'magnum_design_computation', description: 'Design computational verification experiments with runnable code.', price: 0.30, tier: 'enterprise', category: 'math_verification' },
|
|
80
119
|
];
|
|
81
120
|
|
package/package.json
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "50c",
|
|
3
|
-
"version": "3.9.
|
|
4
|
-
"description": "AI developer tools via MCP. Pay-per-use from $0.01. No subscriptions.",
|
|
5
|
-
"bin": {
|
|
6
|
-
"50c": "./bin/50c.js"
|
|
7
|
-
},
|
|
8
|
-
"scripts": {
|
|
9
|
-
"postinstall": "node -e \"console.log('\\n50c Hub installed. Run: 50c install\\n')\""
|
|
10
|
-
},
|
|
11
|
-
"keywords": [
|
|
12
|
-
"mcp",
|
|
13
|
-
"ai",
|
|
14
|
-
"llm",
|
|
15
|
-
"cli",
|
|
16
|
-
"agent",
|
|
17
|
-
"50c",
|
|
18
|
-
"hints",
|
|
19
|
-
"genius",
|
|
20
|
-
"beacon",
|
|
21
|
-
"developer-tools",
|
|
22
|
-
"context",
|
|
23
|
-
"compression",
|
|
24
|
-
"caz",
|
|
25
|
-
"file-memory"
|
|
26
|
-
],
|
|
27
|
-
"author": "genxis",
|
|
28
|
-
"license": "SEE LICENSE IN LICENSE",
|
|
29
|
-
"homepage": "https://50c.ai",
|
|
30
|
-
"engines": {
|
|
31
|
-
"node": ">=18.0.0"
|
|
32
|
-
},
|
|
33
|
-
"files": [
|
|
34
|
-
"bin/",
|
|
35
|
-
"lib/",
|
|
36
|
-
"README.md",
|
|
37
|
-
"LICENSE"
|
|
38
|
-
]
|
|
39
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "50c",
|
|
3
|
+
"version": "3.9.3",
|
|
4
|
+
"description": "AI developer tools via MCP. Pay-per-use from $0.01. No subscriptions.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"50c": "./bin/50c.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node -e \"console.log('\\n50c Hub installed. Run: 50c install\\n')\""
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"mcp",
|
|
13
|
+
"ai",
|
|
14
|
+
"llm",
|
|
15
|
+
"cli",
|
|
16
|
+
"agent",
|
|
17
|
+
"50c",
|
|
18
|
+
"hints",
|
|
19
|
+
"genius",
|
|
20
|
+
"beacon",
|
|
21
|
+
"developer-tools",
|
|
22
|
+
"context",
|
|
23
|
+
"compression",
|
|
24
|
+
"caz",
|
|
25
|
+
"file-memory"
|
|
26
|
+
],
|
|
27
|
+
"author": "genxis",
|
|
28
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
29
|
+
"homepage": "https://50c.ai",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin/",
|
|
35
|
+
"lib/",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
]
|
|
39
|
+
}
|