securenow 5.3.0 → 5.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/apps.js +4 -2
- package/cli/client.js +3 -1
- package/cli/monitor.js +79 -49
- package/cli/security.js +137 -86
- package/cli.js +4 -6
- package/package.json +1 -1
package/cli/apps.js
CHANGED
|
@@ -9,7 +9,8 @@ async function list(args, flags) {
|
|
|
9
9
|
const s = ui.spinner('Fetching applications');
|
|
10
10
|
|
|
11
11
|
try {
|
|
12
|
-
const
|
|
12
|
+
const data = await api.get('/applications');
|
|
13
|
+
const apps = data.applications || [];
|
|
13
14
|
s.stop(`Found ${apps.length} application${apps.length !== 1 ? 's' : ''}`);
|
|
14
15
|
console.log('');
|
|
15
16
|
|
|
@@ -101,7 +102,8 @@ async function info(args, flags) {
|
|
|
101
102
|
|
|
102
103
|
const s = ui.spinner('Fetching application details');
|
|
103
104
|
try {
|
|
104
|
-
const
|
|
105
|
+
const data = await api.get(`/applications/${id}`);
|
|
106
|
+
const app = data.application || data;
|
|
105
107
|
s.stop('Application details loaded');
|
|
106
108
|
|
|
107
109
|
if (flags.json) {
|
package/cli/client.js
CHANGED
|
@@ -60,7 +60,9 @@ function request(method, endpoint, { body, query, token, raw } = {}) {
|
|
|
60
60
|
}
|
|
61
61
|
if (res.statusCode >= 400) {
|
|
62
62
|
const msg = parsed?.error || parsed?.message || `Request failed (HTTP ${res.statusCode})`;
|
|
63
|
-
|
|
63
|
+
const details = parsed?.details || parsed?.unauthorizedKeys;
|
|
64
|
+
const err = new CLIError(details ? `${msg} — ${details}` : msg, res.statusCode);
|
|
65
|
+
reject(err);
|
|
64
66
|
return;
|
|
65
67
|
}
|
|
66
68
|
|
package/cli/monitor.js
CHANGED
|
@@ -21,14 +21,14 @@ async function tracesList(args, flags) {
|
|
|
21
21
|
const s = ui.spinner('Fetching traces');
|
|
22
22
|
try {
|
|
23
23
|
const query = {
|
|
24
|
-
|
|
24
|
+
appKeys: appKey,
|
|
25
25
|
limit: flags.limit || 20,
|
|
26
26
|
};
|
|
27
|
-
if (flags.start) query.
|
|
28
|
-
if (flags.end) query.
|
|
27
|
+
if (flags.start) query.from = flags.start;
|
|
28
|
+
if (flags.end) query.to = flags.end;
|
|
29
29
|
|
|
30
|
-
const data = await api.get('/traces
|
|
31
|
-
const traces =
|
|
30
|
+
const data = await api.get('/traces', { query });
|
|
31
|
+
const traces = data.traces || [];
|
|
32
32
|
s.stop(`Found ${traces.length} trace${traces.length !== 1 ? 's' : ''}`);
|
|
33
33
|
|
|
34
34
|
if (flags.json) { ui.json(traces); return; }
|
|
@@ -37,7 +37,7 @@ async function tracesList(args, flags) {
|
|
|
37
37
|
const rows = traces.map(t => [
|
|
38
38
|
ui.c.dim(ui.truncate(t.traceID || t.traceId || t._id, 16)),
|
|
39
39
|
t.operationName || t.name || t.serviceName || '—',
|
|
40
|
-
ui.httpStatusColor(t.statusCode || t.httpStatusCode || '—'),
|
|
40
|
+
ui.httpStatusColor(t.statusCode || t.httpStatusCode || t.responseStatusCode || '—'),
|
|
41
41
|
ui.durationColor(t.durationNano ? t.durationNano / 1e6 : t.duration),
|
|
42
42
|
t.httpMethod || t.method || '—',
|
|
43
43
|
ui.truncate(t.httpUrl || t.url || t.httpRoute || '', 40),
|
|
@@ -62,37 +62,31 @@ async function tracesShow(args, flags) {
|
|
|
62
62
|
|
|
63
63
|
const s = ui.spinner('Fetching trace details');
|
|
64
64
|
try {
|
|
65
|
-
const
|
|
65
|
+
const appKey = resolveApp(flags);
|
|
66
|
+
const traceQuery = appKey ? { appKeys: appKey } : {};
|
|
67
|
+
const data = await api.get(`/traces/${traceId}`, { query: traceQuery });
|
|
66
68
|
s.stop('Trace loaded');
|
|
67
69
|
|
|
68
70
|
if (flags.json) { ui.json(data); return; }
|
|
69
71
|
|
|
70
|
-
const
|
|
71
|
-
console.log('');
|
|
72
|
-
ui.heading(`Trace ${traceId}`);
|
|
72
|
+
const spans = data.spans || [];
|
|
73
73
|
console.log('');
|
|
74
|
+
ui.heading(`Trace ${data.traceId || traceId}`);
|
|
74
75
|
|
|
75
|
-
if (
|
|
76
|
-
ui.subheading(`Spans (${
|
|
76
|
+
if (spans.length) {
|
|
77
|
+
ui.subheading(`Spans (${spans.length})`);
|
|
77
78
|
console.log('');
|
|
78
|
-
const rows =
|
|
79
|
+
const rows = spans.map(span => [
|
|
79
80
|
ui.c.dim(ui.truncate(span.spanID || span.spanId, 16)),
|
|
80
81
|
span.operationName || span.name || '—',
|
|
81
|
-
ui.httpStatusColor(span.statusCode || '—'),
|
|
82
|
+
ui.httpStatusColor(span.statusCode || span.responseStatusCode || '—'),
|
|
82
83
|
ui.durationColor(span.durationNano ? span.durationNano / 1e6 : span.duration),
|
|
83
84
|
span.kind || '—',
|
|
84
85
|
]);
|
|
85
86
|
ui.table(['Span ID', 'Operation', 'Status', 'Duration', 'Kind'], rows);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (trace.rootSpan || trace.serviceName) {
|
|
87
|
+
} else {
|
|
89
88
|
console.log('');
|
|
90
|
-
ui.
|
|
91
|
-
['Service', trace.serviceName || '—'],
|
|
92
|
-
['Root Operation', trace.rootOperationName || trace.rootSpan?.operationName || '—'],
|
|
93
|
-
['Duration', trace.durationMs ? `${trace.durationMs}ms` : '—'],
|
|
94
|
-
['Timestamp', trace.startTime ? new Date(trace.startTime).toLocaleString() : '—'],
|
|
95
|
-
]);
|
|
89
|
+
ui.info('No spans found for this trace.');
|
|
96
90
|
}
|
|
97
91
|
console.log('');
|
|
98
92
|
} catch (err) {
|
|
@@ -116,11 +110,39 @@ async function tracesAnalyze(args, flags) {
|
|
|
116
110
|
|
|
117
111
|
if (flags.json) { ui.json(result); return; }
|
|
118
112
|
|
|
113
|
+
const analysis = result.analysis;
|
|
119
114
|
console.log('');
|
|
120
115
|
ui.heading('AI Trace Analysis');
|
|
121
116
|
console.log('');
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
|
|
118
|
+
if (typeof analysis === 'object' && analysis !== null) {
|
|
119
|
+
if (analysis.summary) {
|
|
120
|
+
ui.subheading('Summary');
|
|
121
|
+
console.log(`\n ${analysis.summary}\n`);
|
|
122
|
+
}
|
|
123
|
+
if (analysis.riskLevel) {
|
|
124
|
+
console.log(` ${ui.c.bold('Risk Level:')} ${ui.statusBadge(analysis.riskLevel)}\n`);
|
|
125
|
+
}
|
|
126
|
+
if (analysis.securityIssues?.length) {
|
|
127
|
+
ui.subheading('Security Issues');
|
|
128
|
+
console.log('');
|
|
129
|
+
analysis.securityIssues.forEach((issue, i) => {
|
|
130
|
+
console.log(` ${i + 1}. ${typeof issue === 'string' ? issue : issue.description || JSON.stringify(issue)}`);
|
|
131
|
+
});
|
|
132
|
+
console.log('');
|
|
133
|
+
}
|
|
134
|
+
if (analysis.recommendations?.length) {
|
|
135
|
+
ui.subheading('Recommendations');
|
|
136
|
+
console.log('');
|
|
137
|
+
analysis.recommendations.forEach((rec, i) => {
|
|
138
|
+
console.log(` ${i + 1}. ${typeof rec === 'string' ? rec : rec.description || JSON.stringify(rec)}`);
|
|
139
|
+
});
|
|
140
|
+
console.log('');
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
console.log(analysis || JSON.stringify(result, null, 2));
|
|
144
|
+
console.log('');
|
|
145
|
+
}
|
|
124
146
|
} catch (err) {
|
|
125
147
|
s.fail('Analysis failed');
|
|
126
148
|
throw err;
|
|
@@ -142,15 +164,15 @@ async function logsList(args, flags) {
|
|
|
142
164
|
const minutes = parseInt(flags.minutes || '60', 10);
|
|
143
165
|
const now = Date.now();
|
|
144
166
|
const query = {
|
|
145
|
-
|
|
167
|
+
appKeys: appKey,
|
|
146
168
|
limit: flags.limit || 50,
|
|
147
|
-
|
|
148
|
-
|
|
169
|
+
from: flags.start || new Date(now - minutes * 60 * 1000).toISOString(),
|
|
170
|
+
to: flags.end || new Date(now).toISOString(),
|
|
149
171
|
};
|
|
150
|
-
if (flags.level) query.
|
|
172
|
+
if (flags.level) query.severity = flags.level;
|
|
151
173
|
|
|
152
174
|
const data = await api.get('/logs', { query });
|
|
153
|
-
const logs =
|
|
175
|
+
const logs = data.logs || [];
|
|
154
176
|
s.stop(`Found ${logs.length} log${logs.length !== 1 ? 's' : ''}`);
|
|
155
177
|
|
|
156
178
|
if (flags.json) { ui.json(logs); return; }
|
|
@@ -190,7 +212,7 @@ async function logsTrace(args, flags) {
|
|
|
190
212
|
const s = ui.spinner('Fetching logs for trace');
|
|
191
213
|
try {
|
|
192
214
|
const data = await api.get(`/logs/trace/${traceId}`);
|
|
193
|
-
const logs =
|
|
215
|
+
const logs = data.logs || [];
|
|
194
216
|
s.stop(`Found ${logs.length} log${logs.length !== 1 ? 's' : ''}`);
|
|
195
217
|
|
|
196
218
|
if (flags.json) { ui.json(logs); return; }
|
|
@@ -225,10 +247,10 @@ async function issuesList(args, flags) {
|
|
|
225
247
|
if (flags.status) query.status = flags.status;
|
|
226
248
|
|
|
227
249
|
const data = await api.get('/issues', { query });
|
|
228
|
-
const issues =
|
|
250
|
+
const issues = data.issues || [];
|
|
229
251
|
s.stop(`Found ${issues.length} issue${issues.length !== 1 ? 's' : ''}`);
|
|
230
252
|
|
|
231
|
-
if (flags.json) { ui.json(
|
|
253
|
+
if (flags.json) { ui.json(data); return; }
|
|
232
254
|
|
|
233
255
|
console.log('');
|
|
234
256
|
const rows = issues.map(i => [
|
|
@@ -242,6 +264,9 @@ async function issuesList(args, flags) {
|
|
|
242
264
|
]);
|
|
243
265
|
|
|
244
266
|
ui.table(['ID', 'Severity', 'Status', 'Title', 'App', 'Count', 'Last Seen'], rows);
|
|
267
|
+
if (data.total != null) {
|
|
268
|
+
console.log(ui.c.dim(` Total: ${data.total}`));
|
|
269
|
+
}
|
|
245
270
|
console.log('');
|
|
246
271
|
} catch (err) {
|
|
247
272
|
s.fail('Failed to fetch issues');
|
|
@@ -259,7 +284,8 @@ async function issuesShow(args, flags) {
|
|
|
259
284
|
|
|
260
285
|
const s = ui.spinner('Fetching issue');
|
|
261
286
|
try {
|
|
262
|
-
const
|
|
287
|
+
const data = await api.get(`/issues/${id}`);
|
|
288
|
+
const issue = data.issue || data;
|
|
263
289
|
s.stop('Issue loaded');
|
|
264
290
|
|
|
265
291
|
if (flags.json) { ui.json(issue); return; }
|
|
@@ -320,8 +346,9 @@ async function notificationsList(args, flags) {
|
|
|
320
346
|
try {
|
|
321
347
|
const query = { limit: flags.limit || 20, page: flags.page || 1 };
|
|
322
348
|
const data = await api.get('/notifications', { query });
|
|
323
|
-
const notifications =
|
|
324
|
-
|
|
349
|
+
const notifications = data.notifications || [];
|
|
350
|
+
const pagination = data.pagination;
|
|
351
|
+
s.stop(`Found ${notifications.length} notification${notifications.length !== 1 ? 's' : ''}${pagination ? ` (page ${pagination.page}/${pagination.totalPages})` : ''}`);
|
|
325
352
|
|
|
326
353
|
if (flags.json) { ui.json(data); return; }
|
|
327
354
|
|
|
@@ -353,7 +380,7 @@ async function notificationsRead(args, flags) {
|
|
|
353
380
|
|
|
354
381
|
const s = ui.spinner('Marking as read');
|
|
355
382
|
try {
|
|
356
|
-
await api.put(`/notifications/${id}
|
|
383
|
+
await api.put(`/notifications/${id}/read`);
|
|
357
384
|
s.stop('Notification marked as read');
|
|
358
385
|
} catch (err) {
|
|
359
386
|
s.fail('Failed to mark notification');
|
|
@@ -377,7 +404,7 @@ async function notificationsUnread() {
|
|
|
377
404
|
requireAuth();
|
|
378
405
|
try {
|
|
379
406
|
const data = await api.get('/notifications/unread-count');
|
|
380
|
-
const count = data.count ??
|
|
407
|
+
const count = data.count ?? 0;
|
|
381
408
|
console.log(`\n ${ui.c.bold(String(count))} unread notification${count !== 1 ? 's' : ''}\n`);
|
|
382
409
|
} catch (err) {
|
|
383
410
|
throw err;
|
|
@@ -390,11 +417,12 @@ async function status(args, flags) {
|
|
|
390
417
|
requireAuth();
|
|
391
418
|
const s = ui.spinner('Fetching dashboard overview');
|
|
392
419
|
try {
|
|
393
|
-
const [
|
|
420
|
+
const [appsData, unreadData] = await Promise.all([
|
|
394
421
|
api.get('/applications'),
|
|
395
422
|
api.get('/notifications/unread-count').catch(() => ({ count: 0 })),
|
|
396
423
|
]);
|
|
397
424
|
|
|
425
|
+
const apps = appsData.applications || [];
|
|
398
426
|
s.stop('Dashboard loaded');
|
|
399
427
|
|
|
400
428
|
console.log('');
|
|
@@ -403,7 +431,7 @@ async function status(args, flags) {
|
|
|
403
431
|
|
|
404
432
|
ui.keyValue([
|
|
405
433
|
['Applications', String(apps.length)],
|
|
406
|
-
['Unread Alerts', String(
|
|
434
|
+
['Unread Alerts', String(unreadData.count ?? 0)],
|
|
407
435
|
]);
|
|
408
436
|
|
|
409
437
|
if (apps.length > 0) {
|
|
@@ -420,16 +448,18 @@ async function status(args, flags) {
|
|
|
420
448
|
const appKey = resolveApp(flags);
|
|
421
449
|
if (appKey) {
|
|
422
450
|
try {
|
|
423
|
-
const protectionData = await api.get('/applications/protection-status'
|
|
424
|
-
|
|
425
|
-
|
|
451
|
+
const protectionData = await api.get('/applications/protection-status');
|
|
452
|
+
const statuses = protectionData.statuses;
|
|
453
|
+
if (statuses && Object.keys(statuses).length > 0) {
|
|
454
|
+
ui.subheading('Protection Status');
|
|
426
455
|
console.log('');
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
ui.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
456
|
+
const rows = Object.entries(statuses).map(([id, s]) => [
|
|
457
|
+
ui.c.dim(ui.truncate(id, 12)),
|
|
458
|
+
s.protected ? ui.c.green('● protected') : ui.c.red('○ unprotected'),
|
|
459
|
+
String(s.traceCount || 0),
|
|
460
|
+
s.lastTrace ? ui.timeAgo(s.lastTrace) : ui.c.dim('—'),
|
|
461
|
+
]);
|
|
462
|
+
ui.table(['App ID', 'Status', 'Traces (15m)', 'Last Trace'], rows);
|
|
433
463
|
}
|
|
434
464
|
} catch {}
|
|
435
465
|
}
|
package/cli/security.js
CHANGED
|
@@ -15,7 +15,7 @@ async function alertRulesList(args, flags) {
|
|
|
15
15
|
const s = ui.spinner('Fetching alert rules');
|
|
16
16
|
try {
|
|
17
17
|
const data = await api.get('/alert-rules');
|
|
18
|
-
const rules =
|
|
18
|
+
const rules = data.alertRules || [];
|
|
19
19
|
s.stop(`Found ${rules.length} rule${rules.length !== 1 ? 's' : ''}`);
|
|
20
20
|
|
|
21
21
|
if (flags.json) { ui.json(rules); return; }
|
|
@@ -44,7 +44,7 @@ async function alertChannelsList(args, flags) {
|
|
|
44
44
|
const s = ui.spinner('Fetching alert channels');
|
|
45
45
|
try {
|
|
46
46
|
const data = await api.get('/alert-channels');
|
|
47
|
-
const channels =
|
|
47
|
+
const channels = data.alertChannels || [];
|
|
48
48
|
s.stop(`Found ${channels.length} channel${channels.length !== 1 ? 's' : ''}`);
|
|
49
49
|
|
|
50
50
|
if (flags.json) { ui.json(channels); return; }
|
|
@@ -73,10 +73,10 @@ async function alertHistoryList(args, flags) {
|
|
|
73
73
|
try {
|
|
74
74
|
const query = { limit: flags.limit || 20 };
|
|
75
75
|
const data = await api.get('/alert-history', { query });
|
|
76
|
-
const history =
|
|
77
|
-
s.stop(`Found ${history.length} alert${history.length !== 1 ? 's' : ''}`);
|
|
76
|
+
const history = data.alerts || [];
|
|
77
|
+
s.stop(`Found ${history.length} alert${history.length !== 1 ? 's' : ''}${data.totalItems ? ` (${data.totalItems} total)` : ''}`);
|
|
78
78
|
|
|
79
|
-
if (flags.json) { ui.json(
|
|
79
|
+
if (flags.json) { ui.json(data); return; }
|
|
80
80
|
|
|
81
81
|
console.log('');
|
|
82
82
|
const rows = history.map(h => [
|
|
@@ -102,10 +102,10 @@ async function blocklistList(args, flags) {
|
|
|
102
102
|
const s = ui.spinner('Fetching blocklist');
|
|
103
103
|
try {
|
|
104
104
|
const data = await api.get('/blocklist');
|
|
105
|
-
const items =
|
|
106
|
-
s.stop(`Found ${items.length} blocked IP${items.length !== 1 ? 's' : ''}`);
|
|
105
|
+
const items = data.blockedIps || [];
|
|
106
|
+
s.stop(`Found ${items.length} blocked IP${items.length !== 1 ? 's' : ''}${data.total ? ` (${data.total} total)` : ''}`);
|
|
107
107
|
|
|
108
|
-
if (flags.json) { ui.json(
|
|
108
|
+
if (flags.json) { ui.json(data); return; }
|
|
109
109
|
|
|
110
110
|
console.log('');
|
|
111
111
|
const rows = items.map(b => [
|
|
@@ -174,7 +174,8 @@ async function blocklistStats(args, flags) {
|
|
|
174
174
|
requireAuth();
|
|
175
175
|
const s = ui.spinner('Fetching blocklist stats');
|
|
176
176
|
try {
|
|
177
|
-
const
|
|
177
|
+
const data = await api.get('/blocklist/stats');
|
|
178
|
+
const stats = data.stats || data;
|
|
178
179
|
s.stop('Stats loaded');
|
|
179
180
|
|
|
180
181
|
if (flags.json) { ui.json(stats); return; }
|
|
@@ -182,8 +183,13 @@ async function blocklistStats(args, flags) {
|
|
|
182
183
|
console.log('');
|
|
183
184
|
ui.heading('Blocklist Statistics');
|
|
184
185
|
console.log('');
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
ui.keyValue([
|
|
187
|
+
['Total Active', String(stats.totalActive ?? '—')],
|
|
188
|
+
['Total Removed', String(stats.totalRemoved ?? '—')],
|
|
189
|
+
['Manual Blocks', String(stats.manualCount ?? '—')],
|
|
190
|
+
['Automation Blocks', String(stats.automationCount ?? '—')],
|
|
191
|
+
['Active Rules', String(stats.activeAutomationRules ?? '—')],
|
|
192
|
+
]);
|
|
187
193
|
console.log('');
|
|
188
194
|
} catch (err) {
|
|
189
195
|
s.fail('Failed to fetch stats');
|
|
@@ -198,7 +204,7 @@ async function trustedList(args, flags) {
|
|
|
198
204
|
const s = ui.spinner('Fetching trusted IPs');
|
|
199
205
|
try {
|
|
200
206
|
const data = await api.get('/trusted-ips');
|
|
201
|
-
const items =
|
|
207
|
+
const items = data.trustedIps || [];
|
|
202
208
|
s.stop(`Found ${items.length} trusted IP${items.length !== 1 ? 's' : ''}`);
|
|
203
209
|
|
|
204
210
|
if (flags.json) { ui.json(items); return; }
|
|
@@ -278,17 +284,40 @@ async function forensicsQuery(args, flags) {
|
|
|
278
284
|
const body = { query };
|
|
279
285
|
if (flags.instance) body.instanceId = flags.instance;
|
|
280
286
|
|
|
281
|
-
const job = await api.post('/forensics/
|
|
282
|
-
const jobId = job.jobId
|
|
287
|
+
const job = await api.post('/forensics/query', body);
|
|
288
|
+
const jobId = job.jobId;
|
|
289
|
+
|
|
290
|
+
if (!jobId) {
|
|
291
|
+
s.stop('Query complete');
|
|
292
|
+
if (flags.json) { ui.json(job); return; }
|
|
293
|
+
if (job.result) {
|
|
294
|
+
console.log('');
|
|
295
|
+
if (job.sqlquery) {
|
|
296
|
+
ui.subheading('Generated SQL');
|
|
297
|
+
console.log(`\n ${ui.c.dim(job.sqlquery)}\n`);
|
|
298
|
+
}
|
|
299
|
+
const data = job.result;
|
|
300
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
301
|
+
const headers = Object.keys(data[0]);
|
|
302
|
+
const rows = data.map(row => headers.map(h => String(row[h] ?? '')));
|
|
303
|
+
ui.table(headers, rows);
|
|
304
|
+
} else {
|
|
305
|
+
ui.json(data);
|
|
306
|
+
}
|
|
307
|
+
console.log('');
|
|
308
|
+
}
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
283
312
|
s.update('Processing query...');
|
|
284
313
|
|
|
285
314
|
let result;
|
|
286
315
|
const maxAttempts = 60;
|
|
287
316
|
for (let i = 0; i < maxAttempts; i++) {
|
|
288
317
|
await new Promise(r => setTimeout(r, 2000));
|
|
289
|
-
result = await api.get(`/forensics/
|
|
318
|
+
result = await api.get(`/forensics/query/status/${jobId}`);
|
|
290
319
|
if (result.status === 'completed' || result.status === 'failed') break;
|
|
291
|
-
s.update(`Processing query... (${i * 2}s)`);
|
|
320
|
+
s.update(`Processing query... (${(i + 1) * 2}s)`);
|
|
292
321
|
}
|
|
293
322
|
|
|
294
323
|
if (result.status === 'failed') {
|
|
@@ -308,14 +337,14 @@ async function forensicsQuery(args, flags) {
|
|
|
308
337
|
if (flags.json) { ui.json(result); return; }
|
|
309
338
|
|
|
310
339
|
console.log('');
|
|
311
|
-
if (result.
|
|
340
|
+
if (result.sqlquery) {
|
|
312
341
|
ui.subheading('Generated SQL');
|
|
313
|
-
console.log(`\n ${ui.c.dim(result.
|
|
342
|
+
console.log(`\n ${ui.c.dim(result.sqlquery)}\n`);
|
|
314
343
|
}
|
|
315
344
|
|
|
316
|
-
if (result.
|
|
317
|
-
const data = result.
|
|
318
|
-
ui.subheading(`Results (${Array.isArray(data) ? data.length : '?'} rows)`);
|
|
345
|
+
if (result.result) {
|
|
346
|
+
const data = result.result;
|
|
347
|
+
ui.subheading(`Results (${result.rowCount ?? (Array.isArray(data) ? data.length : '?')} rows)`);
|
|
319
348
|
console.log('');
|
|
320
349
|
|
|
321
350
|
if (Array.isArray(data) && data.length > 0) {
|
|
@@ -326,11 +355,6 @@ async function forensicsQuery(args, flags) {
|
|
|
326
355
|
ui.json(data);
|
|
327
356
|
}
|
|
328
357
|
}
|
|
329
|
-
|
|
330
|
-
if (result.explanation) {
|
|
331
|
-
ui.subheading('Explanation');
|
|
332
|
-
console.log(`\n ${result.explanation}\n`);
|
|
333
|
-
}
|
|
334
358
|
console.log('');
|
|
335
359
|
} catch (err) {
|
|
336
360
|
s.fail('Forensic query failed');
|
|
@@ -343,7 +367,7 @@ async function forensicsLibrary(args, flags) {
|
|
|
343
367
|
const s = ui.spinner('Fetching query library');
|
|
344
368
|
try {
|
|
345
369
|
const data = await api.get('/forensics/query-library');
|
|
346
|
-
const queries =
|
|
370
|
+
const queries = data.data || [];
|
|
347
371
|
s.stop(`Found ${queries.length} saved quer${queries.length !== 1 ? 'ies' : 'y'}`);
|
|
348
372
|
|
|
349
373
|
if (flags.json) { ui.json(queries); return; }
|
|
@@ -381,46 +405,42 @@ async function ipLookup(args, flags) {
|
|
|
381
405
|
if (flags.json) { ui.json(data); return; }
|
|
382
406
|
|
|
383
407
|
console.log('');
|
|
384
|
-
ui.heading(`IP Intelligence: ${ip}`);
|
|
408
|
+
ui.heading(`IP Intelligence: ${data.ip || ip}`);
|
|
385
409
|
console.log('');
|
|
386
410
|
|
|
387
|
-
const info = data.ipData || data.intel || data;
|
|
388
411
|
const pairs = [];
|
|
389
|
-
|
|
390
|
-
if (
|
|
391
|
-
if (
|
|
392
|
-
if (
|
|
393
|
-
if (
|
|
394
|
-
if (
|
|
395
|
-
if (
|
|
396
|
-
if (
|
|
397
|
-
if (
|
|
398
|
-
if (
|
|
399
|
-
if (
|
|
400
|
-
if (
|
|
401
|
-
if (info.isCrawler != null) pairs.push(['Crawler', info.isCrawler ? ui.c.yellow('Yes') : 'No']);
|
|
402
|
-
if (info.threatLevel) pairs.push(['Threat Level', ui.statusBadge(info.threatLevel)]);
|
|
403
|
-
if (info.riskLevel) pairs.push(['Risk Level', ui.statusBadge(info.riskLevel)]);
|
|
412
|
+
if (data.countryName || data.countryCode) pairs.push(['Country', `${data.countryName || ''} ${data.countryCode ? `(${data.countryCode})` : ''}`.trim()]);
|
|
413
|
+
if (data.domain) pairs.push(['Domain', data.domain]);
|
|
414
|
+
if (data.isp) pairs.push(['ISP', data.isp]);
|
|
415
|
+
if (data.usageType) pairs.push(['Usage Type', data.usageType]);
|
|
416
|
+
if (data.abuseConfidenceScore != null) pairs.push(['Abuse Score', `${data.abuseConfidenceScore}/100`]);
|
|
417
|
+
if (data.securenowScore != null) pairs.push(['SecureNow Score', String(data.securenowScore)]);
|
|
418
|
+
if (data.verdict) pairs.push(['Verdict', data.verdict]);
|
|
419
|
+
if (data.isMalicious != null) pairs.push(['Malicious', data.isMalicious ? ui.c.red('Yes') : ui.c.green('No')]);
|
|
420
|
+
if (data.isBot != null) pairs.push(['Bot', data.isBot ? ui.c.yellow('Yes') : 'No']);
|
|
421
|
+
if (data.activityType) pairs.push(['Activity', data.activityType]);
|
|
422
|
+
if (data.totalReports != null) pairs.push(['Total Reports', String(data.totalReports)]);
|
|
423
|
+
if (data.lastReportedAt) pairs.push(['Last Reported', new Date(data.lastReportedAt).toLocaleString()]);
|
|
404
424
|
|
|
405
425
|
if (pairs.length) {
|
|
406
426
|
ui.keyValue(pairs);
|
|
407
|
-
} else {
|
|
408
|
-
ui.keyValue(Object.entries(info).slice(0, 20).map(([k, v]) => [k, String(v)]));
|
|
409
427
|
}
|
|
410
428
|
|
|
411
|
-
if (data.
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
429
|
+
if (data.riskFactors?.length) {
|
|
430
|
+
ui.subheading('Risk Factors');
|
|
431
|
+
console.log('');
|
|
432
|
+
data.riskFactors.forEach(f => console.log(` • ${f}`));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (data.attackTypes?.length) {
|
|
436
|
+
ui.subheading('Attack Types');
|
|
437
|
+
console.log('');
|
|
438
|
+
data.attackTypes.forEach(a => console.log(` • ${a}`));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (data.summary) {
|
|
442
|
+
ui.subheading('Summary');
|
|
443
|
+
console.log(`\n ${data.summary}`);
|
|
424
444
|
}
|
|
425
445
|
console.log('');
|
|
426
446
|
} catch (err) {
|
|
@@ -440,16 +460,16 @@ async function ipTraces(args, flags) {
|
|
|
440
460
|
const s = ui.spinner(`Fetching traces for ${ip}`);
|
|
441
461
|
try {
|
|
442
462
|
const data = await api.get(`/ip/${ip}/traces`);
|
|
443
|
-
const traces =
|
|
463
|
+
const traces = data.traces || [];
|
|
444
464
|
s.stop(`Found ${traces.length} trace${traces.length !== 1 ? 's' : ''}`);
|
|
445
465
|
|
|
446
|
-
if (flags.json) { ui.json(
|
|
466
|
+
if (flags.json) { ui.json(data); return; }
|
|
447
467
|
|
|
448
468
|
console.log('');
|
|
449
469
|
const rows = traces.map(t => [
|
|
450
470
|
ui.c.dim(ui.truncate(t.traceID || t.traceId, 16)),
|
|
451
471
|
t.httpMethod || t.method || '—',
|
|
452
|
-
ui.httpStatusColor(t.statusCode || t.httpStatusCode || '—'),
|
|
472
|
+
ui.httpStatusColor(t.statusCode || t.httpStatusCode || t.responseStatusCode || '—'),
|
|
453
473
|
ui.truncate(t.httpUrl || t.url || '', 40),
|
|
454
474
|
ui.durationColor(t.durationNano ? t.durationNano / 1e6 : t.duration),
|
|
455
475
|
ui.timeAgo(t.timestamp),
|
|
@@ -469,22 +489,40 @@ async function apiMapList(args, flags) {
|
|
|
469
489
|
const s = ui.spinner('Fetching API map');
|
|
470
490
|
try {
|
|
471
491
|
const data = await api.get('/api-map');
|
|
472
|
-
const
|
|
473
|
-
s.stop(
|
|
492
|
+
const apiMap = data.apiMap;
|
|
493
|
+
s.stop('API map loaded');
|
|
474
494
|
|
|
475
495
|
if (flags.json) { ui.json(data); return; }
|
|
476
496
|
|
|
497
|
+
if (!apiMap) {
|
|
498
|
+
console.log('');
|
|
499
|
+
ui.info(data.message || 'No API map discovered yet. Run discovery from the dashboard.');
|
|
500
|
+
console.log('');
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
477
504
|
console.log('');
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
505
|
+
if (apiMap.apps && typeof apiMap.apps === 'object') {
|
|
506
|
+
for (const [appName, appData] of Object.entries(apiMap.apps)) {
|
|
507
|
+
ui.subheading(appName);
|
|
508
|
+
console.log('');
|
|
509
|
+
const endpoints = appData.endpoints || [];
|
|
510
|
+
if (endpoints.length) {
|
|
511
|
+
const rows = endpoints.map(e => [
|
|
512
|
+
e.method || '—',
|
|
513
|
+
e.path || e.route || '—',
|
|
514
|
+
e.requestCount != null ? String(e.requestCount) : '—',
|
|
515
|
+
e.description || ui.c.dim('—'),
|
|
516
|
+
]);
|
|
517
|
+
ui.table(['Method', 'Path', 'Requests', 'Description'], rows);
|
|
518
|
+
} else {
|
|
519
|
+
ui.info('No endpoints discovered for this app.');
|
|
520
|
+
}
|
|
521
|
+
console.log('');
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
ui.json(apiMap);
|
|
525
|
+
}
|
|
488
526
|
} catch (err) {
|
|
489
527
|
s.fail('Failed to fetch API map');
|
|
490
528
|
throw err;
|
|
@@ -495,15 +533,30 @@ async function apiMapStats(args, flags) {
|
|
|
495
533
|
requireAuth();
|
|
496
534
|
const s = ui.spinner('Fetching API map stats');
|
|
497
535
|
try {
|
|
498
|
-
const
|
|
536
|
+
const data = await api.get('/api-map/stats');
|
|
537
|
+
const stats = data.stats;
|
|
499
538
|
s.stop('Stats loaded');
|
|
500
539
|
|
|
501
540
|
if (flags.json) { ui.json(stats); return; }
|
|
502
541
|
|
|
542
|
+
if (!stats) {
|
|
543
|
+
console.log('');
|
|
544
|
+
ui.info('No API map stats available.');
|
|
545
|
+
console.log('');
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
503
549
|
console.log('');
|
|
504
550
|
ui.heading('API Map Statistics');
|
|
505
551
|
console.log('');
|
|
506
|
-
ui.keyValue(
|
|
552
|
+
ui.keyValue([
|
|
553
|
+
['Total Apps', String(stats.totalApps ?? '—')],
|
|
554
|
+
['Total Endpoints', String(stats.totalEndpoints ?? '—')],
|
|
555
|
+
['Total Requests', String(stats.totalRequests ?? '—')],
|
|
556
|
+
['Discovery Status', stats.discoveryStatus || '—'],
|
|
557
|
+
['Last Discovered', stats.lastDiscoveredAt ? new Date(stats.lastDiscoveredAt).toLocaleString() : '—'],
|
|
558
|
+
['Version', String(stats.version ?? '—')],
|
|
559
|
+
]);
|
|
507
560
|
console.log('');
|
|
508
561
|
} catch (err) {
|
|
509
562
|
s.fail('Failed to fetch stats');
|
|
@@ -518,7 +571,7 @@ async function instancesList(args, flags) {
|
|
|
518
571
|
const s = ui.spinner('Fetching instances');
|
|
519
572
|
try {
|
|
520
573
|
const data = await api.get('/instances');
|
|
521
|
-
const instances =
|
|
574
|
+
const instances = data.instances || [];
|
|
522
575
|
s.stop(`Found ${instances.length} instance${instances.length !== 1 ? 's' : ''}`);
|
|
523
576
|
|
|
524
577
|
if (flags.json) { ui.json(instances); return; }
|
|
@@ -529,10 +582,10 @@ async function instancesList(args, flags) {
|
|
|
529
582
|
inst.name || inst.host || '—',
|
|
530
583
|
inst.host || '—',
|
|
531
584
|
inst.port != null ? String(inst.port) : '—',
|
|
532
|
-
|
|
585
|
+
inst.linkedApps != null ? String(inst.linkedApps) : '—',
|
|
533
586
|
ui.timeAgo(inst.createdAt),
|
|
534
587
|
]);
|
|
535
|
-
ui.table(['ID', 'Name', 'Host', 'Port', '
|
|
588
|
+
ui.table(['ID', 'Name', 'Host', 'Port', 'Linked Apps', 'Added'], rows);
|
|
536
589
|
console.log('');
|
|
537
590
|
} catch (err) {
|
|
538
591
|
s.fail('Failed to fetch instances');
|
|
@@ -551,11 +604,10 @@ async function instancesTest(args, flags) {
|
|
|
551
604
|
const s = ui.spinner('Testing instance connection');
|
|
552
605
|
try {
|
|
553
606
|
const result = await api.post(`/instances/${id}/test`);
|
|
554
|
-
if (result.success
|
|
555
|
-
s.stop(
|
|
607
|
+
if (result.success) {
|
|
608
|
+
s.stop(`Connection successful${result.storageGb ? ` (${result.storageGb} GB storage)` : ''}`);
|
|
556
609
|
} else {
|
|
557
|
-
s.fail('
|
|
558
|
-
if (result.error) ui.error(result.error);
|
|
610
|
+
s.fail(result.message || 'Connection failed');
|
|
559
611
|
}
|
|
560
612
|
|
|
561
613
|
if (flags.json) ui.json(result);
|
|
@@ -569,11 +621,10 @@ async function instancesTest(args, flags) {
|
|
|
569
621
|
|
|
570
622
|
async function analytics(args, flags) {
|
|
571
623
|
requireAuth();
|
|
572
|
-
const appKey = resolveApp(flags);
|
|
573
624
|
const s = ui.spinner('Fetching analytics');
|
|
574
625
|
try {
|
|
575
626
|
const query = {};
|
|
576
|
-
|
|
627
|
+
const appKey = resolveApp(flags);
|
|
577
628
|
if (flags.instance) query.instanceId = flags.instance;
|
|
578
629
|
|
|
579
630
|
const endpoints = ['2xx-responses', '3xx-responses', '4xx-responses', '5xx-responses', '500-errors'];
|
|
@@ -596,7 +647,7 @@ async function analytics(args, flags) {
|
|
|
596
647
|
|
|
597
648
|
const pairs = endpoints.map((ep, i) => {
|
|
598
649
|
const val = results[i];
|
|
599
|
-
const count = val?.count ??
|
|
650
|
+
const count = val?.meta?.count ?? '—';
|
|
600
651
|
return [ep, String(count)];
|
|
601
652
|
});
|
|
602
653
|
ui.keyValue(pairs);
|
package/cli.js
CHANGED
|
@@ -377,13 +377,11 @@ async function main() {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
main().catch((err) => {
|
|
380
|
-
if (err.name
|
|
381
|
-
ui.error(err.message);
|
|
382
|
-
} else {
|
|
380
|
+
if (err.name !== 'CLIError') {
|
|
383
381
|
ui.error(err.message || 'An unexpected error occurred');
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
382
|
+
}
|
|
383
|
+
if (process.env.SECURENOW_DEBUG) {
|
|
384
|
+
console.error(err.stack || err);
|
|
387
385
|
}
|
|
388
386
|
process.exit(1);
|
|
389
387
|
});
|
package/package.json
CHANGED