securenow 8.0.3 → 8.2.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/NPM_README.md +37 -1
- package/SKILL-CLI.md +237 -229
- package/cli/diagnostics.js +48 -1
- package/cli/security.js +467 -374
- package/cli.js +451 -418
- package/events.d.ts +29 -0
- package/events.js +160 -0
- package/package.json +7 -1
package/cli.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const ui = require('./cli/ui');
|
|
5
|
-
|
|
6
|
-
// ── Argument Parser ──
|
|
7
|
-
|
|
8
|
-
function parseArgs(argv) {
|
|
9
|
-
const positional = [];
|
|
10
|
-
const flags = {};
|
|
11
|
-
|
|
12
|
-
for (let i = 0; i < argv.length; i++) {
|
|
13
|
-
const arg = argv[i];
|
|
14
|
-
if (arg.startsWith('--')) {
|
|
15
|
-
const eqIdx = arg.indexOf('=');
|
|
16
|
-
if (eqIdx !== -1) {
|
|
17
|
-
flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
|
|
18
|
-
} else {
|
|
19
|
-
const next = argv[i + 1];
|
|
20
|
-
if (next && !next.startsWith('-')) {
|
|
21
|
-
flags[arg.slice(2)] = next;
|
|
22
|
-
i++;
|
|
23
|
-
} else {
|
|
24
|
-
flags[arg.slice(2)] = true;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
28
|
-
const shortMap = { f: 'force', y: 'yes', v: 'verbose', j: 'json' };
|
|
29
|
-
const long = shortMap[arg[1]] || arg[1];
|
|
30
|
-
flags[long] = true;
|
|
31
|
-
} else {
|
|
32
|
-
positional.push(arg);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { positional, flags };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ── Command Registry ──
|
|
40
|
-
|
|
41
|
-
const COMMANDS = {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const ui = require('./cli/ui');
|
|
5
|
+
|
|
6
|
+
// ── Argument Parser ──
|
|
7
|
+
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
const positional = [];
|
|
10
|
+
const flags = {};
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13
|
+
const arg = argv[i];
|
|
14
|
+
if (arg.startsWith('--')) {
|
|
15
|
+
const eqIdx = arg.indexOf('=');
|
|
16
|
+
if (eqIdx !== -1) {
|
|
17
|
+
flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
|
|
18
|
+
} else {
|
|
19
|
+
const next = argv[i + 1];
|
|
20
|
+
if (next && !next.startsWith('-')) {
|
|
21
|
+
flags[arg.slice(2)] = next;
|
|
22
|
+
i++;
|
|
23
|
+
} else {
|
|
24
|
+
flags[arg.slice(2)] = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
28
|
+
const shortMap = { f: 'force', y: 'yes', v: 'verbose', j: 'json' };
|
|
29
|
+
const long = shortMap[arg[1]] || arg[1];
|
|
30
|
+
flags[long] = true;
|
|
31
|
+
} else {
|
|
32
|
+
positional.push(arg);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { positional, flags };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Command Registry ──
|
|
40
|
+
|
|
41
|
+
const COMMANDS = {
|
|
42
42
|
init: {
|
|
43
43
|
desc: 'Set up SecureNow in the current project (instrumentation + runtime credentials)',
|
|
44
44
|
usage: 'securenow init [--env local] [--key <API_KEY>] [--ts|--js] [--src|--root] [--force]',
|
|
@@ -46,21 +46,21 @@ const COMMANDS = {
|
|
|
46
46
|
env: 'Deployment environment to write into .securenow/runtime.json (default: local)',
|
|
47
47
|
environment: 'Alias for --env',
|
|
48
48
|
key: 'Firewall API key to write to .securenow/runtime.json',
|
|
49
|
-
'api-key': 'Alias for --key',
|
|
50
|
-
typescript: 'Force TypeScript',
|
|
51
|
-
javascript: 'Force JavaScript',
|
|
52
|
-
src: 'Create in src/',
|
|
53
|
-
root: 'Create in root',
|
|
54
|
-
force: 'Overwrite existing',
|
|
55
|
-
},
|
|
56
|
-
run: (a, f) => require('./cli/init').init(a, f),
|
|
57
|
-
},
|
|
49
|
+
'api-key': 'Alias for --key',
|
|
50
|
+
typescript: 'Force TypeScript',
|
|
51
|
+
javascript: 'Force JavaScript',
|
|
52
|
+
src: 'Create in src/',
|
|
53
|
+
root: 'Create in root',
|
|
54
|
+
force: 'Overwrite existing',
|
|
55
|
+
},
|
|
56
|
+
run: (a, f) => require('./cli/init').init(a, f),
|
|
57
|
+
},
|
|
58
58
|
login: {
|
|
59
59
|
desc: 'Onboard SecureNow admin auth and app runtime config',
|
|
60
60
|
usage: 'securenow login [--token <TOKEN>] [--global]',
|
|
61
|
-
flags: {
|
|
62
|
-
token: 'Authenticate with a token directly',
|
|
63
|
-
global: 'Save credentials to ~/.securenow/ (shared across all projects)',
|
|
61
|
+
flags: {
|
|
62
|
+
token: 'Authenticate with a token directly',
|
|
63
|
+
global: 'Save credentials to ~/.securenow/ (shared across all projects)',
|
|
64
64
|
},
|
|
65
65
|
run: (a, f) => require('./cli/auth').login(a, f),
|
|
66
66
|
},
|
|
@@ -107,8 +107,8 @@ const COMMANDS = {
|
|
|
107
107
|
},
|
|
108
108
|
whoami: {
|
|
109
109
|
desc: 'Show admin auth and SDK runtime status',
|
|
110
|
-
usage: 'securenow whoami',
|
|
111
|
-
run: () => require('./cli/auth').whoami(),
|
|
110
|
+
usage: 'securenow whoami',
|
|
111
|
+
run: () => require('./cli/auth').whoami(),
|
|
112
112
|
},
|
|
113
113
|
'api-key': {
|
|
114
114
|
desc: 'Manage the runtime API key stored in runtime credentials',
|
|
@@ -128,21 +128,21 @@ const COMMANDS = {
|
|
|
128
128
|
set: {
|
|
129
129
|
desc: 'Save an API key (snk_live_...) to runtime credentials',
|
|
130
130
|
usage: 'securenow api-key set <snk_live_...> [--global]',
|
|
131
|
-
flags: { global: 'Save to ~/.securenow/ instead of project-local' },
|
|
132
|
-
run: (a, f) => require('./cli/apiKey').set(a, f),
|
|
133
|
-
},
|
|
134
|
-
clear: {
|
|
135
|
-
desc: 'Remove the stored API key',
|
|
136
|
-
usage: 'securenow api-key clear [--global]',
|
|
137
|
-
flags: { global: 'Clear from ~/.securenow/ instead of project-local' },
|
|
138
|
-
run: (a, f) => require('./cli/apiKey').clear(a, f),
|
|
139
|
-
},
|
|
140
|
-
show: {
|
|
141
|
-
desc: 'Print the masked API key currently in use',
|
|
142
|
-
usage: 'securenow api-key show',
|
|
143
|
-
run: () => require('./cli/apiKey').show(),
|
|
144
|
-
},
|
|
145
|
-
},
|
|
131
|
+
flags: { global: 'Save to ~/.securenow/ instead of project-local' },
|
|
132
|
+
run: (a, f) => require('./cli/apiKey').set(a, f),
|
|
133
|
+
},
|
|
134
|
+
clear: {
|
|
135
|
+
desc: 'Remove the stored API key',
|
|
136
|
+
usage: 'securenow api-key clear [--global]',
|
|
137
|
+
flags: { global: 'Clear from ~/.securenow/ instead of project-local' },
|
|
138
|
+
run: (a, f) => require('./cli/apiKey').clear(a, f),
|
|
139
|
+
},
|
|
140
|
+
show: {
|
|
141
|
+
desc: 'Print the masked API key currently in use',
|
|
142
|
+
usage: 'securenow api-key show',
|
|
143
|
+
run: () => require('./cli/apiKey').show(),
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
146
|
defaultSub: 'show',
|
|
147
147
|
},
|
|
148
148
|
credentials: {
|
|
@@ -164,47 +164,47 @@ const COMMANDS = {
|
|
|
164
164
|
},
|
|
165
165
|
defaultSub: 'runtime',
|
|
166
166
|
},
|
|
167
|
-
apps: {
|
|
168
|
-
desc: 'Manage applications',
|
|
169
|
-
usage: 'securenow apps <subcommand> [options]',
|
|
170
|
-
sub: {
|
|
171
|
-
list: { desc: 'List all applications', run: (a, f) => require('./cli/apps').list(a, f) },
|
|
172
|
-
create: { desc: 'Create a new application (interactive instance picker)', usage: 'securenow apps create <name> [--hosts host1,host2] [--instance <id>]', run: (a, f) => require('./cli/apps').create(a, f) },
|
|
173
|
-
info: { desc: 'Show application details', usage: 'securenow apps info <id>', run: (a, f) => require('./cli/apps').info(a, f) },
|
|
174
|
-
delete: { desc: 'Delete an application', usage: 'securenow apps delete <id> [--force]', run: (a, f) => require('./cli/apps').remove(a, f) },
|
|
175
|
-
default: { desc: 'Set default application', usage: 'securenow apps default <key>', run: (a, f) => require('./cli/apps').setDefault(a, f) },
|
|
176
|
-
discover: { desc: 'Discover subdomains and add as apps', usage: 'securenow apps discover [appId] [--domain example.com]', run: (a, f) => require('./cli/apps').discover(a, f) },
|
|
177
|
-
scan: { desc: 'Scan all app domains for new subdomains', usage: 'securenow apps scan [--yes]', run: (a, f) => require('./cli/apps').scan(a, f) },
|
|
178
|
-
},
|
|
179
|
-
defaultSub: 'list',
|
|
180
|
-
},
|
|
181
|
-
traces: {
|
|
182
|
-
desc: 'View and analyze traces',
|
|
183
|
-
usage: 'securenow traces <subcommand> [options]',
|
|
184
|
-
sub: {
|
|
167
|
+
apps: {
|
|
168
|
+
desc: 'Manage applications',
|
|
169
|
+
usage: 'securenow apps <subcommand> [options]',
|
|
170
|
+
sub: {
|
|
171
|
+
list: { desc: 'List all applications', run: (a, f) => require('./cli/apps').list(a, f) },
|
|
172
|
+
create: { desc: 'Create a new application (interactive instance picker)', usage: 'securenow apps create <name> [--hosts host1,host2] [--instance <id>]', run: (a, f) => require('./cli/apps').create(a, f) },
|
|
173
|
+
info: { desc: 'Show application details', usage: 'securenow apps info <id>', run: (a, f) => require('./cli/apps').info(a, f) },
|
|
174
|
+
delete: { desc: 'Delete an application', usage: 'securenow apps delete <id> [--force]', run: (a, f) => require('./cli/apps').remove(a, f) },
|
|
175
|
+
default: { desc: 'Set default application', usage: 'securenow apps default <key>', run: (a, f) => require('./cli/apps').setDefault(a, f) },
|
|
176
|
+
discover: { desc: 'Discover subdomains and add as apps', usage: 'securenow apps discover [appId] [--domain example.com]', run: (a, f) => require('./cli/apps').discover(a, f) },
|
|
177
|
+
scan: { desc: 'Scan all app domains for new subdomains', usage: 'securenow apps scan [--yes]', run: (a, f) => require('./cli/apps').scan(a, f) },
|
|
178
|
+
},
|
|
179
|
+
defaultSub: 'list',
|
|
180
|
+
},
|
|
181
|
+
traces: {
|
|
182
|
+
desc: 'View and analyze traces',
|
|
183
|
+
usage: 'securenow traces <subcommand> [options]',
|
|
184
|
+
sub: {
|
|
185
185
|
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) },
|
|
186
186
|
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) },
|
|
187
187
|
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) },
|
|
188
|
-
},
|
|
189
|
-
defaultSub: 'list',
|
|
190
|
-
},
|
|
191
|
-
logs: {
|
|
192
|
-
desc: 'View application logs',
|
|
193
|
-
usage: 'securenow logs [options]',
|
|
194
|
-
sub: {
|
|
188
|
+
},
|
|
189
|
+
defaultSub: 'list',
|
|
190
|
+
},
|
|
191
|
+
logs: {
|
|
192
|
+
desc: 'View application logs',
|
|
193
|
+
usage: 'securenow logs [options]',
|
|
194
|
+
sub: {
|
|
195
195
|
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) },
|
|
196
196
|
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) },
|
|
197
|
-
},
|
|
198
|
-
defaultSub: 'list',
|
|
199
|
-
},
|
|
197
|
+
},
|
|
198
|
+
defaultSub: 'list',
|
|
199
|
+
},
|
|
200
200
|
notifications: {
|
|
201
201
|
desc: 'Manage notifications',
|
|
202
202
|
usage: 'securenow notifications <subcommand> [options]',
|
|
203
|
-
sub: {
|
|
204
|
-
list: { desc: 'List notifications', flags: { limit: 'Max results', page: 'Page number' }, run: (a, f) => require('./cli/monitor').notificationsList(a, f) },
|
|
205
|
-
read: { desc: 'Mark notification as read', usage: 'securenow notifications read <id>', run: (a, f) => require('./cli/monitor').notificationsRead(a, f) },
|
|
206
|
-
'read-all': { desc: 'Mark all as read', run: () => require('./cli/monitor').notificationsReadAll() },
|
|
207
|
-
unread: { desc: 'Show unread count', run: () => require('./cli/monitor').notificationsUnread() },
|
|
203
|
+
sub: {
|
|
204
|
+
list: { desc: 'List notifications', flags: { limit: 'Max results', page: 'Page number' }, run: (a, f) => require('./cli/monitor').notificationsList(a, f) },
|
|
205
|
+
read: { desc: 'Mark notification as read', usage: 'securenow notifications read <id>', run: (a, f) => require('./cli/monitor').notificationsRead(a, f) },
|
|
206
|
+
'read-all': { desc: 'Mark all as read', run: () => require('./cli/monitor').notificationsReadAll() },
|
|
207
|
+
unread: { desc: 'Show unread count', run: () => require('./cli/monitor').notificationsUnread() },
|
|
208
208
|
},
|
|
209
209
|
defaultSub: 'list',
|
|
210
210
|
},
|
|
@@ -253,20 +253,32 @@ const COMMANDS = {
|
|
|
253
253
|
alerts: {
|
|
254
254
|
desc: 'Manage alerting',
|
|
255
255
|
usage: 'securenow alerts <subcommand> [options]',
|
|
256
|
-
sub: {
|
|
256
|
+
sub: {
|
|
257
257
|
rules: {
|
|
258
|
-
desc: '
|
|
258
|
+
desc: 'Create, list, show, update, test, or tune alert rules',
|
|
259
259
|
flags: {
|
|
260
260
|
json: 'Output as JSON',
|
|
261
|
-
|
|
261
|
+
name: 'With create: rule name',
|
|
262
|
+
description: 'With create: rule description',
|
|
263
|
+
nlp: 'With create: plain-English intent stored on the query',
|
|
264
|
+
text: 'Alias for --nlp',
|
|
265
|
+
category: 'With create: query category (default: custom)',
|
|
266
|
+
severity: 'With create: critical | high | medium | low',
|
|
267
|
+
schedule: 'With create: cron expression (default */15 * * * *)',
|
|
268
|
+
'throttle-minutes': 'With create: notification throttle window in minutes',
|
|
269
|
+
'no-throttle': 'With create: disable throttling',
|
|
270
|
+
'execution-mode': 'With create: scheduled | instant | hybrid',
|
|
271
|
+
channel: 'With create: comma-separated alert channel ids (default: in-app)',
|
|
272
|
+
'query-mapping-id': 'With create: reuse an existing saved query instead of --sql',
|
|
273
|
+
'applications-all': 'With create/update: scope rule to all apps',
|
|
262
274
|
'no-applications-all': 'With update: scope to explicit --apps list',
|
|
263
|
-
apps: 'Comma-separated app keys (with update)',
|
|
275
|
+
apps: 'Comma-separated app keys (with create/update)',
|
|
264
276
|
app: 'Application key for rule tests',
|
|
265
277
|
mode: 'Rule test mode: dry_run or live',
|
|
266
278
|
wait: 'Wait for rule test completion',
|
|
267
|
-
sql: '
|
|
279
|
+
sql: 'Detection/candidate/replacement SQL, @file, or - for stdin',
|
|
268
280
|
query: 'Alias for --sql',
|
|
269
|
-
file: 'Read
|
|
281
|
+
file: 'Read SQL from a file',
|
|
270
282
|
reason: 'Audit reason for a write',
|
|
271
283
|
'apply-globally': 'Required for system query tuning',
|
|
272
284
|
'reactivate-paused': 'Reactivate paused system copies after tuning',
|
|
@@ -278,35 +290,35 @@ const COMMANDS = {
|
|
|
278
290
|
},
|
|
279
291
|
run: (a, f) => require('./cli/security').alertRulesRoute(a, f),
|
|
280
292
|
},
|
|
281
|
-
channels: { desc: 'List alert channels', run: (a, f) => require('./cli/security').alertChannelsList(a, f) },
|
|
282
|
-
history: { desc: 'View alert history', flags: { limit: 'Max results' }, run: (a, f) => require('./cli/security').alertHistoryList(a, f) },
|
|
283
|
-
},
|
|
284
|
-
defaultSub: 'rules',
|
|
285
|
-
},
|
|
286
|
-
fp: {
|
|
287
|
-
desc: 'Manage false-positive exclusion rules',
|
|
288
|
-
usage: 'securenow fp <subcommand> [options]',
|
|
289
|
-
flags: { json: 'Output as JSON', conditions: 'JSON array of conditions', 'match-mode': 'Match mode: all (AND) or any (OR)', 'rule-scope': 'Scope: this_rule | specific_rules | all_existing | any_rule', 'target-rules': 'Comma-separated rule IDs (when --rule-scope specific_rules)', 'path-safe': 'Add path_safe_values condition (standard|strict)', 'query-safe': 'Add query_safe_values condition (standard|strict)', 'query-keys': 'Allowed query param names (comma-separated)', 'ua-safe': 'Add ua_safe_values condition (standard|strict)', 'headers-safe': 'Add headers_safe_values condition (standard|strict)', 'headers-keys': 'Allowed header names (comma-separated)' },
|
|
290
|
-
sub: {
|
|
291
|
-
list: { desc: 'List all exclusion rules', run: (a, f) => require('./cli/fp').list(a, f) },
|
|
292
|
-
show: { desc: 'Show exclusion rule details', usage: 'securenow fp show <id>', run: (a, f) => require('./cli/fp').show(a, f) },
|
|
293
|
-
create: { desc: 'Create an exclusion rule', usage: 'securenow fp create [--conditions \'[...]\'] [--path /api/event] [--method POST] [--path-safe standard] [--ua-safe standard] [--headers-safe standard] [--query-keys page,limit] [--headers-keys host,content-type] [--reason "..."] [--rule-scope any_rule|specific_rules|all_existing] [--target-rules id1,id2]', run: (a, f) => require('./cli/fp').create(a, f) },
|
|
294
|
-
edit: { desc: 'Edit an exclusion rule', usage: 'securenow fp edit <id> [--active true/false] [--conditions \'[...]\']', run: (a, f) => require('./cli/fp').edit(a, f) },
|
|
295
|
-
delete: { desc: 'Delete an exclusion rule', usage: 'securenow fp delete <id> [--yes]', run: (a, f) => require('./cli/fp').remove(a, f) },
|
|
296
|
-
'test-body': { desc: 'Test a request body against conditions', usage: 'securenow fp test-body <body|@file> --conditions \'[...]\'', run: (a, f) => require('./cli/fp').testBody(a, f) },
|
|
297
|
-
'dry-run': { desc: 'Dry-run conditions against live traces (last 3 days)', usage: 'securenow fp dry-run --conditions \'[...]\'', run: (a, f) => require('./cli/fp').dryRun(a, f) },
|
|
298
|
-
'ai-fill': { desc: 'AI-generate exclusion conditions', usage: 'securenow fp ai-fill [--description "..."] [--context \'{"method":"POST",...}\']', run: (a, f) => require('./cli/fp').aiFill(a, f) },
|
|
299
|
-
mark: { desc: 'Mark an IP as false positive on a notification', usage: 'securenow fp mark <notification-id> <ip> [--conditions \'[...]\'] [--reason "..."] [--rule-scope this_rule|specific_rules|all_existing|any_rule] [--target-rules id1,id2]', run: (a, f) => require('./cli/fp').mark(a, f) },
|
|
300
|
-
},
|
|
301
|
-
defaultSub: 'list',
|
|
302
|
-
},
|
|
293
|
+
channels: { desc: 'List alert channels', run: (a, f) => require('./cli/security').alertChannelsList(a, f) },
|
|
294
|
+
history: { desc: 'View alert history', flags: { limit: 'Max results' }, run: (a, f) => require('./cli/security').alertHistoryList(a, f) },
|
|
295
|
+
},
|
|
296
|
+
defaultSub: 'rules',
|
|
297
|
+
},
|
|
298
|
+
fp: {
|
|
299
|
+
desc: 'Manage false-positive exclusion rules',
|
|
300
|
+
usage: 'securenow fp <subcommand> [options]',
|
|
301
|
+
flags: { json: 'Output as JSON', conditions: 'JSON array of conditions', 'match-mode': 'Match mode: all (AND) or any (OR)', 'rule-scope': 'Scope: this_rule | specific_rules | all_existing | any_rule', 'target-rules': 'Comma-separated rule IDs (when --rule-scope specific_rules)', 'path-safe': 'Add path_safe_values condition (standard|strict)', 'query-safe': 'Add query_safe_values condition (standard|strict)', 'query-keys': 'Allowed query param names (comma-separated)', 'ua-safe': 'Add ua_safe_values condition (standard|strict)', 'headers-safe': 'Add headers_safe_values condition (standard|strict)', 'headers-keys': 'Allowed header names (comma-separated)' },
|
|
302
|
+
sub: {
|
|
303
|
+
list: { desc: 'List all exclusion rules', run: (a, f) => require('./cli/fp').list(a, f) },
|
|
304
|
+
show: { desc: 'Show exclusion rule details', usage: 'securenow fp show <id>', run: (a, f) => require('./cli/fp').show(a, f) },
|
|
305
|
+
create: { desc: 'Create an exclusion rule', usage: 'securenow fp create [--conditions \'[...]\'] [--path /api/event] [--method POST] [--path-safe standard] [--ua-safe standard] [--headers-safe standard] [--query-keys page,limit] [--headers-keys host,content-type] [--reason "..."] [--rule-scope any_rule|specific_rules|all_existing] [--target-rules id1,id2]', run: (a, f) => require('./cli/fp').create(a, f) },
|
|
306
|
+
edit: { desc: 'Edit an exclusion rule', usage: 'securenow fp edit <id> [--active true/false] [--conditions \'[...]\']', run: (a, f) => require('./cli/fp').edit(a, f) },
|
|
307
|
+
delete: { desc: 'Delete an exclusion rule', usage: 'securenow fp delete <id> [--yes]', run: (a, f) => require('./cli/fp').remove(a, f) },
|
|
308
|
+
'test-body': { desc: 'Test a request body against conditions', usage: 'securenow fp test-body <body|@file> --conditions \'[...]\'', run: (a, f) => require('./cli/fp').testBody(a, f) },
|
|
309
|
+
'dry-run': { desc: 'Dry-run conditions against live traces (last 3 days)', usage: 'securenow fp dry-run --conditions \'[...]\'', run: (a, f) => require('./cli/fp').dryRun(a, f) },
|
|
310
|
+
'ai-fill': { desc: 'AI-generate exclusion conditions', usage: 'securenow fp ai-fill [--description "..."] [--context \'{"method":"POST",...}\']', run: (a, f) => require('./cli/fp').aiFill(a, f) },
|
|
311
|
+
mark: { desc: 'Mark an IP as false positive on a notification', usage: 'securenow fp mark <notification-id> <ip> [--conditions \'[...]\'] [--reason "..."] [--rule-scope this_rule|specific_rules|all_existing|any_rule] [--target-rules id1,id2]', run: (a, f) => require('./cli/fp').mark(a, f) },
|
|
312
|
+
},
|
|
313
|
+
defaultSub: 'list',
|
|
314
|
+
},
|
|
303
315
|
firewall: {
|
|
304
316
|
desc: 'Firewall status, per-app toggle, and IP testing',
|
|
305
317
|
usage: 'securenow firewall <subcommand> [options]',
|
|
306
318
|
flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
|
|
307
|
-
sub: {
|
|
319
|
+
sub: {
|
|
308
320
|
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) },
|
|
309
|
-
apps: { desc: 'List apps with their firewall on/off state', run: (a, f) => require('./cli/firewall').appsList(a, f) },
|
|
321
|
+
apps: { desc: 'List apps with their firewall on/off state', run: (a, f) => require('./cli/firewall').appsList(a, f) },
|
|
310
322
|
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) },
|
|
311
323
|
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) },
|
|
312
324
|
'test-ip': { desc: 'Check if an IP would be blocked', usage: 'securenow firewall test-ip <ip> [--path /admin/users] [--method GET] [--env production]', flags: { env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').testIp(a, f) },
|
|
@@ -423,8 +435,8 @@ const COMMANDS = {
|
|
|
423
435
|
remove: { desc: 'Unblock an IP (compatibility alias)', usage: 'securenow blocklist remove <id> [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
|
|
424
436
|
stats: { desc: 'Blocklist statistics', run: (a, f) => require('./cli/security').blocklistStats(a, f) },
|
|
425
437
|
},
|
|
426
|
-
defaultSub: 'list',
|
|
427
|
-
},
|
|
438
|
+
defaultSub: 'list',
|
|
439
|
+
},
|
|
428
440
|
allowlist: {
|
|
429
441
|
desc: 'Manage IP allowlist (only allow listed IPs)',
|
|
430
442
|
usage: 'securenow allowlist <subcommand> [options]',
|
|
@@ -432,11 +444,11 @@ const COMMANDS = {
|
|
|
432
444
|
sub: {
|
|
433
445
|
list: { desc: 'List allowed IPs', run: (a, f) => require('./cli/security').allowlistList(a, f) },
|
|
434
446
|
add: { desc: 'Allow an IP', usage: 'securenow allowlist add <ip> [--app <key>] [--env local] [--label <label>] [--reason <reason>]', run: (a, f) => require('./cli/security').allowlistAdd(a, f) },
|
|
435
|
-
remove: { desc: 'Remove an allowed IP', usage: 'securenow allowlist remove <id>', run: (a, f) => require('./cli/security').allowlistRemove(a, f) },
|
|
436
|
-
stats: { desc: 'Allowlist statistics', run: (a, f) => require('./cli/security').allowlistStats(a, f) },
|
|
437
|
-
},
|
|
438
|
-
defaultSub: 'list',
|
|
439
|
-
},
|
|
447
|
+
remove: { desc: 'Remove an allowed IP', usage: 'securenow allowlist remove <id>', run: (a, f) => require('./cli/security').allowlistRemove(a, f) },
|
|
448
|
+
stats: { desc: 'Allowlist statistics', run: (a, f) => require('./cli/security').allowlistStats(a, f) },
|
|
449
|
+
},
|
|
450
|
+
defaultSub: 'list',
|
|
451
|
+
},
|
|
440
452
|
trusted: {
|
|
441
453
|
desc: 'Manage trusted IPs',
|
|
442
454
|
usage: 'securenow trusted <subcommand> [options]',
|
|
@@ -444,43 +456,43 @@ const COMMANDS = {
|
|
|
444
456
|
sub: {
|
|
445
457
|
list: { desc: 'List trusted IPs', run: (a, f) => require('./cli/security').trustedList(a, f) },
|
|
446
458
|
add: { desc: 'Add trusted IP', usage: 'securenow trusted add <ip> [--app <key>] [--env local] [--label <label>]', run: (a, f) => require('./cli/security').trustedAdd(a, f) },
|
|
447
|
-
remove: { desc: 'Remove trusted IP', usage: 'securenow trusted remove <id>', run: (a, f) => require('./cli/security').trustedRemove(a, f) },
|
|
448
|
-
},
|
|
449
|
-
defaultSub: 'list',
|
|
450
|
-
},
|
|
451
|
-
ip: {
|
|
452
|
-
desc: 'IP intelligence lookup',
|
|
453
|
-
usage: 'securenow ip <ip-address>',
|
|
454
|
-
sub: {
|
|
455
|
-
lookup: { desc: 'Look up IP intelligence', run: (a, f) => require('./cli/security').ipLookup(a, f) },
|
|
459
|
+
remove: { desc: 'Remove trusted IP', usage: 'securenow trusted remove <id>', run: (a, f) => require('./cli/security').trustedRemove(a, f) },
|
|
460
|
+
},
|
|
461
|
+
defaultSub: 'list',
|
|
462
|
+
},
|
|
463
|
+
ip: {
|
|
464
|
+
desc: 'IP intelligence lookup',
|
|
465
|
+
usage: 'securenow ip <ip-address>',
|
|
466
|
+
sub: {
|
|
467
|
+
lookup: { desc: 'Look up IP intelligence', run: (a, f) => require('./cli/security').ipLookup(a, f) },
|
|
456
468
|
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) },
|
|
457
|
-
},
|
|
458
|
-
defaultAction: (a, f) => require('./cli/security').ipLookup(a, f),
|
|
459
|
-
},
|
|
460
|
-
forensics: {
|
|
461
|
-
desc: 'Run forensic queries (natural language → SQL)',
|
|
462
|
-
usage: 'securenow forensics <query> [--app <key>]',
|
|
463
|
-
sub: {
|
|
469
|
+
},
|
|
470
|
+
defaultAction: (a, f) => require('./cli/security').ipLookup(a, f),
|
|
471
|
+
},
|
|
472
|
+
forensics: {
|
|
473
|
+
desc: 'Run forensic queries (natural language → SQL)',
|
|
474
|
+
usage: 'securenow forensics <query> [--app <key>]',
|
|
475
|
+
sub: {
|
|
464
476
|
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) },
|
|
465
477
|
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) },
|
|
466
|
-
library: { desc: 'View saved queries', run: (a, f) => require('./cli/security').forensicsLibrary(a, f) },
|
|
467
|
-
},
|
|
468
|
-
defaultAction: (a, f) => require('./cli/security').forensicsQuery(a, f),
|
|
469
|
-
},
|
|
470
|
-
instances: {
|
|
471
|
-
desc: 'Manage ClickHouse instances',
|
|
472
|
-
usage: 'securenow instances <subcommand> [options]',
|
|
473
|
-
sub: {
|
|
474
|
-
list: { desc: 'List instances', run: (a, f) => require('./cli/security').instancesList(a, f) },
|
|
475
|
-
test: { desc: 'Test instance connection', usage: 'securenow instances test <id>', run: (a, f) => require('./cli/security').instancesTest(a, f) },
|
|
476
|
-
},
|
|
477
|
-
defaultSub: 'list',
|
|
478
|
-
},
|
|
479
|
-
analytics: {
|
|
480
|
-
desc: 'View response analytics',
|
|
481
|
-
usage: 'securenow analytics [--app <key>]',
|
|
482
|
-
run: (a, f) => require('./cli/security').analytics(a, f),
|
|
483
|
-
},
|
|
478
|
+
library: { desc: 'View saved queries', run: (a, f) => require('./cli/security').forensicsLibrary(a, f) },
|
|
479
|
+
},
|
|
480
|
+
defaultAction: (a, f) => require('./cli/security').forensicsQuery(a, f),
|
|
481
|
+
},
|
|
482
|
+
instances: {
|
|
483
|
+
desc: 'Manage ClickHouse instances',
|
|
484
|
+
usage: 'securenow instances <subcommand> [options]',
|
|
485
|
+
sub: {
|
|
486
|
+
list: { desc: 'List instances', run: (a, f) => require('./cli/security').instancesList(a, f) },
|
|
487
|
+
test: { desc: 'Test instance connection', usage: 'securenow instances test <id>', run: (a, f) => require('./cli/security').instancesTest(a, f) },
|
|
488
|
+
},
|
|
489
|
+
defaultSub: 'list',
|
|
490
|
+
},
|
|
491
|
+
analytics: {
|
|
492
|
+
desc: 'View response analytics',
|
|
493
|
+
usage: 'securenow analytics [--app <key>]',
|
|
494
|
+
run: (a, f) => require('./cli/security').analytics(a, f),
|
|
495
|
+
},
|
|
484
496
|
status: {
|
|
485
497
|
desc: 'Dashboard overview',
|
|
486
498
|
usage: 'securenow status [--app <key>] [--env local|production|all]',
|
|
@@ -491,89 +503,89 @@ const COMMANDS = {
|
|
|
491
503
|
},
|
|
492
504
|
run: (a, f) => require('./cli/monitor').status(a, f),
|
|
493
505
|
},
|
|
494
|
-
config: {
|
|
495
|
-
desc: 'Manage CLI configuration',
|
|
496
|
-
usage: 'securenow config <set|get> [key] [value]',
|
|
497
|
-
sub: {
|
|
498
|
-
set: {
|
|
499
|
-
desc: 'Set a config value',
|
|
500
|
-
usage: 'securenow config set <key> <value>',
|
|
501
|
-
run: (a) => {
|
|
502
|
-
const conf = require('./cli/config');
|
|
503
|
-
const [key, ...rest] = a;
|
|
504
|
-
const value = rest.join(' ');
|
|
505
|
-
if (!key || !value) { ui.error('Usage: securenow config set <key> <value>'); process.exit(1); }
|
|
506
|
-
conf.setConfigValue(key, value);
|
|
507
|
-
ui.success(`${key} = ${value}`);
|
|
508
|
-
},
|
|
509
|
-
},
|
|
510
|
-
get: {
|
|
511
|
-
desc: 'Get a config value',
|
|
512
|
-
usage: 'securenow config get [key]',
|
|
513
|
-
run: (a) => {
|
|
514
|
-
const conf = require('./cli/config');
|
|
515
|
-
const key = a[0];
|
|
516
|
-
if (key) {
|
|
517
|
-
const val = conf.getConfigValue(key);
|
|
518
|
-
console.log(val != null ? val : ui.c.dim('(not set)'));
|
|
519
|
-
} else {
|
|
520
|
-
const all = conf.loadConfig();
|
|
521
|
-
console.log('');
|
|
522
|
-
ui.keyValue(Object.entries(all).map(([k, v]) => [k, v != null ? String(v) : ui.c.dim('(not set)')]));
|
|
523
|
-
console.log('');
|
|
524
|
-
}
|
|
525
|
-
},
|
|
526
|
-
},
|
|
527
|
-
path: {
|
|
528
|
-
desc: 'Show config file path',
|
|
529
|
-
run: () => {
|
|
530
|
-
const conf = require('./cli/config');
|
|
531
|
-
console.log(`Config: ${conf.CONFIG_FILE}`);
|
|
532
|
-
console.log(`Credentials: ${conf.CREDENTIALS_FILE}`);
|
|
533
|
-
if (conf.hasLocalCredentials()) {
|
|
534
|
-
console.log(`Local creds: ${conf.LOCAL_CREDENTIALS_FILE} ${require('./cli/ui').c.green('(active)')}`);
|
|
535
|
-
}
|
|
536
|
-
console.log(`Auth source: ${conf.getAuthSource()}`);
|
|
537
|
-
},
|
|
538
|
-
},
|
|
539
|
-
},
|
|
540
|
-
},
|
|
541
|
-
run: {
|
|
542
|
-
desc: 'Run a Node.js app with automatic OTel instrumentation',
|
|
543
|
-
usage: 'securenow run [node-flags] [--firewall-only] <script> [app-args]',
|
|
544
|
-
flags: {
|
|
545
|
-
watch: 'Enable Node.js watch mode',
|
|
546
|
-
inspect: 'Enable Node.js inspector',
|
|
547
|
-
'firewall-only': 'Preload firewall without OTel tracing overhead',
|
|
548
|
-
},
|
|
549
|
-
rawArgv: true,
|
|
550
|
-
run: (rawArgs) => require('./cli/run').run(rawArgs),
|
|
551
|
-
},
|
|
552
|
-
redact: {
|
|
553
|
-
desc: 'Redact sensitive fields from a JSON payload',
|
|
554
|
-
usage: "securenow redact '<json>' [--fields f1,f2] [--json]",
|
|
555
|
-
flags: { fields: 'Comma-separated extra field names to redact (added to defaults)' },
|
|
556
|
-
run: (a, f) => require('./cli/utils').redact(a, f),
|
|
557
|
-
},
|
|
558
|
-
cidr: {
|
|
559
|
-
desc: 'CIDR utilities (match and parse)',
|
|
560
|
-
usage: 'securenow cidr <match|parse> ...',
|
|
561
|
-
sub: {
|
|
562
|
-
match: {
|
|
563
|
-
desc: 'Check if an IP matches a CIDR list',
|
|
564
|
-
usage: 'securenow cidr match <ip> <cidr1,cidr2,...>',
|
|
565
|
-
run: (a, f) => require('./cli/utils').cidrMatch(a, f),
|
|
566
|
-
},
|
|
567
|
-
parse: {
|
|
568
|
-
desc: 'Parse a CIDR and show network/broadcast/size',
|
|
569
|
-
usage: 'securenow cidr parse <cidr>',
|
|
570
|
-
run: (a, f) => require('./cli/utils').cidrParse(a, f),
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
|
-
},
|
|
574
|
-
log: {
|
|
575
|
-
desc: 'Emit logs via OTLP (for scripts, cron, debugging)',
|
|
576
|
-
usage: 'securenow log send <message> [--level info|warn|error] [--attrs k=v,k=v]',
|
|
506
|
+
config: {
|
|
507
|
+
desc: 'Manage CLI configuration',
|
|
508
|
+
usage: 'securenow config <set|get> [key] [value]',
|
|
509
|
+
sub: {
|
|
510
|
+
set: {
|
|
511
|
+
desc: 'Set a config value',
|
|
512
|
+
usage: 'securenow config set <key> <value>',
|
|
513
|
+
run: (a) => {
|
|
514
|
+
const conf = require('./cli/config');
|
|
515
|
+
const [key, ...rest] = a;
|
|
516
|
+
const value = rest.join(' ');
|
|
517
|
+
if (!key || !value) { ui.error('Usage: securenow config set <key> <value>'); process.exit(1); }
|
|
518
|
+
conf.setConfigValue(key, value);
|
|
519
|
+
ui.success(`${key} = ${value}`);
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
get: {
|
|
523
|
+
desc: 'Get a config value',
|
|
524
|
+
usage: 'securenow config get [key]',
|
|
525
|
+
run: (a) => {
|
|
526
|
+
const conf = require('./cli/config');
|
|
527
|
+
const key = a[0];
|
|
528
|
+
if (key) {
|
|
529
|
+
const val = conf.getConfigValue(key);
|
|
530
|
+
console.log(val != null ? val : ui.c.dim('(not set)'));
|
|
531
|
+
} else {
|
|
532
|
+
const all = conf.loadConfig();
|
|
533
|
+
console.log('');
|
|
534
|
+
ui.keyValue(Object.entries(all).map(([k, v]) => [k, v != null ? String(v) : ui.c.dim('(not set)')]));
|
|
535
|
+
console.log('');
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
path: {
|
|
540
|
+
desc: 'Show config file path',
|
|
541
|
+
run: () => {
|
|
542
|
+
const conf = require('./cli/config');
|
|
543
|
+
console.log(`Config: ${conf.CONFIG_FILE}`);
|
|
544
|
+
console.log(`Credentials: ${conf.CREDENTIALS_FILE}`);
|
|
545
|
+
if (conf.hasLocalCredentials()) {
|
|
546
|
+
console.log(`Local creds: ${conf.LOCAL_CREDENTIALS_FILE} ${require('./cli/ui').c.green('(active)')}`);
|
|
547
|
+
}
|
|
548
|
+
console.log(`Auth source: ${conf.getAuthSource()}`);
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
run: {
|
|
554
|
+
desc: 'Run a Node.js app with automatic OTel instrumentation',
|
|
555
|
+
usage: 'securenow run [node-flags] [--firewall-only] <script> [app-args]',
|
|
556
|
+
flags: {
|
|
557
|
+
watch: 'Enable Node.js watch mode',
|
|
558
|
+
inspect: 'Enable Node.js inspector',
|
|
559
|
+
'firewall-only': 'Preload firewall without OTel tracing overhead',
|
|
560
|
+
},
|
|
561
|
+
rawArgv: true,
|
|
562
|
+
run: (rawArgs) => require('./cli/run').run(rawArgs),
|
|
563
|
+
},
|
|
564
|
+
redact: {
|
|
565
|
+
desc: 'Redact sensitive fields from a JSON payload',
|
|
566
|
+
usage: "securenow redact '<json>' [--fields f1,f2] [--json]",
|
|
567
|
+
flags: { fields: 'Comma-separated extra field names to redact (added to defaults)' },
|
|
568
|
+
run: (a, f) => require('./cli/utils').redact(a, f),
|
|
569
|
+
},
|
|
570
|
+
cidr: {
|
|
571
|
+
desc: 'CIDR utilities (match and parse)',
|
|
572
|
+
usage: 'securenow cidr <match|parse> ...',
|
|
573
|
+
sub: {
|
|
574
|
+
match: {
|
|
575
|
+
desc: 'Check if an IP matches a CIDR list',
|
|
576
|
+
usage: 'securenow cidr match <ip> <cidr1,cidr2,...>',
|
|
577
|
+
run: (a, f) => require('./cli/utils').cidrMatch(a, f),
|
|
578
|
+
},
|
|
579
|
+
parse: {
|
|
580
|
+
desc: 'Parse a CIDR and show network/broadcast/size',
|
|
581
|
+
usage: 'securenow cidr parse <cidr>',
|
|
582
|
+
run: (a, f) => require('./cli/utils').cidrParse(a, f),
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
log: {
|
|
587
|
+
desc: 'Emit logs via OTLP (for scripts, cron, debugging)',
|
|
588
|
+
usage: 'securenow log send <message> [--level info|warn|error] [--attrs k=v,k=v]',
|
|
577
589
|
sub: {
|
|
578
590
|
send: {
|
|
579
591
|
desc: 'Send a single log record to the OTLP collector',
|
|
@@ -583,11 +595,32 @@ const COMMANDS = {
|
|
|
583
595
|
level: 'Severity (trace|debug|info|warn|error|fatal)',
|
|
584
596
|
attrs: 'Comma-separated key=value attributes',
|
|
585
597
|
},
|
|
586
|
-
run: (a, f) => require('./cli/diagnostics').logSend(a, f),
|
|
587
|
-
},
|
|
588
|
-
},
|
|
589
|
-
defaultSub: 'send',
|
|
590
|
-
},
|
|
598
|
+
run: (a, f) => require('./cli/diagnostics').logSend(a, f),
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
defaultSub: 'send',
|
|
602
|
+
},
|
|
603
|
+
event: {
|
|
604
|
+
desc: 'Emit a custom security event to SecureNow (for scripts, testing, non-JS apps)',
|
|
605
|
+
usage: 'securenow event send <type> [--user <id>] [--session <id>] [--ip <ip>] [--attrs k=v,k=v]',
|
|
606
|
+
sub: {
|
|
607
|
+
send: {
|
|
608
|
+
desc: 'Send a single custom event to the /v1/events ingest',
|
|
609
|
+
flags: {
|
|
610
|
+
env: 'Deployment environment for this event (defaults to credentials file)',
|
|
611
|
+
environment: 'Alias for --env',
|
|
612
|
+
user: 'End-user / account id (enduser.id)',
|
|
613
|
+
session: 'Session id (session.id)',
|
|
614
|
+
ip: 'End-user client IP',
|
|
615
|
+
'user-agent': 'End-user agent string',
|
|
616
|
+
level: 'Severity (trace|debug|info|warn|error|fatal)',
|
|
617
|
+
attrs: 'Comma-separated key=value attributes',
|
|
618
|
+
},
|
|
619
|
+
run: (a, f) => require('./cli/diagnostics').eventSend(a, f),
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
defaultSub: 'send',
|
|
623
|
+
},
|
|
591
624
|
'test-span': {
|
|
592
625
|
desc: 'Emit a test span to verify collector connectivity',
|
|
593
626
|
usage: 'securenow test-span [<span-name>] [--env local|production]',
|
|
@@ -597,11 +630,11 @@ const COMMANDS = {
|
|
|
597
630
|
},
|
|
598
631
|
run: (a, f) => require('./cli/diagnostics').testSpan(a, f),
|
|
599
632
|
},
|
|
600
|
-
doctor: {
|
|
601
|
-
desc: 'Diagnose SecureNow configuration and collector connectivity',
|
|
602
|
-
usage: 'securenow doctor [--json]',
|
|
603
|
-
run: (a, f) => require('./cli/diagnostics').doctor(a, f),
|
|
604
|
-
},
|
|
633
|
+
doctor: {
|
|
634
|
+
desc: 'Diagnose SecureNow configuration and collector connectivity',
|
|
635
|
+
usage: 'securenow doctor [--json]',
|
|
636
|
+
run: (a, f) => require('./cli/diagnostics').doctor(a, f),
|
|
637
|
+
},
|
|
605
638
|
env: {
|
|
606
639
|
desc: 'Show resolved SecureNow configuration (service name, endpoints, credentials)',
|
|
607
640
|
usage: 'securenow env [--json]',
|
|
@@ -615,163 +648,163 @@ const COMMANDS = {
|
|
|
615
648
|
version: {
|
|
616
649
|
desc: 'Show CLI version',
|
|
617
650
|
run: () => {
|
|
618
|
-
try {
|
|
619
|
-
const pkg = require('./package.json');
|
|
620
|
-
console.log(`securenow v${pkg.version}`);
|
|
621
|
-
} catch {
|
|
622
|
-
console.log('securenow (version unknown)');
|
|
623
|
-
}
|
|
624
|
-
},
|
|
625
|
-
},
|
|
626
|
-
};
|
|
627
|
-
|
|
628
|
-
// ── Help System ──
|
|
629
|
-
|
|
630
|
-
function showBanner() {
|
|
631
|
-
console.log('');
|
|
632
|
-
console.log(` ${ui.c.bold(ui.c.cyan('SecureNow CLI'))} ${ui.c.dim('— Security observability from the terminal')}`);
|
|
633
|
-
console.log('');
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
function showHelp(commandName) {
|
|
637
|
-
if (commandName && COMMANDS[commandName]) {
|
|
638
|
-
const cmd = COMMANDS[commandName];
|
|
639
|
-
showBanner();
|
|
640
|
-
console.log(` ${ui.c.bold(commandName)} — ${cmd.desc}`);
|
|
641
|
-
console.log('');
|
|
642
|
-
if (cmd.usage) {
|
|
643
|
-
console.log(` ${ui.c.bold('USAGE')}`);
|
|
644
|
-
console.log(` ${cmd.usage}`);
|
|
645
|
-
console.log('');
|
|
646
|
-
}
|
|
647
|
-
if (cmd.sub) {
|
|
648
|
-
console.log(` ${ui.c.bold('SUBCOMMANDS')}`);
|
|
649
|
-
const entries = Object.entries(cmd.sub);
|
|
650
|
-
const maxLen = entries.reduce((m, [k]) => Math.max(m, k.length), 0);
|
|
651
|
-
for (const [name, sub] of entries) {
|
|
652
|
-
console.log(` ${ui.c.cyan(name.padEnd(maxLen + 2))} ${sub.desc}`);
|
|
653
|
-
}
|
|
654
|
-
console.log('');
|
|
655
|
-
}
|
|
656
|
-
if (cmd.flags) {
|
|
657
|
-
console.log(` ${ui.c.bold('FLAGS')}`);
|
|
658
|
-
for (const [flag, desc] of Object.entries(cmd.flags)) {
|
|
659
|
-
console.log(` --${ui.c.cyan(flag.padEnd(16))} ${desc}`);
|
|
660
|
-
}
|
|
661
|
-
console.log('');
|
|
662
|
-
}
|
|
663
|
-
console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
|
|
664
|
-
console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
|
|
665
|
-
console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help`);
|
|
666
|
-
console.log('');
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
showBanner();
|
|
671
|
-
|
|
672
|
-
const groups = {
|
|
673
|
-
'Run': ['run'],
|
|
651
|
+
try {
|
|
652
|
+
const pkg = require('./package.json');
|
|
653
|
+
console.log(`securenow v${pkg.version}`);
|
|
654
|
+
} catch {
|
|
655
|
+
console.log('securenow (version unknown)');
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
// ── Help System ──
|
|
662
|
+
|
|
663
|
+
function showBanner() {
|
|
664
|
+
console.log('');
|
|
665
|
+
console.log(` ${ui.c.bold(ui.c.cyan('SecureNow CLI'))} ${ui.c.dim('— Security observability from the terminal')}`);
|
|
666
|
+
console.log('');
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function showHelp(commandName) {
|
|
670
|
+
if (commandName && COMMANDS[commandName]) {
|
|
671
|
+
const cmd = COMMANDS[commandName];
|
|
672
|
+
showBanner();
|
|
673
|
+
console.log(` ${ui.c.bold(commandName)} — ${cmd.desc}`);
|
|
674
|
+
console.log('');
|
|
675
|
+
if (cmd.usage) {
|
|
676
|
+
console.log(` ${ui.c.bold('USAGE')}`);
|
|
677
|
+
console.log(` ${cmd.usage}`);
|
|
678
|
+
console.log('');
|
|
679
|
+
}
|
|
680
|
+
if (cmd.sub) {
|
|
681
|
+
console.log(` ${ui.c.bold('SUBCOMMANDS')}`);
|
|
682
|
+
const entries = Object.entries(cmd.sub);
|
|
683
|
+
const maxLen = entries.reduce((m, [k]) => Math.max(m, k.length), 0);
|
|
684
|
+
for (const [name, sub] of entries) {
|
|
685
|
+
console.log(` ${ui.c.cyan(name.padEnd(maxLen + 2))} ${sub.desc}`);
|
|
686
|
+
}
|
|
687
|
+
console.log('');
|
|
688
|
+
}
|
|
689
|
+
if (cmd.flags) {
|
|
690
|
+
console.log(` ${ui.c.bold('FLAGS')}`);
|
|
691
|
+
for (const [flag, desc] of Object.entries(cmd.flags)) {
|
|
692
|
+
console.log(` --${ui.c.cyan(flag.padEnd(16))} ${desc}`);
|
|
693
|
+
}
|
|
694
|
+
console.log('');
|
|
695
|
+
}
|
|
696
|
+
console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
|
|
697
|
+
console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
|
|
698
|
+
console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help`);
|
|
699
|
+
console.log('');
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
showBanner();
|
|
704
|
+
|
|
705
|
+
const groups = {
|
|
706
|
+
'Run': ['run'],
|
|
674
707
|
'Authentication': ['login', 'logout', 'whoami', 'credentials'],
|
|
675
|
-
'Applications': ['apps', 'init', 'status'],
|
|
676
|
-
'Observe': ['traces', 'logs', 'analytics'],
|
|
708
|
+
'Applications': ['apps', 'init', 'status'],
|
|
709
|
+
'Observe': ['traces', 'logs', 'analytics'],
|
|
677
710
|
'Detect & Respond': ['human', 'notifications', 'alerts', 'fp'],
|
|
678
|
-
'Investigate': ['ip', 'forensics'],
|
|
711
|
+
'Investigate': ['ip', 'forensics'],
|
|
679
712
|
'Firewall': ['firewall'],
|
|
680
713
|
'Remediation': ['automation', 'ratelimit', 'blocklist', 'allowlist', 'trusted'],
|
|
681
|
-
'Telemetry': ['log', 'test-span'],
|
|
714
|
+
'Telemetry': ['log', 'event', 'test-span'],
|
|
682
715
|
'Utilities': ['redact', 'cidr', 'doctor', 'env', 'mcp'],
|
|
683
|
-
'Settings': ['instances', 'config', 'version'],
|
|
684
|
-
};
|
|
685
|
-
|
|
686
|
-
for (const [group, cmds] of Object.entries(groups)) {
|
|
687
|
-
console.log(` ${ui.c.bold(group)}`);
|
|
688
|
-
for (const name of cmds) {
|
|
689
|
-
const cmd = COMMANDS[name];
|
|
690
|
-
if (cmd) {
|
|
691
|
-
console.log(` ${ui.c.cyan(name.padEnd(18))} ${cmd.desc}`);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
console.log('');
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
|
|
698
|
-
console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
|
|
699
|
-
console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help for a command`);
|
|
700
|
-
console.log('');
|
|
701
|
-
console.log(` ${ui.c.dim('Run')} securenow help <command> ${ui.c.dim('for detailed usage')}`);
|
|
702
|
-
console.log('');
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// ── Main Router ──
|
|
706
|
-
|
|
707
|
-
async function main() {
|
|
708
|
-
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
709
|
-
const cmdName = positional[0] || 'help';
|
|
710
|
-
|
|
711
|
-
if (cmdName === 'help' || flags.help) {
|
|
712
|
-
showHelp(flags.help === true ? positional[0] : flags.help || positional[1] || (cmdName !== 'help' ? cmdName : null));
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
const cmd = COMMANDS[cmdName];
|
|
717
|
-
if (!cmd) {
|
|
718
|
-
// Auto-detect: if the first arg looks like a file path, treat it as `securenow run <file>`
|
|
719
|
-
if (/\.(m?[jt]sx?|cjs)$/.test(cmdName) || cmdName.includes('/') || cmdName.includes('\\')) {
|
|
720
|
-
return COMMANDS.run.run(process.argv.slice(2));
|
|
721
|
-
}
|
|
722
|
-
ui.error(`Unknown command: ${cmdName}`);
|
|
723
|
-
ui.info('Run `securenow help` for a list of commands.');
|
|
724
|
-
process.exit(1);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
if (cmd.rawArgv) {
|
|
728
|
-
// Pass raw argv (everything after the command name) so the command can
|
|
729
|
-
// forward flags like --watch, --inspect verbatim to child processes.
|
|
730
|
-
const cmdIdx = process.argv.indexOf(cmdName);
|
|
731
|
-
const rawArgs = cmdIdx !== -1 ? process.argv.slice(cmdIdx + 1) : positional.slice(1);
|
|
732
|
-
await cmd.run(rawArgs);
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
if (cmd.run && !cmd.sub) {
|
|
737
|
-
await cmd.run(positional.slice(1), flags);
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
if (cmd.sub) {
|
|
742
|
-
let subName = positional[1];
|
|
743
|
-
let subArgs = positional.slice(2);
|
|
744
|
-
|
|
745
|
-
if (subName && cmd.sub[subName]) {
|
|
746
|
-
if (flags.help) {
|
|
747
|
-
showHelp(cmdName);
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
await cmd.sub[subName].run(subArgs, flags);
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (cmd.defaultAction) {
|
|
755
|
-
const allArgs = subName ? [subName, ...subArgs] : [];
|
|
756
|
-
await cmd.defaultAction(allArgs, flags);
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
if (cmd.defaultSub) {
|
|
761
|
-
const allArgs = subName ? [subName, ...subArgs] : [];
|
|
762
|
-
await cmd.sub[cmd.defaultSub].run(allArgs, flags);
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
showHelp(cmdName);
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
if (cmd.run) {
|
|
771
|
-
await cmd.run(positional.slice(1), flags);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
716
|
+
'Settings': ['instances', 'config', 'version'],
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
for (const [group, cmds] of Object.entries(groups)) {
|
|
720
|
+
console.log(` ${ui.c.bold(group)}`);
|
|
721
|
+
for (const name of cmds) {
|
|
722
|
+
const cmd = COMMANDS[name];
|
|
723
|
+
if (cmd) {
|
|
724
|
+
console.log(` ${ui.c.cyan(name.padEnd(18))} ${cmd.desc}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
console.log('');
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
|
|
731
|
+
console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
|
|
732
|
+
console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help for a command`);
|
|
733
|
+
console.log('');
|
|
734
|
+
console.log(` ${ui.c.dim('Run')} securenow help <command> ${ui.c.dim('for detailed usage')}`);
|
|
735
|
+
console.log('');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// ── Main Router ──
|
|
739
|
+
|
|
740
|
+
async function main() {
|
|
741
|
+
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
742
|
+
const cmdName = positional[0] || 'help';
|
|
743
|
+
|
|
744
|
+
if (cmdName === 'help' || flags.help) {
|
|
745
|
+
showHelp(flags.help === true ? positional[0] : flags.help || positional[1] || (cmdName !== 'help' ? cmdName : null));
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const cmd = COMMANDS[cmdName];
|
|
750
|
+
if (!cmd) {
|
|
751
|
+
// Auto-detect: if the first arg looks like a file path, treat it as `securenow run <file>`
|
|
752
|
+
if (/\.(m?[jt]sx?|cjs)$/.test(cmdName) || cmdName.includes('/') || cmdName.includes('\\')) {
|
|
753
|
+
return COMMANDS.run.run(process.argv.slice(2));
|
|
754
|
+
}
|
|
755
|
+
ui.error(`Unknown command: ${cmdName}`);
|
|
756
|
+
ui.info('Run `securenow help` for a list of commands.');
|
|
757
|
+
process.exit(1);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (cmd.rawArgv) {
|
|
761
|
+
// Pass raw argv (everything after the command name) so the command can
|
|
762
|
+
// forward flags like --watch, --inspect verbatim to child processes.
|
|
763
|
+
const cmdIdx = process.argv.indexOf(cmdName);
|
|
764
|
+
const rawArgs = cmdIdx !== -1 ? process.argv.slice(cmdIdx + 1) : positional.slice(1);
|
|
765
|
+
await cmd.run(rawArgs);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (cmd.run && !cmd.sub) {
|
|
770
|
+
await cmd.run(positional.slice(1), flags);
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (cmd.sub) {
|
|
775
|
+
let subName = positional[1];
|
|
776
|
+
let subArgs = positional.slice(2);
|
|
777
|
+
|
|
778
|
+
if (subName && cmd.sub[subName]) {
|
|
779
|
+
if (flags.help) {
|
|
780
|
+
showHelp(cmdName);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
await cmd.sub[subName].run(subArgs, flags);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (cmd.defaultAction) {
|
|
788
|
+
const allArgs = subName ? [subName, ...subArgs] : [];
|
|
789
|
+
await cmd.defaultAction(allArgs, flags);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (cmd.defaultSub) {
|
|
794
|
+
const allArgs = subName ? [subName, ...subArgs] : [];
|
|
795
|
+
await cmd.sub[cmd.defaultSub].run(allArgs, flags);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
showHelp(cmdName);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (cmd.run) {
|
|
804
|
+
await cmd.run(positional.slice(1), flags);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
775
808
|
main().catch((err) => {
|
|
776
809
|
if (err.name !== 'CLIError') {
|
|
777
810
|
ui.error(err.message || 'An unexpected error occurred');
|