securenow 7.5.0 → 7.6.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.
- package/CONSUMING-APPS-GUIDE.md +2 -0
- package/NPM_README.md +201 -237
- package/README.md +73 -26
- package/SKILL-API.md +209 -205
- package/SKILL-CLI.md +71 -64
- package/app-config.js +479 -83
- package/cli/apiKey.js +1 -1
- package/cli/apps.js +1 -1
- package/cli/config.js +31 -12
- package/cli/credentials.js +88 -0
- package/cli/diagnostics.js +81 -98
- package/cli/firewall.js +29 -14
- package/cli/init.js +246 -201
- package/cli/monitor.js +107 -43
- package/cli/security.js +24 -12
- package/cli/ui.js +22 -12
- package/cli/utils.js +2 -1
- package/cli.js +71 -39
- package/console-instrumentation.js +1 -1
- package/docs/ENVIRONMENT-VARIABLES.md +137 -863
- package/docs/ENVIRONMENTS.md +60 -0
- package/docs/EXPRESS-SETUP-GUIDE.md +3 -0
- package/docs/FIREWALL-GUIDE.md +3 -0
- package/docs/INDEX.md +6 -8
- package/docs/LOGGING-GUIDE.md +3 -0
- package/docs/MCP-GUIDE.md +8 -0
- package/docs/NEXTJS-GUIDE.md +3 -0
- package/docs/NEXTJS-QUICKSTART.md +24 -16
- package/docs/NUXT-GUIDE.md +3 -0
- package/docs/QUICKSTART-BODY-CAPTURE.md +3 -0
- package/docs/REQUEST-BODY-CAPTURE.md +3 -0
- package/firewall-cloud.js +10 -10
- package/firewall-only.js +25 -23
- package/firewall.js +47 -29
- package/free-trial-banner.js +1 -1
- package/mcp/catalog.js +104 -17
- package/nextjs-auto-capture.d.ts +7 -4
- package/nextjs-auto-capture.js +7 -7
- package/nextjs-middleware.js +4 -3
- package/nextjs-wrapper.js +6 -6
- package/nextjs.d.ts +36 -25
- package/nextjs.js +47 -55
- package/nuxt-server-plugin.mjs +35 -51
- package/nuxt.d.ts +29 -23
- package/package.json +1 -1
- package/postinstall.js +27 -61
- package/register.d.ts +19 -33
- package/register.js +8 -8
- package/resolve-ip.js +4 -5
- package/tracing.d.ts +21 -19
- package/tracing.js +34 -42
package/cli/monitor.js
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { api, requireAuth } = require('./client');
|
|
4
|
-
const config = require('./config');
|
|
5
|
-
const ui = require('./ui');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
const { api, requireAuth } = require('./client');
|
|
4
|
+
const config = require('./config');
|
|
5
|
+
const ui = require('./ui');
|
|
6
|
+
const appConfig = require('../app-config');
|
|
7
|
+
|
|
8
|
+
function resolveApp(flags) {
|
|
9
|
+
return flags.app || config.getDefaultApp();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function resolveEnvironment(flags) {
|
|
13
|
+
return flags.env || flags.environment || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function addEnvironment(query, flags) {
|
|
17
|
+
const environment = resolveEnvironment(flags);
|
|
18
|
+
if (environment) query.environment = environment;
|
|
19
|
+
return query;
|
|
20
|
+
}
|
|
10
21
|
|
|
11
22
|
// ── Traces ──
|
|
12
23
|
|
|
@@ -20,10 +31,11 @@ async function tracesList(args, flags) {
|
|
|
20
31
|
|
|
21
32
|
const s = ui.spinner('Fetching traces');
|
|
22
33
|
try {
|
|
23
|
-
const query = {
|
|
24
|
-
appKeys: appKey,
|
|
25
|
-
limit: flags.limit || 20,
|
|
26
|
-
};
|
|
34
|
+
const query = {
|
|
35
|
+
appKeys: appKey,
|
|
36
|
+
limit: flags.limit || 20,
|
|
37
|
+
};
|
|
38
|
+
addEnvironment(query, flags);
|
|
27
39
|
if (flags.start) query.from = flags.start;
|
|
28
40
|
if (flags.end) query.to = flags.end;
|
|
29
41
|
|
|
@@ -35,7 +47,8 @@ async function tracesList(args, flags) {
|
|
|
35
47
|
|
|
36
48
|
console.log('');
|
|
37
49
|
const rows = traces.map(t => [
|
|
38
|
-
ui.c.dim(ui.truncate(t.traceID || t.traceId || t._id, 16)),
|
|
50
|
+
ui.c.dim(ui.truncate(t.traceID || t.traceId || t._id, 16)),
|
|
51
|
+
t.environment || 'production',
|
|
39
52
|
t.operationName || t.name || t.serviceName || '—',
|
|
40
53
|
ui.httpStatusColor(t.statusCode || t.httpStatusCode || t.responseStatusCode || '—'),
|
|
41
54
|
ui.durationColor(t.durationNano ? t.durationNano / 1e6 : t.duration),
|
|
@@ -44,7 +57,7 @@ async function tracesList(args, flags) {
|
|
|
44
57
|
ui.timeAgo(t.timestamp),
|
|
45
58
|
]);
|
|
46
59
|
|
|
47
|
-
ui.table(['Trace ID', 'Operation', 'Status', 'Duration', 'Method', 'URL', 'Time'], rows);
|
|
60
|
+
ui.table(['Trace ID', 'Env', 'Operation', 'Status', 'Duration', 'Method', 'URL', 'Time'], rows);
|
|
48
61
|
console.log('');
|
|
49
62
|
} catch (err) {
|
|
50
63
|
s.fail('Failed to fetch traces');
|
|
@@ -62,9 +75,10 @@ async function tracesShow(args, flags) {
|
|
|
62
75
|
|
|
63
76
|
const s = ui.spinner('Fetching trace details');
|
|
64
77
|
try {
|
|
65
|
-
const appKey = resolveApp(flags);
|
|
66
|
-
const traceQuery = appKey ? { appKeys: appKey } : {};
|
|
67
|
-
|
|
78
|
+
const appKey = resolveApp(flags);
|
|
79
|
+
const traceQuery = appKey ? { appKeys: appKey } : {};
|
|
80
|
+
addEnvironment(traceQuery, flags);
|
|
81
|
+
const data = await api.get(`/traces/${traceId}`, { query: traceQuery });
|
|
68
82
|
s.stop('Trace loaded');
|
|
69
83
|
|
|
70
84
|
if (flags.json) { ui.json(data); return; }
|
|
@@ -103,9 +117,29 @@ async function tracesAnalyze(args, flags) {
|
|
|
103
117
|
process.exit(1);
|
|
104
118
|
}
|
|
105
119
|
|
|
106
|
-
const s = ui.spinner('Analyzing trace with AI');
|
|
107
|
-
try {
|
|
108
|
-
|
|
120
|
+
const s = ui.spinner('Analyzing trace with AI');
|
|
121
|
+
try {
|
|
122
|
+
const environment = resolveEnvironment(flags);
|
|
123
|
+
const appKey = resolveApp(flags);
|
|
124
|
+
if (!appKey) {
|
|
125
|
+
s.fail('Missing app key');
|
|
126
|
+
ui.error('Use --app <key> or set a default with `securenow config set defaultApp <key>`');
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const query = { appKeys: appKey };
|
|
131
|
+
if (environment) query.environment = environment;
|
|
132
|
+
const [traceData, logsData] = await Promise.all([
|
|
133
|
+
api.get(`/traces/${traceId}`, { query }),
|
|
134
|
+
api.get(`/logs/trace/${traceId}`, { query }).catch(() => ({ logs: [] })),
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
const body = {
|
|
138
|
+
traceData: traceData.spans || traceData.traceData || [],
|
|
139
|
+
logs: logsData.logs || [],
|
|
140
|
+
};
|
|
141
|
+
if (environment) body.environment = environment;
|
|
142
|
+
let result = await api.post('/traces/analyze', body);
|
|
109
143
|
|
|
110
144
|
if (result.analysisId && result.status === 'running') {
|
|
111
145
|
const analysisId = result.analysisId;
|
|
@@ -223,10 +257,11 @@ async function logsList(args, flags) {
|
|
|
223
257
|
limit: flags.limit || 200,
|
|
224
258
|
from: flags.start || new Date(now - minutes * 60 * 1000).toISOString(),
|
|
225
259
|
to: flags.end || new Date(now).toISOString(),
|
|
226
|
-
};
|
|
227
|
-
if (severity) query.severity = severity;
|
|
228
|
-
|
|
229
|
-
|
|
260
|
+
};
|
|
261
|
+
if (severity) query.severity = severity;
|
|
262
|
+
addEnvironment(query, flags);
|
|
263
|
+
|
|
264
|
+
const data = await api.get('/logs', { query });
|
|
230
265
|
const logs = data.logs || [];
|
|
231
266
|
s.stop(`Found ${logs.length} log${logs.length !== 1 ? 's' : ''}`);
|
|
232
267
|
|
|
@@ -244,7 +279,8 @@ async function logsList(args, flags) {
|
|
|
244
279
|
const time = ui.c.dim(parsedTime ? parsedTime.toLocaleTimeString() : '');
|
|
245
280
|
const body = log.body || log.message || log.severityText || '';
|
|
246
281
|
|
|
247
|
-
|
|
282
|
+
const env = log.environment ? ` ${ui.c.dim(`[${log.environment}]`)}` : '';
|
|
283
|
+
console.log(` ${time} ${levelColor(level.padEnd(7))}${env} ${body}`);
|
|
248
284
|
|
|
249
285
|
if (log.traceId && flags.verbose) {
|
|
250
286
|
console.log(` ${ui.c.dim(` trace=${log.traceId} span=${log.spanId || ''}`)}`);
|
|
@@ -265,9 +301,13 @@ async function logsTrace(args, flags) {
|
|
|
265
301
|
process.exit(1);
|
|
266
302
|
}
|
|
267
303
|
|
|
268
|
-
const s = ui.spinner('Fetching logs for trace');
|
|
269
|
-
try {
|
|
270
|
-
const
|
|
304
|
+
const s = ui.spinner('Fetching logs for trace');
|
|
305
|
+
try {
|
|
306
|
+
const query = {};
|
|
307
|
+
const appKey = resolveApp(flags);
|
|
308
|
+
if (appKey) query.appKeys = appKey;
|
|
309
|
+
addEnvironment(query, flags);
|
|
310
|
+
const data = await api.get(`/logs/trace/${traceId}`, { query });
|
|
271
311
|
const logs = data.logs || [];
|
|
272
312
|
s.stop(`Found ${logs.length} log${logs.length !== 1 ? 's' : ''}`);
|
|
273
313
|
|
|
@@ -283,7 +323,8 @@ async function logsTrace(args, flags) {
|
|
|
283
323
|
|
|
284
324
|
const parsedTime = ui.parseTimestamp(log.timestamp);
|
|
285
325
|
const time = ui.c.dim(parsedTime ? parsedTime.toLocaleTimeString() : '');
|
|
286
|
-
|
|
326
|
+
const env = log.environment ? ` ${ui.c.dim(`[${log.environment}]`)}` : '';
|
|
327
|
+
console.log(` ${time} ${levelColor(level.padEnd(7))}${env} ${log.body || log.message || ''}`);
|
|
287
328
|
}
|
|
288
329
|
console.log('');
|
|
289
330
|
} catch (err) {
|
|
@@ -400,22 +441,45 @@ async function status(args, flags) {
|
|
|
400
441
|
ui.table(['Name', 'Key', 'Hosts'], rows);
|
|
401
442
|
}
|
|
402
443
|
|
|
403
|
-
const appKey = resolveApp(flags);
|
|
404
|
-
|
|
444
|
+
const appKey = resolveApp(flags);
|
|
445
|
+
const requestedEnv = resolveEnvironment(flags) || appConfig.resolveDeploymentEnvironment() || 'production';
|
|
446
|
+
const showAllEnvs = requestedEnv === 'all' || requestedEnv === '*';
|
|
447
|
+
{
|
|
405
448
|
try {
|
|
406
|
-
const protectionData = await api.get('/applications/protection-status');
|
|
407
|
-
const statuses = protectionData.statuses;
|
|
408
|
-
if (statuses && Object.keys(statuses).length > 0) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
449
|
+
const protectionData = await api.get('/applications/protection-status');
|
|
450
|
+
const statuses = protectionData.statuses;
|
|
451
|
+
if (statuses && Object.keys(statuses).length > 0) {
|
|
452
|
+
const appById = new Map(apps.map((app) => [String(app._id || app.id), app]));
|
|
453
|
+
const envRows = [];
|
|
454
|
+
|
|
455
|
+
for (const [id, status] of Object.entries(statuses)) {
|
|
456
|
+
const app = appById.get(String(id));
|
|
457
|
+
if (appKey && app && app.key !== appKey) continue;
|
|
458
|
+
|
|
459
|
+
const environments = status.environments || { production: status.production || status };
|
|
460
|
+
const entries = showAllEnvs
|
|
461
|
+
? Object.entries(environments)
|
|
462
|
+
: [[requestedEnv, environments[requestedEnv] || status.production || status]];
|
|
463
|
+
|
|
464
|
+
for (const [envName, envStatus] of entries) {
|
|
465
|
+
envRows.push([
|
|
466
|
+
app?.name || ui.c.dim(ui.truncate(id, 12)),
|
|
467
|
+
envName,
|
|
468
|
+
envStatus.firewallEnabled ? ui.c.green('on') : ui.c.dim('off'),
|
|
469
|
+
envStatus.protected ? ui.c.green('live') : ui.c.dim('idle'),
|
|
470
|
+
String(envStatus.traceCount || 0),
|
|
471
|
+
envStatus.lastTrace ? ui.timeAgo(envStatus.lastTrace) : ui.c.dim('-'),
|
|
472
|
+
]);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (envRows.length) {
|
|
477
|
+
ui.subheading(`Protection Status (${showAllEnvs ? 'all envs' : requestedEnv})`);
|
|
478
|
+
console.log('');
|
|
479
|
+
ui.table(['App', 'Env', 'Firewall', 'Traces', 'Count (15m)', 'Last Trace'], envRows);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
}
|
|
419
483
|
} catch {}
|
|
420
484
|
}
|
|
421
485
|
|
package/cli/security.js
CHANGED
|
@@ -4,9 +4,13 @@ const { api, requireAuth } = require('./client');
|
|
|
4
4
|
const config = require('./config');
|
|
5
5
|
const ui = require('./ui');
|
|
6
6
|
|
|
7
|
-
function resolveApp(flags) {
|
|
8
|
-
return flags.app || config.getDefaultApp();
|
|
9
|
-
}
|
|
7
|
+
function resolveApp(flags) {
|
|
8
|
+
return flags.app || config.getDefaultApp();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function resolveEnvironment(flags, fallback = null) {
|
|
12
|
+
return flags.env || flags.environment || fallback;
|
|
13
|
+
}
|
|
10
14
|
|
|
11
15
|
// ── Alert Rules ──
|
|
12
16
|
|
|
@@ -523,7 +527,7 @@ async function forensicsQuery(args, flags) {
|
|
|
523
527
|
|
|
524
528
|
const s = ui.spinner('Submitting forensic query');
|
|
525
529
|
try {
|
|
526
|
-
const body = { query };
|
|
530
|
+
const body = { query, environment: resolveEnvironment(flags, 'production') };
|
|
527
531
|
const resolved = await resolveAppId(flags);
|
|
528
532
|
if (resolved) {
|
|
529
533
|
body.applicationId = resolved.id;
|
|
@@ -650,7 +654,10 @@ async function forensicsChat(args, flags) {
|
|
|
650
654
|
console.log(ui.c.dim(` App: ${resolved.key} (${resolved.id})`));
|
|
651
655
|
console.log(ui.c.dim(' Type your question, or "exit" to quit.\n'));
|
|
652
656
|
|
|
653
|
-
|
|
657
|
+
const environment = resolveEnvironment(flags, 'production');
|
|
658
|
+
console.log(ui.c.dim(` Env: ${environment}`));
|
|
659
|
+
|
|
660
|
+
let conversationId = null;
|
|
654
661
|
|
|
655
662
|
const readline = require('readline');
|
|
656
663
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -670,7 +677,7 @@ async function forensicsChat(args, flags) {
|
|
|
670
677
|
|
|
671
678
|
const s = ui.spinner('Thinking');
|
|
672
679
|
try {
|
|
673
|
-
const body = { message, applicationKey: resolved.key };
|
|
680
|
+
const body = { message, applicationKey: resolved.key, environment };
|
|
674
681
|
if (conversationId) body.conversationId = conversationId;
|
|
675
682
|
|
|
676
683
|
const chatRes = await api.post('/forensics/chat', body);
|
|
@@ -680,7 +687,7 @@ async function forensicsChat(args, flags) {
|
|
|
680
687
|
let result;
|
|
681
688
|
for (let i = 0; i < 150; i++) {
|
|
682
689
|
await new Promise(r => setTimeout(r, 2000));
|
|
683
|
-
result = await api.get(`/forensics/chat/status/${conversationId}
|
|
690
|
+
result = await api.get(`/forensics/chat/status/${conversationId}`, { query: { environment } });
|
|
684
691
|
if (result.status === 'complete' || result.status === 'failed' || result.status === 'awaiting_confirmation') break;
|
|
685
692
|
const progress = result._progress;
|
|
686
693
|
if (progress?.action) s.update(progress.action);
|
|
@@ -698,11 +705,11 @@ async function forensicsChat(args, flags) {
|
|
|
698
705
|
const proceed = await ui.confirm('Proceed with this query?');
|
|
699
706
|
if (proceed) {
|
|
700
707
|
const cs = ui.spinner('Executing query');
|
|
701
|
-
await api.post(`/forensics/chat/confirm/${conversationId}
|
|
708
|
+
await api.post(`/forensics/chat/confirm/${conversationId}`, { environment });
|
|
702
709
|
let confirmResult;
|
|
703
710
|
for (let i = 0; i < 90; i++) {
|
|
704
711
|
await new Promise(r => setTimeout(r, 2000));
|
|
705
|
-
confirmResult = await api.get(`/forensics/chat/status/${conversationId}
|
|
712
|
+
confirmResult = await api.get(`/forensics/chat/status/${conversationId}`, { query: { environment } });
|
|
706
713
|
if (confirmResult.status !== 'processing') break;
|
|
707
714
|
}
|
|
708
715
|
cs.stop('Done');
|
|
@@ -836,9 +843,14 @@ async function ipTraces(args, flags) {
|
|
|
836
843
|
process.exit(1);
|
|
837
844
|
}
|
|
838
845
|
|
|
839
|
-
const s = ui.spinner(`Fetching traces for ${ip}`);
|
|
840
|
-
try {
|
|
841
|
-
const
|
|
846
|
+
const s = ui.spinner(`Fetching traces for ${ip}`);
|
|
847
|
+
try {
|
|
848
|
+
const query = {};
|
|
849
|
+
const appKey = resolveApp(flags);
|
|
850
|
+
if (appKey) query.appKeys = appKey;
|
|
851
|
+
const environment = resolveEnvironment(flags, null);
|
|
852
|
+
if (environment) query.environment = environment;
|
|
853
|
+
const data = await api.get(`/ip/${ip}/traces`, { query });
|
|
842
854
|
const traces = data.traces || [];
|
|
843
855
|
s.stop(`Found ${traces.length} trace${traces.length !== 1 ? 's' : ''}`);
|
|
844
856
|
|
package/cli/ui.js
CHANGED
|
@@ -111,13 +111,21 @@ function keyValue(pairs) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
function heading(text) {
|
|
115
|
-
console.log(`\n${c.bold(c.cyan(text))}`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
}
|
|
114
|
+
function heading(text) {
|
|
115
|
+
console.log(`\n${c.bold(c.cyan(text))}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function header(text) {
|
|
119
|
+
heading(text);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function bold(text) {
|
|
123
|
+
return c.bold(text);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function subheading(text) {
|
|
127
|
+
console.log(`\n ${c.bold(text)}`);
|
|
128
|
+
}
|
|
121
129
|
|
|
122
130
|
function success(msg) {
|
|
123
131
|
console.log(`${c.green('✓')} ${msg}`);
|
|
@@ -361,11 +369,13 @@ function durationColor(ms) {
|
|
|
361
369
|
module.exports = {
|
|
362
370
|
c,
|
|
363
371
|
NO_COLOR,
|
|
364
|
-
stripAnsi,
|
|
365
|
-
table,
|
|
366
|
-
keyValue,
|
|
367
|
-
heading,
|
|
368
|
-
|
|
372
|
+
stripAnsi,
|
|
373
|
+
table,
|
|
374
|
+
keyValue,
|
|
375
|
+
heading,
|
|
376
|
+
header,
|
|
377
|
+
bold,
|
|
378
|
+
subheading,
|
|
369
379
|
success,
|
|
370
380
|
error,
|
|
371
381
|
warn,
|
package/cli/utils.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const ui = require('./ui');
|
|
5
5
|
const cidrLib = require('../cidr');
|
|
6
6
|
const { redactSensitiveData, DEFAULT_SENSITIVE_FIELDS } = require('../nextjs-middleware');
|
|
7
|
+
const appConfig = require('../app-config');
|
|
7
8
|
|
|
8
9
|
// ── redact ──
|
|
9
10
|
|
|
@@ -38,7 +39,7 @@ function redact(args, flags) {
|
|
|
38
39
|
const extra = flags.fields
|
|
39
40
|
? String(flags.fields).split(',').map((s) => s.trim()).filter(Boolean)
|
|
40
41
|
: [];
|
|
41
|
-
const envExtra = (
|
|
42
|
+
const envExtra = (appConfig.env('SECURENOW_SENSITIVE_FIELDS') || '')
|
|
42
43
|
.split(',').map((s) => s.trim()).filter(Boolean);
|
|
43
44
|
const fields = [...DEFAULT_SENSITIVE_FIELDS, ...envExtra, ...extra];
|
|
44
45
|
|
package/cli.js
CHANGED
|
@@ -39,11 +39,13 @@ function parseArgs(argv) {
|
|
|
39
39
|
// ── Command Registry ──
|
|
40
40
|
|
|
41
41
|
const COMMANDS = {
|
|
42
|
-
init: {
|
|
43
|
-
desc: 'Set up SecureNow in the current project (instrumentation
|
|
44
|
-
usage: 'securenow init [--key <API_KEY>] [--ts|--js] [--src|--root] [--force]',
|
|
45
|
-
flags: {
|
|
46
|
-
|
|
42
|
+
init: {
|
|
43
|
+
desc: 'Set up SecureNow in the current project (instrumentation + .securenow credentials)',
|
|
44
|
+
usage: 'securenow init [--env local] [--key <API_KEY>] [--ts|--js] [--src|--root] [--force]',
|
|
45
|
+
flags: {
|
|
46
|
+
env: 'Deployment environment to write into .securenow/credentials.json (default: local)',
|
|
47
|
+
environment: 'Alias for --env',
|
|
48
|
+
key: 'Firewall API key to write to .securenow/credentials.json',
|
|
47
49
|
'api-key': 'Alias for --key',
|
|
48
50
|
typescript: 'Force TypeScript',
|
|
49
51
|
javascript: 'Force JavaScript',
|
|
@@ -73,7 +75,7 @@ const COMMANDS = {
|
|
|
73
75
|
usage: 'securenow whoami',
|
|
74
76
|
run: () => require('./cli/auth').whoami(),
|
|
75
77
|
},
|
|
76
|
-
'api-key': {
|
|
78
|
+
'api-key': {
|
|
77
79
|
desc: 'Manage the firewall API key stored in .securenow/credentials.json',
|
|
78
80
|
usage: 'securenow api-key <subcommand> [options]',
|
|
79
81
|
sub: {
|
|
@@ -95,8 +97,27 @@ const COMMANDS = {
|
|
|
95
97
|
run: () => require('./cli/apiKey').show(),
|
|
96
98
|
},
|
|
97
99
|
},
|
|
98
|
-
defaultSub: 'show',
|
|
99
|
-
},
|
|
100
|
+
defaultSub: 'show',
|
|
101
|
+
},
|
|
102
|
+
credentials: {
|
|
103
|
+
desc: 'Create production/runtime SecureNow credentials files',
|
|
104
|
+
usage: 'securenow credentials <subcommand> [options]',
|
|
105
|
+
sub: {
|
|
106
|
+
runtime: {
|
|
107
|
+
desc: 'Write tokenless runtime credentials for production file-based deploys',
|
|
108
|
+
usage: 'securenow credentials runtime [--env production] [--out .securenow/credentials.production.json] [--stdout]',
|
|
109
|
+
flags: {
|
|
110
|
+
env: 'Deployment environment for this runtime file (default: production)',
|
|
111
|
+
environment: 'Alias for --env',
|
|
112
|
+
out: 'Output path (default: .securenow/credentials.<env>.json)',
|
|
113
|
+
output: 'Alias for --out',
|
|
114
|
+
stdout: 'Print JSON to stdout instead of writing a file',
|
|
115
|
+
},
|
|
116
|
+
run: (a, f) => require('./cli/credentials').runtime(a, f),
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
defaultSub: 'runtime',
|
|
120
|
+
},
|
|
100
121
|
apps: {
|
|
101
122
|
desc: 'Manage applications',
|
|
102
123
|
usage: 'securenow apps <subcommand> [options]',
|
|
@@ -115,9 +136,9 @@ const COMMANDS = {
|
|
|
115
136
|
desc: 'View and analyze traces',
|
|
116
137
|
usage: 'securenow traces <subcommand> [options]',
|
|
117
138
|
sub: {
|
|
118
|
-
list: { desc: 'List recent traces', flags: { app: 'App key', limit: 'Max results', start: 'Start time', end: 'End time' }, run: (a, f) => require('./cli/monitor').tracesList(a, f) },
|
|
119
|
-
show: { desc: 'Show trace details', usage: 'securenow traces show <traceId>', run: (a, f) => require('./cli/monitor').tracesShow(a, f) },
|
|
120
|
-
analyze: { desc: 'AI-analyze a trace', usage: 'securenow traces analyze <traceId>', run: (a, f) => require('./cli/monitor').tracesAnalyze(a, f) },
|
|
139
|
+
list: { desc: 'List recent traces', flags: { app: 'App key', env: 'Environment (production, staging, preview, local, or all)', environment: 'Alias for --env', limit: 'Max results', start: 'Start time', end: 'End time' }, run: (a, f) => require('./cli/monitor').tracesList(a, f) },
|
|
140
|
+
show: { desc: 'Show trace details', usage: 'securenow traces show <traceId>', flags: { app: 'App key', env: 'Environment', environment: 'Alias for --env' }, run: (a, f) => require('./cli/monitor').tracesShow(a, f) },
|
|
141
|
+
analyze: { desc: 'AI-analyze a trace', usage: 'securenow traces analyze <traceId>', flags: { app: 'App key', env: 'Environment', environment: 'Alias for --env' }, run: (a, f) => require('./cli/monitor').tracesAnalyze(a, f) },
|
|
121
142
|
},
|
|
122
143
|
defaultSub: 'list',
|
|
123
144
|
},
|
|
@@ -125,8 +146,8 @@ const COMMANDS = {
|
|
|
125
146
|
desc: 'View application logs',
|
|
126
147
|
usage: 'securenow logs [options]',
|
|
127
148
|
sub: {
|
|
128
|
-
list: { desc: 'List recent logs', flags: { app: 'App key', limit: 'Max results (default 200)', since: 'Time window (e.g. 30m, 6h, 2d)', minutes: 'Time window in minutes (alias for --since Nm)', level: 'Filter by level (error, warn, info, debug)', start: 'Start time (ISO 8601)', end: 'End time (ISO 8601)' }, run: (a, f) => require('./cli/monitor').logsList(a, f) },
|
|
129
|
-
trace: { desc: 'Show logs for a trace', usage: 'securenow logs trace <traceId>', run: (a, f) => require('./cli/monitor').logsTrace(a, f) },
|
|
149
|
+
list: { desc: 'List recent logs', flags: { app: 'App key', env: 'Environment (production, staging, preview, local, or all)', environment: 'Alias for --env', limit: 'Max results (default 200)', since: 'Time window (e.g. 30m, 6h, 2d)', minutes: 'Time window in minutes (alias for --since Nm)', level: 'Filter by level (error, warn, info, debug)', start: 'Start time (ISO 8601)', end: 'End time (ISO 8601)' }, run: (a, f) => require('./cli/monitor').logsList(a, f) },
|
|
150
|
+
trace: { desc: 'Show logs for a trace', usage: 'securenow logs trace <traceId>', flags: { app: 'App key', env: 'Environment', environment: 'Alias for --env' }, run: (a, f) => require('./cli/monitor').logsTrace(a, f) },
|
|
130
151
|
},
|
|
131
152
|
defaultSub: 'list',
|
|
132
153
|
},
|
|
@@ -182,11 +203,11 @@ const COMMANDS = {
|
|
|
182
203
|
usage: 'securenow firewall <subcommand> [options]',
|
|
183
204
|
flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
|
|
184
205
|
sub: {
|
|
185
|
-
status: { desc: 'Show firewall status, layers, and blocklist info', run: (a, f) => require('./cli/firewall').status(a, f) },
|
|
206
|
+
status: { desc: 'Show firewall status, layers, and blocklist info', flags: { env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').status(a, f) },
|
|
186
207
|
apps: { desc: 'List apps with their firewall on/off state', run: (a, f) => require('./cli/firewall').appsList(a, f) },
|
|
187
|
-
enable: { desc: 'Turn the firewall ON for an app', usage: 'securenow firewall enable [--app <key>]', flags: { app: 'App key (defaults to logged-in app)' }, run: (a, f) => require('./cli/firewall').enable(a, f) },
|
|
188
|
-
disable: { desc: 'Turn the firewall OFF for an app', usage: 'securenow firewall disable [--app <key>]', flags: { app: 'App key (defaults to logged-in app)' }, run: (a, f) => require('./cli/firewall').disable(a, f) },
|
|
189
|
-
'test-ip': { desc: 'Check if an IP would be blocked', usage: 'securenow firewall test-ip <ip>', run: (a, f) => require('./cli/firewall').testIp(a, f) },
|
|
208
|
+
enable: { desc: 'Turn the firewall ON for an app environment', usage: 'securenow firewall enable [--app <key>] [--env production]', flags: { app: 'App key (defaults to logged-in app)', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').enable(a, f) },
|
|
209
|
+
disable: { desc: 'Turn the firewall OFF for an app environment', usage: 'securenow firewall disable [--app <key>] [--env local]', flags: { app: 'App key (defaults to logged-in app)', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').disable(a, f) },
|
|
210
|
+
'test-ip': { desc: 'Check if an IP would be blocked', usage: 'securenow firewall test-ip <ip> [--env production]', flags: { env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').testIp(a, f) },
|
|
190
211
|
},
|
|
191
212
|
defaultSub: 'status',
|
|
192
213
|
},
|
|
@@ -227,7 +248,7 @@ const COMMANDS = {
|
|
|
227
248
|
usage: 'securenow ip <ip-address>',
|
|
228
249
|
sub: {
|
|
229
250
|
lookup: { desc: 'Look up IP intelligence', run: (a, f) => require('./cli/security').ipLookup(a, f) },
|
|
230
|
-
traces: { desc: 'Show traces for an IP', usage: 'securenow ip traces <ip>', run: (a, f) => require('./cli/security').ipTraces(a, f) },
|
|
251
|
+
traces: { desc: 'Show traces for an IP', usage: 'securenow ip traces <ip> [--env production]', flags: { app: 'App key', env: 'Environment', environment: 'Alias for --env' }, run: (a, f) => require('./cli/security').ipTraces(a, f) },
|
|
231
252
|
},
|
|
232
253
|
defaultAction: (a, f) => require('./cli/security').ipLookup(a, f),
|
|
233
254
|
},
|
|
@@ -235,8 +256,8 @@ const COMMANDS = {
|
|
|
235
256
|
desc: 'Run forensic queries (natural language → SQL)',
|
|
236
257
|
usage: 'securenow forensics <query> [--app <key>]',
|
|
237
258
|
sub: {
|
|
238
|
-
query: { desc: 'Run a forensic query', flags: { app: 'App key to scope query' }, run: (a, f) => require('./cli/security').forensicsQuery(a, f) },
|
|
239
|
-
chat: { desc: 'Interactive forensics chat (scoped to an app)', usage: 'securenow forensics chat --app <key>', flags: { app: 'App key to chat with' }, run: (a, f) => require('./cli/security').forensicsChat(a, f) },
|
|
259
|
+
query: { desc: 'Run a forensic query', flags: { app: 'App key to scope query', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/security').forensicsQuery(a, f) },
|
|
260
|
+
chat: { desc: 'Interactive forensics chat (scoped to an app)', usage: 'securenow forensics chat --app <key> [--env production]', flags: { app: 'App key to chat with', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/security').forensicsChat(a, f) },
|
|
240
261
|
library: { desc: 'View saved queries', run: (a, f) => require('./cli/security').forensicsLibrary(a, f) },
|
|
241
262
|
},
|
|
242
263
|
defaultAction: (a, f) => require('./cli/security').forensicsQuery(a, f),
|
|
@@ -255,11 +276,16 @@ const COMMANDS = {
|
|
|
255
276
|
usage: 'securenow analytics [--app <key>]',
|
|
256
277
|
run: (a, f) => require('./cli/security').analytics(a, f),
|
|
257
278
|
},
|
|
258
|
-
status: {
|
|
259
|
-
desc: 'Dashboard overview',
|
|
260
|
-
usage: 'securenow status [--app <key>]',
|
|
261
|
-
|
|
262
|
-
|
|
279
|
+
status: {
|
|
280
|
+
desc: 'Dashboard overview',
|
|
281
|
+
usage: 'securenow status [--app <key>] [--env local|production|all]',
|
|
282
|
+
flags: {
|
|
283
|
+
app: 'App key to highlight',
|
|
284
|
+
env: 'Environment to display in protection summary (default: credentials file, use all for every env)',
|
|
285
|
+
environment: 'Alias for --env',
|
|
286
|
+
},
|
|
287
|
+
run: (a, f) => require('./cli/monitor').status(a, f),
|
|
288
|
+
},
|
|
263
289
|
config: {
|
|
264
290
|
desc: 'Manage CLI configuration',
|
|
265
291
|
usage: 'securenow config <set|get> [key] [value]',
|
|
@@ -343,30 +369,36 @@ const COMMANDS = {
|
|
|
343
369
|
log: {
|
|
344
370
|
desc: 'Emit logs via OTLP (for scripts, cron, debugging)',
|
|
345
371
|
usage: 'securenow log send <message> [--level info|warn|error] [--attrs k=v,k=v]',
|
|
346
|
-
sub: {
|
|
347
|
-
send: {
|
|
348
|
-
desc: 'Send a single log record to the OTLP collector',
|
|
349
|
-
flags: {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
372
|
+
sub: {
|
|
373
|
+
send: {
|
|
374
|
+
desc: 'Send a single log record to the OTLP collector',
|
|
375
|
+
flags: {
|
|
376
|
+
env: 'Deployment environment for this log (defaults to credentials file)',
|
|
377
|
+
environment: 'Alias for --env',
|
|
378
|
+
level: 'Severity (trace|debug|info|warn|error|fatal)',
|
|
379
|
+
attrs: 'Comma-separated key=value attributes',
|
|
380
|
+
},
|
|
353
381
|
run: (a, f) => require('./cli/diagnostics').logSend(a, f),
|
|
354
382
|
},
|
|
355
383
|
},
|
|
356
384
|
defaultSub: 'send',
|
|
357
385
|
},
|
|
358
|
-
'test-span': {
|
|
359
|
-
desc: 'Emit a test span to verify collector connectivity',
|
|
360
|
-
usage: 'securenow test-span [<span-name>]',
|
|
361
|
-
|
|
362
|
-
|
|
386
|
+
'test-span': {
|
|
387
|
+
desc: 'Emit a test span to verify collector connectivity',
|
|
388
|
+
usage: 'securenow test-span [<span-name>] [--env local|production]',
|
|
389
|
+
flags: {
|
|
390
|
+
env: 'Deployment environment for this span (defaults to credentials file)',
|
|
391
|
+
environment: 'Alias for --env',
|
|
392
|
+
},
|
|
393
|
+
run: (a, f) => require('./cli/diagnostics').testSpan(a, f),
|
|
394
|
+
},
|
|
363
395
|
doctor: {
|
|
364
396
|
desc: 'Diagnose SecureNow configuration and collector connectivity',
|
|
365
397
|
usage: 'securenow doctor [--json]',
|
|
366
398
|
run: (a, f) => require('./cli/diagnostics').doctor(a, f),
|
|
367
399
|
},
|
|
368
400
|
env: {
|
|
369
|
-
desc: 'Show resolved SecureNow configuration (service name, endpoints,
|
|
401
|
+
desc: 'Show resolved SecureNow configuration (service name, endpoints, credentials)',
|
|
370
402
|
usage: 'securenow env [--json]',
|
|
371
403
|
run: (a, f) => require('./cli/diagnostics').env(a, f),
|
|
372
404
|
},
|
|
@@ -434,7 +466,7 @@ function showHelp(commandName) {
|
|
|
434
466
|
|
|
435
467
|
const groups = {
|
|
436
468
|
'Run': ['run'],
|
|
437
|
-
'Authentication': ['login', 'logout', 'whoami'],
|
|
469
|
+
'Authentication': ['login', 'logout', 'whoami', 'credentials'],
|
|
438
470
|
'Applications': ['apps', 'init', 'status'],
|
|
439
471
|
'Observe': ['traces', 'logs', 'analytics'],
|
|
440
472
|
'Detect & Respond': ['notifications', 'alerts', 'fp'],
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* to automatically send logs to OpenTelemetry / any OTLP-compatible backend.
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
10
|
-
* 1.
|
|
10
|
+
* 1. Run `npx securenow login` / `npx securenow init` so credentials defaults exist
|
|
11
11
|
* 2. Import this file AFTER securenow is initialized
|
|
12
12
|
* 3. Use console.log/info/warn/error as normal
|
|
13
13
|
*
|