securenow 7.6.9 → 7.7.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 +12 -1
- package/SKILL-CLI.md +29 -16
- package/cli/automation.js +275 -0
- package/cli/firewall.js +29 -12
- package/cli/human.js +96 -2
- package/cli/security.js +171 -42
- package/cli.js +71 -28
- package/mcp/catalog.js +327 -15
- package/nextjs.js +22 -23
- package/nuxt-server-plugin.mjs +13 -8
- package/package.json +1 -1
- package/resolve-ip.js +135 -60
- package/tracing.js +25 -4
package/cli/security.js
CHANGED
|
@@ -38,12 +38,18 @@ async function alertRulesRoute(args, flags) {
|
|
|
38
38
|
if (sub === 'show') {
|
|
39
39
|
return alertRuleShow(args.slice(1), flags);
|
|
40
40
|
}
|
|
41
|
-
if (sub === 'update') {
|
|
42
|
-
return alertRuleUpdate(args.slice(1), flags);
|
|
43
|
-
}
|
|
44
|
-
if (sub === '
|
|
45
|
-
return
|
|
46
|
-
}
|
|
41
|
+
if (sub === 'update') {
|
|
42
|
+
return alertRuleUpdate(args.slice(1), flags);
|
|
43
|
+
}
|
|
44
|
+
if (sub === 'test') {
|
|
45
|
+
return alertRuleTest(args.slice(1), flags);
|
|
46
|
+
}
|
|
47
|
+
if (sub === 'exclusions') {
|
|
48
|
+
return alertRuleExclusions(args.slice(1), flags);
|
|
49
|
+
}
|
|
50
|
+
if (sub === 'list') {
|
|
51
|
+
return alertRulesList(args.slice(1), flags);
|
|
52
|
+
}
|
|
47
53
|
return alertRulesList(args, flags);
|
|
48
54
|
}
|
|
49
55
|
|
|
@@ -115,7 +121,7 @@ async function alertRuleShow(args, flags) {
|
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
123
|
|
|
118
|
-
async function alertRuleUpdate(args, flags) {
|
|
124
|
+
async function alertRuleUpdate(args, flags) {
|
|
119
125
|
requireAuth();
|
|
120
126
|
const id = args[0];
|
|
121
127
|
if (!id) {
|
|
@@ -172,7 +178,97 @@ async function alertRuleUpdate(args, flags) {
|
|
|
172
178
|
s.fail('Failed to update alert rule');
|
|
173
179
|
throw err;
|
|
174
180
|
}
|
|
175
|
-
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function alertRuleTest(args, flags) {
|
|
184
|
+
requireAuth();
|
|
185
|
+
const id = args[0];
|
|
186
|
+
if (!id) {
|
|
187
|
+
ui.error('Usage: securenow alerts rules test <rule-id> [--app <key>] [--mode dry_run] [--wait]');
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const body = {};
|
|
192
|
+
if (flags.app) body.applicationKey = flags.app;
|
|
193
|
+
if (flags.mode) body.mode = flags.mode;
|
|
194
|
+
const s = ui.spinner('Starting alert rule test');
|
|
195
|
+
try {
|
|
196
|
+
let data = await api.post(`/alert-rules/${id}/test`, body);
|
|
197
|
+
if (flags.wait && data.testId) {
|
|
198
|
+
s.update('Waiting for alert rule test results');
|
|
199
|
+
for (let i = 0; i < 40; i++) {
|
|
200
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
201
|
+
data = await api.get(`/alert-rules/${id}/test/${data.testId}`);
|
|
202
|
+
if (['complete', 'failed'].includes(data.status)) break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
s.stop('Alert rule test ready');
|
|
206
|
+
if (flags.json) { ui.json(data); return; }
|
|
207
|
+
console.log('');
|
|
208
|
+
ui.keyValue([
|
|
209
|
+
['Test ID', data.testId || '-'],
|
|
210
|
+
['Mode', data.mode || body.mode || 'live'],
|
|
211
|
+
['Status', data.status || '-'],
|
|
212
|
+
['Result count', String(data.resultCount ?? '-')],
|
|
213
|
+
['Error', data.error || '-'],
|
|
214
|
+
]);
|
|
215
|
+
console.log('');
|
|
216
|
+
} catch (err) {
|
|
217
|
+
s.fail('Failed to test alert rule');
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function alertRuleExclusions(args, flags) {
|
|
223
|
+
requireAuth();
|
|
224
|
+
const id = args[0];
|
|
225
|
+
const action = args[1] || 'list';
|
|
226
|
+
if (!id) {
|
|
227
|
+
ui.error('Usage: securenow alerts rules exclusions <rule-id> [list|add|delete] [exclusion-id]');
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
if (action === 'add') {
|
|
233
|
+
const body = {};
|
|
234
|
+
if (flags.conditions) body.conditions = JSON.parse(flags.conditions);
|
|
235
|
+
if (flags['match-mode']) body.matchMode = flags['match-mode'];
|
|
236
|
+
if (flags.reason) body.reason = flags.reason;
|
|
237
|
+
if (flags.path) body.pathPattern = flags.path;
|
|
238
|
+
const data = await api.post(`/alert-rules/${id}/exclusions`, body);
|
|
239
|
+
if (flags.json) { ui.json(data); return; }
|
|
240
|
+
ui.success('Exclusion added');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (action === 'delete' || action === 'remove') {
|
|
245
|
+
const exclusionId = args[2];
|
|
246
|
+
if (!exclusionId) {
|
|
247
|
+
ui.error('Exclusion id required.');
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
const data = await api.delete(`/alert-rules/${id}/exclusions/${exclusionId}`);
|
|
251
|
+
if (flags.json) { ui.json(data); return; }
|
|
252
|
+
ui.success('Exclusion removed');
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const data = await api.get(`/alert-rules/${id}/exclusions`);
|
|
257
|
+
const exclusions = data.exclusions || [];
|
|
258
|
+
if (flags.json) { ui.json(data); return; }
|
|
259
|
+
console.log('');
|
|
260
|
+
const rows = exclusions.map((e) => [
|
|
261
|
+
ui.c.dim(ui.truncate(e._id, 12)),
|
|
262
|
+
e.isActive === false ? ui.statusBadge('disabled') : ui.statusBadge('active'),
|
|
263
|
+
e.matchMode || 'all',
|
|
264
|
+
ui.truncate(e.reason || e.pathPattern || JSON.stringify(e.conditions || []), 72),
|
|
265
|
+
]);
|
|
266
|
+
ui.table(['ID', 'Status', 'Mode', 'Reason/Pattern'], rows);
|
|
267
|
+
console.log('');
|
|
268
|
+
} catch (err) {
|
|
269
|
+
throw err;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
176
272
|
|
|
177
273
|
// ── Alert Channels ──
|
|
178
274
|
|
|
@@ -238,7 +334,12 @@ async function blocklistList(args, flags) {
|
|
|
238
334
|
requireAuth();
|
|
239
335
|
const s = ui.spinner('Fetching blocklist');
|
|
240
336
|
try {
|
|
241
|
-
const
|
|
337
|
+
const query = {};
|
|
338
|
+
if (flags.page) query.page = flags.page;
|
|
339
|
+
if (flags.limit) query.limit = flags.limit;
|
|
340
|
+
if (flags.app) query.appKey = flags.app;
|
|
341
|
+
if (flags.env || flags.environment) query.environment = flags.env || flags.environment;
|
|
342
|
+
const data = await api.get('/blocklist', { query });
|
|
242
343
|
const items = data.blockedIps || [];
|
|
243
344
|
s.stop(`Found ${items.length} blocked IP${items.length !== 1 ? 's' : ''}${data.total ? ` (${data.total} total)` : ''}`);
|
|
244
345
|
|
|
@@ -249,11 +350,13 @@ async function blocklistList(args, flags) {
|
|
|
249
350
|
ui.c.dim(ui.truncate(b._id, 12)),
|
|
250
351
|
ui.c.red(b.ip || b.cidr || '—'),
|
|
251
352
|
ui.truncate(b.reason || '', 40),
|
|
252
|
-
b.source || '—',
|
|
253
|
-
ui.
|
|
254
|
-
b.
|
|
255
|
-
|
|
256
|
-
|
|
353
|
+
b.source || '—',
|
|
354
|
+
b.applicationKey || ui.c.dim('all apps'),
|
|
355
|
+
b.environment || ui.c.dim('all envs'),
|
|
356
|
+
ui.timeAgo(b.createdAt),
|
|
357
|
+
b.expiresAt ? new Date(b.expiresAt).toLocaleDateString() : ui.c.dim('permanent'),
|
|
358
|
+
]);
|
|
359
|
+
ui.table(['ID', 'IP/CIDR', 'Reason', 'Source', 'App', 'Env', 'Added', 'Expires'], rows);
|
|
257
360
|
console.log('');
|
|
258
361
|
} catch (err) {
|
|
259
362
|
s.fail('Failed to fetch blocklist');
|
|
@@ -269,10 +372,11 @@ async function blocklistAdd(args, flags) {
|
|
|
269
372
|
if (!ip) { ui.error('IP is required'); process.exit(1); }
|
|
270
373
|
}
|
|
271
374
|
|
|
272
|
-
const body = { ip };
|
|
273
|
-
if (flags.reason) body.reason = flags.reason;
|
|
274
|
-
if (flags.duration) body.duration = flags.duration;
|
|
275
|
-
if (flags.app) body.
|
|
375
|
+
const body = { ip };
|
|
376
|
+
if (flags.reason) body.reason = flags.reason;
|
|
377
|
+
if (flags.duration) body.duration = flags.duration;
|
|
378
|
+
if (flags.app) body.appKey = flags.app;
|
|
379
|
+
if (flags.env || flags.environment) body.environment = flags.env || flags.environment;
|
|
276
380
|
|
|
277
381
|
const s = ui.spinner(`Blocking ${ip}`);
|
|
278
382
|
try {
|
|
@@ -340,7 +444,12 @@ async function allowlistList(args, flags) {
|
|
|
340
444
|
requireAuth();
|
|
341
445
|
const s = ui.spinner('Fetching allowlist');
|
|
342
446
|
try {
|
|
343
|
-
const
|
|
447
|
+
const query = {};
|
|
448
|
+
if (flags.page) query.page = flags.page;
|
|
449
|
+
if (flags.limit) query.limit = flags.limit;
|
|
450
|
+
if (flags.app) query.appKey = flags.app;
|
|
451
|
+
if (flags.env || flags.environment) query.environment = flags.env || flags.environment;
|
|
452
|
+
const data = await api.get('/allowlist', { query });
|
|
344
453
|
const items = data.allowedIps || [];
|
|
345
454
|
s.stop(`Found ${items.length} allowed IP${items.length !== 1 ? 's' : ''}${data.total ? ` (${data.total} total)` : ''}`);
|
|
346
455
|
|
|
@@ -349,13 +458,15 @@ async function allowlistList(args, flags) {
|
|
|
349
458
|
console.log('');
|
|
350
459
|
const rows = items.map(a => [
|
|
351
460
|
ui.c.dim(ui.truncate(a._id, 12)),
|
|
352
|
-
ui.c.green(a.ip || '—'),
|
|
353
|
-
a.label || ui.c.dim('—'),
|
|
354
|
-
ui.truncate(a.reason || '', 40),
|
|
355
|
-
ui.
|
|
356
|
-
a.
|
|
357
|
-
|
|
358
|
-
|
|
461
|
+
ui.c.green(a.ip || '—'),
|
|
462
|
+
a.label || ui.c.dim('—'),
|
|
463
|
+
ui.truncate(a.reason || '', 40),
|
|
464
|
+
a.applicationKey || ui.c.dim('all apps'),
|
|
465
|
+
a.environment || ui.c.dim('all envs'),
|
|
466
|
+
ui.timeAgo(a.createdAt),
|
|
467
|
+
a.expiresAt ? new Date(a.expiresAt).toLocaleDateString() : ui.c.dim('permanent'),
|
|
468
|
+
]);
|
|
469
|
+
ui.table(['ID', 'IP/CIDR', 'Label', 'Reason', 'App', 'Env', 'Added', 'Expires'], rows);
|
|
359
470
|
console.log('');
|
|
360
471
|
} catch (err) {
|
|
361
472
|
s.fail('Failed to fetch allowlist');
|
|
@@ -371,9 +482,14 @@ async function allowlistAdd(args, flags) {
|
|
|
371
482
|
if (!ip) { ui.error('IP is required'); process.exit(1); }
|
|
372
483
|
}
|
|
373
484
|
|
|
374
|
-
const body = { ip };
|
|
375
|
-
if (flags.label) body.label = flags.label;
|
|
376
|
-
if (flags.reason) body.reason = flags.reason;
|
|
485
|
+
const body = { ip };
|
|
486
|
+
if (flags.label) body.label = flags.label;
|
|
487
|
+
if (flags.reason) body.reason = flags.reason;
|
|
488
|
+
if (flags.app) {
|
|
489
|
+
body.applicationsAll = false;
|
|
490
|
+
body.applicationKeys = String(flags.app).split(',').map((x) => x.trim()).filter(Boolean);
|
|
491
|
+
}
|
|
492
|
+
if (flags.env || flags.environment) body.environment = flags.env || flags.environment;
|
|
377
493
|
|
|
378
494
|
const s = ui.spinner(`Allowing ${ip}`);
|
|
379
495
|
try {
|
|
@@ -438,7 +554,10 @@ async function trustedList(args, flags) {
|
|
|
438
554
|
requireAuth();
|
|
439
555
|
const s = ui.spinner('Fetching trusted IPs');
|
|
440
556
|
try {
|
|
441
|
-
const
|
|
557
|
+
const query = {};
|
|
558
|
+
if (flags.app) query.appKey = flags.app;
|
|
559
|
+
if (flags.env || flags.environment) query.environment = flags.env || flags.environment;
|
|
560
|
+
const data = await api.get('/trusted-ips', { query });
|
|
442
561
|
const items = data.trustedIps || [];
|
|
443
562
|
s.stop(`Found ${items.length} trusted IP${items.length !== 1 ? 's' : ''}`);
|
|
444
563
|
|
|
@@ -446,12 +565,14 @@ async function trustedList(args, flags) {
|
|
|
446
565
|
|
|
447
566
|
console.log('');
|
|
448
567
|
const rows = items.map(t => [
|
|
449
|
-
ui.c.dim(ui.truncate(t._id, 12)),
|
|
450
|
-
ui.c.green(t.ip || t.cidr || '—'),
|
|
451
|
-
t.label || t.description || ui.c.dim('—'),
|
|
452
|
-
ui.
|
|
453
|
-
|
|
454
|
-
|
|
568
|
+
ui.c.dim(ui.truncate(t._id, 12)),
|
|
569
|
+
ui.c.green(t.ip || t.cidr || '—'),
|
|
570
|
+
t.label || t.description || ui.c.dim('—'),
|
|
571
|
+
t.applicationKey || ui.c.dim('all apps'),
|
|
572
|
+
t.environment || ui.c.dim('all envs'),
|
|
573
|
+
ui.timeAgo(t.createdAt),
|
|
574
|
+
]);
|
|
575
|
+
ui.table(['ID', 'IP/CIDR', 'Label', 'App', 'Env', 'Added'], rows);
|
|
455
576
|
console.log('');
|
|
456
577
|
} catch (err) {
|
|
457
578
|
s.fail('Failed to fetch trusted IPs');
|
|
@@ -467,9 +588,15 @@ async function trustedAdd(args, flags) {
|
|
|
467
588
|
if (!ip) { ui.error('IP is required'); process.exit(1); }
|
|
468
589
|
}
|
|
469
590
|
|
|
470
|
-
const body = { ip };
|
|
471
|
-
if (flags.label) body.label = flags.label;
|
|
472
|
-
if (flags.description) body.description = flags.description;
|
|
591
|
+
const body = { ip };
|
|
592
|
+
if (flags.label) body.label = flags.label;
|
|
593
|
+
if (flags.description) body.description = flags.description;
|
|
594
|
+
if (flags.note) body.note = flags.note;
|
|
595
|
+
if (flags.app) {
|
|
596
|
+
body.applicationsAll = false;
|
|
597
|
+
body.applicationKeys = String(flags.app).split(',').map((x) => x.trim()).filter(Boolean);
|
|
598
|
+
}
|
|
599
|
+
if (flags.env || flags.environment) body.environment = flags.env || flags.environment;
|
|
473
600
|
|
|
474
601
|
const s = ui.spinner(`Adding ${ip} to trusted IPs`);
|
|
475
602
|
try {
|
|
@@ -965,9 +1092,11 @@ async function analytics(args, flags) {
|
|
|
965
1092
|
|
|
966
1093
|
module.exports = {
|
|
967
1094
|
alertRulesRoute,
|
|
968
|
-
alertRulesList,
|
|
969
|
-
alertRuleShow,
|
|
970
|
-
alertRuleUpdate,
|
|
1095
|
+
alertRulesList,
|
|
1096
|
+
alertRuleShow,
|
|
1097
|
+
alertRuleUpdate,
|
|
1098
|
+
alertRuleTest,
|
|
1099
|
+
alertRuleExclusions,
|
|
971
1100
|
alertChannelsList,
|
|
972
1101
|
alertHistoryList,
|
|
973
1102
|
blocklistList,
|
package/cli.js
CHANGED
|
@@ -164,7 +164,7 @@ const COMMANDS = {
|
|
|
164
164
|
},
|
|
165
165
|
human: {
|
|
166
166
|
desc: 'Work the human action queue prepared by SecureNow AI',
|
|
167
|
-
usage: 'securenow human <list|show|block|fp|prompt|work> [row|notificationId:ip] [options]',
|
|
167
|
+
usage: 'securenow human <list|show|block|fp|action|prompt|work> [row|notificationId:ip] [options]',
|
|
168
168
|
flags: {
|
|
169
169
|
json: 'Output as JSON',
|
|
170
170
|
page: 'Queue page number',
|
|
@@ -178,6 +178,9 @@ const COMMANDS = {
|
|
|
178
178
|
'rule-scope': 'False-positive scope: this_rule | specific_rules | all_existing | any_rule',
|
|
179
179
|
'create-exclusion': 'Create a restrictive exclusion when marking false positive',
|
|
180
180
|
'apply-existing': 'Apply false-positive decision to existing matching rows',
|
|
181
|
+
status: 'Case action status: approved, rejected, executed, failed, or proposed',
|
|
182
|
+
'action-key': 'Case-level proposed action key',
|
|
183
|
+
result: 'Case action result JSON',
|
|
181
184
|
mode: 'Prompt mode label for MCP prompt output',
|
|
182
185
|
},
|
|
183
186
|
sub: {
|
|
@@ -185,6 +188,7 @@ const COMMANDS = {
|
|
|
185
188
|
show: { desc: 'Show one row with AI report, proofs, DAG, and trace links', usage: 'securenow human show <row|notificationId:ip>', run: (a, f) => require('./cli/human').show(a, f) },
|
|
186
189
|
block: { desc: 'Approve the AI block recommendation for a row', usage: 'securenow human block <row|notificationId:ip> --yes --reason "..."', run: (a, f) => require('./cli/human').block(a, f) },
|
|
187
190
|
fp: { desc: 'Mark a row as a scoped false positive', usage: 'securenow human fp <row|notificationId:ip> --yes --reason "..."', run: (a, f) => require('./cli/human').fp(a, f) },
|
|
191
|
+
action: { desc: 'Approve/reject/execute a case-level proposed action', usage: 'securenow human action <row|notificationId> [actionKey] --status approved --yes --reason "..."', run: (a, f) => require('./cli/human').action(a, f) },
|
|
188
192
|
prompt: { desc: 'Print a Codex/Claude MCP prompt for row or queue work', usage: 'securenow human prompt [row|notificationId:ip] [--limit 10]', run: (a, f) => require('./cli/human').prompt(a, f) },
|
|
189
193
|
work: { desc: 'List the queue and print the MCP runbook to work it deeply', usage: 'securenow human work [--limit 10]', run: (a, f) => require('./cli/human').work(a, f) },
|
|
190
194
|
},
|
|
@@ -226,47 +230,86 @@ const COMMANDS = {
|
|
|
226
230
|
},
|
|
227
231
|
defaultSub: 'list',
|
|
228
232
|
},
|
|
229
|
-
firewall: {
|
|
230
|
-
desc: 'Firewall status, per-app toggle, and IP testing',
|
|
231
|
-
usage: 'securenow firewall <subcommand> [options]',
|
|
232
|
-
flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
|
|
233
|
+
firewall: {
|
|
234
|
+
desc: 'Firewall status, per-app toggle, and IP testing',
|
|
235
|
+
usage: 'securenow firewall <subcommand> [options]',
|
|
236
|
+
flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
|
|
233
237
|
sub: {
|
|
234
238
|
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) },
|
|
235
239
|
apps: { desc: 'List apps with their firewall on/off state', run: (a, f) => require('./cli/firewall').appsList(a, f) },
|
|
236
240
|
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) },
|
|
237
241
|
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) },
|
|
238
242
|
'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) },
|
|
239
|
-
},
|
|
240
|
-
defaultSub: 'status',
|
|
241
|
-
},
|
|
242
|
-
|
|
243
|
-
desc: 'Manage
|
|
244
|
-
usage: 'securenow
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
243
|
+
},
|
|
244
|
+
defaultSub: 'status',
|
|
245
|
+
},
|
|
246
|
+
automation: {
|
|
247
|
+
desc: 'Manage automation rules for blocklist actions',
|
|
248
|
+
usage: 'securenow automation <list|show|create|update|dry-run|execute|delete> [rule-id] [options]',
|
|
249
|
+
flags: {
|
|
250
|
+
json: 'Output as JSON',
|
|
251
|
+
body: 'Full JSON body for create/update',
|
|
252
|
+
file: 'Read JSON body from file',
|
|
253
|
+
name: 'Rule name',
|
|
254
|
+
description: 'Rule description',
|
|
255
|
+
conditions: 'Conditions JSON array',
|
|
256
|
+
actions: 'Actions JSON array',
|
|
257
|
+
logic: 'Condition logic: AND or OR',
|
|
258
|
+
status: 'Rule status: active or disabled',
|
|
259
|
+
app: 'Comma-separated app key(s) to scope the rule',
|
|
260
|
+
apps: 'Alias for --app',
|
|
261
|
+
'applications-all': 'Scope rule to all apps',
|
|
262
|
+
env: 'Comma-separated environment(s) to scope the rule',
|
|
263
|
+
environment: 'Alias for --env',
|
|
264
|
+
environments: 'Alias for --env',
|
|
265
|
+
'environments-all': 'Scope rule to all environments',
|
|
266
|
+
limit: 'Dry-run notification scan limit',
|
|
267
|
+
'sample-limit': 'Dry-run sample count',
|
|
268
|
+
yes: 'Confirm execute/delete',
|
|
269
|
+
force: 'Alias for --yes',
|
|
270
|
+
},
|
|
271
|
+
sub: {
|
|
272
|
+
list: { desc: 'List automation rules', run: (a, f) => require('./cli/automation').list(a, f) },
|
|
273
|
+
show: { desc: 'Show automation rule details', usage: 'securenow automation show <rule-id>', run: (a, f) => require('./cli/automation').show(a, f) },
|
|
274
|
+
create: { desc: 'Create automation rule', usage: 'securenow automation create --name "..." --conditions \'[...]\' --actions \'[...]\'', run: (a, f) => require('./cli/automation').create(a, f) },
|
|
275
|
+
update: { desc: 'Update automation rule', usage: 'securenow automation update <rule-id> --body \'{...}\'', run: (a, f) => require('./cli/automation').update(a, f) },
|
|
276
|
+
'dry-run': { desc: 'Preview automation matches without writing blocklist entries', usage: 'securenow automation dry-run <rule-id>', run: (a, f) => require('./cli/automation').dryRun(a, f) },
|
|
277
|
+
execute: { desc: 'Execute an automation rule now', usage: 'securenow automation execute <rule-id> --yes', run: (a, f) => require('./cli/automation').execute(a, f) },
|
|
278
|
+
delete: { desc: 'Delete an automation rule', usage: 'securenow automation delete <rule-id> --yes', run: (a, f) => require('./cli/automation').remove(a, f) },
|
|
279
|
+
},
|
|
280
|
+
defaultSub: 'list',
|
|
281
|
+
},
|
|
282
|
+
blocklist: {
|
|
283
|
+
desc: 'Manage IP blocklist',
|
|
284
|
+
usage: 'securenow blocklist <subcommand> [options]',
|
|
285
|
+
flags: { app: 'Scope to app key', env: 'Scope to environment', environment: 'Alias for --env', page: 'Page number', limit: 'Max results', json: 'Output as JSON' },
|
|
286
|
+
sub: {
|
|
287
|
+
list: { desc: 'List blocked IPs', run: (a, f) => require('./cli/security').blocklistList(a, f) },
|
|
288
|
+
add: { desc: 'Block an IP', usage: 'securenow blocklist add <ip> [--app <key>] [--env production] [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistAdd(a, f) },
|
|
248
289
|
remove: { desc: 'Unblock an IP', usage: 'securenow blocklist remove <id>', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
|
|
249
290
|
stats: { desc: 'Blocklist statistics', run: (a, f) => require('./cli/security').blocklistStats(a, f) },
|
|
250
291
|
},
|
|
251
292
|
defaultSub: 'list',
|
|
252
293
|
},
|
|
253
|
-
allowlist: {
|
|
254
|
-
desc: 'Manage IP allowlist (only allow listed IPs)',
|
|
255
|
-
usage: 'securenow allowlist <subcommand> [options]',
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
294
|
+
allowlist: {
|
|
295
|
+
desc: 'Manage IP allowlist (only allow listed IPs)',
|
|
296
|
+
usage: 'securenow allowlist <subcommand> [options]',
|
|
297
|
+
flags: { app: 'Scope to app key(s), comma-separated', env: 'Scope to environment', environment: 'Alias for --env', page: 'Page number', limit: 'Max results', json: 'Output as JSON' },
|
|
298
|
+
sub: {
|
|
299
|
+
list: { desc: 'List allowed IPs', run: (a, f) => require('./cli/security').allowlistList(a, f) },
|
|
300
|
+
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) },
|
|
259
301
|
remove: { desc: 'Remove an allowed IP', usage: 'securenow allowlist remove <id>', run: (a, f) => require('./cli/security').allowlistRemove(a, f) },
|
|
260
302
|
stats: { desc: 'Allowlist statistics', run: (a, f) => require('./cli/security').allowlistStats(a, f) },
|
|
261
303
|
},
|
|
262
304
|
defaultSub: 'list',
|
|
263
305
|
},
|
|
264
|
-
trusted: {
|
|
265
|
-
desc: 'Manage trusted IPs',
|
|
266
|
-
usage: 'securenow trusted <subcommand> [options]',
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
306
|
+
trusted: {
|
|
307
|
+
desc: 'Manage trusted IPs',
|
|
308
|
+
usage: 'securenow trusted <subcommand> [options]',
|
|
309
|
+
flags: { app: 'Scope to app key(s), comma-separated', env: 'Scope to environment', environment: 'Alias for --env', json: 'Output as JSON' },
|
|
310
|
+
sub: {
|
|
311
|
+
list: { desc: 'List trusted IPs', run: (a, f) => require('./cli/security').trustedList(a, f) },
|
|
312
|
+
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) },
|
|
270
313
|
remove: { desc: 'Remove trusted IP', usage: 'securenow trusted remove <id>', run: (a, f) => require('./cli/security').trustedRemove(a, f) },
|
|
271
314
|
},
|
|
272
315
|
defaultSub: 'list',
|
|
@@ -499,8 +542,8 @@ function showHelp(commandName) {
|
|
|
499
542
|
'Observe': ['traces', 'logs', 'analytics'],
|
|
500
543
|
'Detect & Respond': ['human', 'notifications', 'alerts', 'fp'],
|
|
501
544
|
'Investigate': ['ip', 'forensics'],
|
|
502
|
-
'Firewall': ['firewall'],
|
|
503
|
-
'Remediation': ['blocklist', 'allowlist', 'trusted'],
|
|
545
|
+
'Firewall': ['firewall'],
|
|
546
|
+
'Remediation': ['automation', 'blocklist', 'allowlist', 'trusted'],
|
|
504
547
|
'Telemetry': ['log', 'test-span'],
|
|
505
548
|
'Utilities': ['redact', 'cidr', 'doctor', 'env', 'mcp'],
|
|
506
549
|
'Settings': ['instances', 'config', 'version'],
|