securenow 7.6.9 → 7.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NPM_README.md +17 -3
- package/README.md +9 -4
- package/SKILL-API.md +2 -2
- package/SKILL-CLI.md +36 -22
- package/app-config.js +81 -3
- package/cli/automation.js +275 -0
- package/cli/config.js +8 -8
- package/cli/credentials.js +2 -1
- 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/credentials.js
CHANGED
|
@@ -40,7 +40,7 @@ function buildRuntimeCredentials(options = {}) {
|
|
|
40
40
|
},
|
|
41
41
|
_securenow: {
|
|
42
42
|
...(creds._securenow || {}),
|
|
43
|
-
note: 'Runtime SecureNow credentials and SDK defaults. Mount or copy this JSON as .securenow/credentials.json in production. Do not commit it.',
|
|
43
|
+
note: 'Runtime SecureNow credentials and SDK defaults. Mount or copy this JSON as .securenow/credentials.json or .securenow/credentials.<environment>.json in production. Do not commit it.',
|
|
44
44
|
runtimeOnly: 'This file intentionally omits CLI OAuth fields: token, email, and expiresAt.',
|
|
45
45
|
production: 'Production can use this same file shape instead of environment variables.',
|
|
46
46
|
},
|
|
@@ -83,6 +83,7 @@ async function runtime(_args, flags) {
|
|
|
83
83
|
|
|
84
84
|
ui.success(`Wrote runtime credentials to ${output}`);
|
|
85
85
|
ui.info('Deploy this JSON as .securenow/credentials.json on the server/container.');
|
|
86
|
+
ui.info('SDK v7.7.1+ can also read this generated filename directly when credentials.json is absent.');
|
|
86
87
|
ui.info(`Environment: ${envName}`);
|
|
87
88
|
ui.info(`App: ${creds.app?.name || '(unnamed)'} ${creds.app?.key ? `(${creds.app.key})` : ''}`);
|
|
88
89
|
ui.info(`Firewall key: ${creds.apiKey ? maskSecret(creds.apiKey) : '(missing)'}`);
|
package/cli/firewall.js
CHANGED
|
@@ -7,13 +7,16 @@ function resolveEnvironment(flags) {
|
|
|
7
7
|
return flags.env || flags.environment || 'production';
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
async function status(args, flags) {
|
|
11
|
-
requireAuth();
|
|
12
|
-
const s = ui.spinner('Checking firewall status');
|
|
13
|
-
|
|
14
|
-
try {
|
|
10
|
+
async function status(args, flags) {
|
|
11
|
+
requireAuth();
|
|
12
|
+
const s = ui.spinner('Checking firewall status');
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
15
|
const environment = resolveEnvironment(flags);
|
|
16
|
-
const
|
|
16
|
+
const appKey = await resolveAppKey(flags);
|
|
17
|
+
const query = { environment };
|
|
18
|
+
if (appKey) query.appKey = appKey;
|
|
19
|
+
const data = await api.get('/firewall/status', { query });
|
|
17
20
|
|
|
18
21
|
s.stop('Firewall status retrieved');
|
|
19
22
|
|
|
@@ -23,10 +26,14 @@ async function status(args, flags) {
|
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
console.log('');
|
|
26
|
-
|
|
29
|
+
const enabledLabel = data.firewallEnabled === false
|
|
30
|
+
? ui.c.bold(ui.c.yellow('Firewall: DISABLED FOR APP/ENV'))
|
|
31
|
+
: ui.c.bold(ui.c.green('Firewall: ENABLED'));
|
|
32
|
+
console.log(` ${enabledLabel}`);
|
|
27
33
|
console.log('');
|
|
28
34
|
ui.keyValue([
|
|
29
35
|
['Blocked IPs', `${data.totalIps} total (${data.exactCount} exact + ${data.cidrCount} CIDR ranges)`],
|
|
36
|
+
['App scope', appKey || 'all apps'],
|
|
30
37
|
['Environment', data.environment || environment],
|
|
31
38
|
['Last updated', data.updatedAt || 'unknown'],
|
|
32
39
|
['Allowed IPs', data.allowlistCount != null ? `${data.allowlistCount} total (${data.allowlistExactCount} exact + ${data.allowlistCidrCount} CIDR ranges)` : '0'],
|
|
@@ -66,7 +73,10 @@ async function testIp(args, flags) {
|
|
|
66
73
|
|
|
67
74
|
try {
|
|
68
75
|
const environment = resolveEnvironment(flags);
|
|
69
|
-
const
|
|
76
|
+
const appKey = await resolveAppKey(flags);
|
|
77
|
+
const query = { environment };
|
|
78
|
+
if (appKey) query.appKey = appKey;
|
|
79
|
+
const data = await api.get(`/firewall/check/${encodeURIComponent(ip)}`, { query });
|
|
70
80
|
|
|
71
81
|
s.stop(`IP ${ip} checked`);
|
|
72
82
|
|
|
@@ -75,8 +85,14 @@ async function testIp(args, flags) {
|
|
|
75
85
|
return;
|
|
76
86
|
}
|
|
77
87
|
|
|
78
|
-
console.log('');
|
|
79
|
-
if (data.
|
|
88
|
+
console.log('');
|
|
89
|
+
if (data.firewallEnabled === false) {
|
|
90
|
+
console.log(` ${ui.c.bold(ui.c.yellow('NOT ENFORCED'))} — firewall is disabled for ${appKey || 'this app'} (${data.environment || environment})`);
|
|
91
|
+
if (data.reason) console.log(` ${ui.c.dim(`Reason: ${data.reason}`)}`);
|
|
92
|
+
console.log('');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (data.blocked) {
|
|
80
96
|
if (data.allowlistActive && !data.allowlisted) {
|
|
81
97
|
console.log(` ${ui.c.bold(ui.c.red('BLOCKED'))} — ${ip} is not on the allowlist`);
|
|
82
98
|
console.log(` ${ui.c.dim('Allowlist is active — only listed IPs are permitted')}`);
|
|
@@ -92,8 +108,9 @@ async function testIp(args, flags) {
|
|
|
92
108
|
} else {
|
|
93
109
|
console.log(` ${ui.c.bold(ui.c.green('ALLOWED'))} — ${ip} is not in the blocklist`);
|
|
94
110
|
}
|
|
95
|
-
}
|
|
96
|
-
console.log(` ${ui.c.dim(`
|
|
111
|
+
}
|
|
112
|
+
console.log(` ${ui.c.dim(`App scope: ${appKey || 'all apps'} · Environment: ${data.environment || environment}`)}`);
|
|
113
|
+
console.log(` ${ui.c.dim(`Blocklist contains ${data.totalBlockedIps} entries`)}`);
|
|
97
114
|
if (data.allowlistActive) {
|
|
98
115
|
console.log(` ${ui.c.dim('Allowlist is active')}`);
|
|
99
116
|
}
|
package/cli/human.js
CHANGED
|
@@ -214,6 +214,40 @@ async function show(args, flags) {
|
|
|
214
214
|
const s = ui.spinner('Fetching human action detail');
|
|
215
215
|
try {
|
|
216
216
|
const { task, row } = await resolveTask(ref, flags);
|
|
217
|
+
if (task.kind === 'case_action' || task.actionKey) {
|
|
218
|
+
const [notification, agentCase] = await Promise.all([
|
|
219
|
+
api.get(`/notifications/${encodeURIComponent(task.notificationId)}`).catch(() => ({})),
|
|
220
|
+
api.get(`/notifications/${encodeURIComponent(task.notificationId)}/agent-case`).catch(() => ({})),
|
|
221
|
+
]);
|
|
222
|
+
s.stop('Case action loaded');
|
|
223
|
+
if (flags.json) {
|
|
224
|
+
ui.json({ task, notification, agentCase });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
console.log('');
|
|
228
|
+
printTaskSummary(task, row);
|
|
229
|
+
console.log('');
|
|
230
|
+
ui.subheading('Case action');
|
|
231
|
+
ui.keyValue([
|
|
232
|
+
['Action key', task.actionKey || '-'],
|
|
233
|
+
['Type', task.actionType || task.action?.type || '-'],
|
|
234
|
+
['Title', task.action?.title || task.title || '-'],
|
|
235
|
+
['Description', ui.truncate(task.action?.description || '', 140)],
|
|
236
|
+
['Status', task.action?.status || 'proposed'],
|
|
237
|
+
]);
|
|
238
|
+
if ((agentCase.proposedActions || []).length) {
|
|
239
|
+
console.log('');
|
|
240
|
+
const rows = agentCase.proposedActions.map((action) => [
|
|
241
|
+
action.actionKey || '-',
|
|
242
|
+
action.type || '-',
|
|
243
|
+
action.status || '-',
|
|
244
|
+
ui.truncate(action.title || action.description || '', 80),
|
|
245
|
+
]);
|
|
246
|
+
ui.table(['Key', 'Type', 'Status', 'Action'], rows);
|
|
247
|
+
}
|
|
248
|
+
console.log('');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
217
251
|
const [ipReport, notification] = await Promise.all([
|
|
218
252
|
api.get(`/notifications/${encodeURIComponent(task.notificationId)}/ips/${encodeURIComponent(task.ip)}`),
|
|
219
253
|
api.get(`/notifications/${encodeURIComponent(task.notificationId)}`).catch(() => ({})),
|
|
@@ -248,6 +282,63 @@ async function show(args, flags) {
|
|
|
248
282
|
}
|
|
249
283
|
}
|
|
250
284
|
|
|
285
|
+
async function action(args, flags) {
|
|
286
|
+
requireAuth();
|
|
287
|
+
const ref = args[0];
|
|
288
|
+
if (!ref) {
|
|
289
|
+
ui.error('Usage: securenow human action <row|notificationId> [actionKey] --status approved|rejected|executed|failed --yes --reason "..."');
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const parsed = parseTaskRef(ref);
|
|
294
|
+
let notificationId = parsed?.notificationId;
|
|
295
|
+
let actionKey = flags.actionKey || flags['action-key'] || args[1];
|
|
296
|
+
let task = null;
|
|
297
|
+
let row = null;
|
|
298
|
+
|
|
299
|
+
if (parsed?.kind === 'row' || !actionKey) {
|
|
300
|
+
const resolved = await resolveTask(ref, flags);
|
|
301
|
+
task = resolved.task;
|
|
302
|
+
row = resolved.row;
|
|
303
|
+
notificationId = task.notificationId;
|
|
304
|
+
actionKey = actionKey || task.actionKey;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const status = flags.status || args[2];
|
|
308
|
+
if (!notificationId || !actionKey || !status) {
|
|
309
|
+
ui.error('notificationId, actionKey, and --status are required.');
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!['proposed', 'approved', 'rejected', 'executed', 'failed'].includes(status)) {
|
|
314
|
+
ui.error('Status must be one of: proposed, approved, rejected, executed, failed');
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!flags.yes && !flags.force) {
|
|
319
|
+
const label = task ? `row ${row || ''} (${actionKey})` : `${notificationId}/${actionKey}`;
|
|
320
|
+
const ok = await ui.confirm(`Set case action ${label} to ${status}?`);
|
|
321
|
+
if (!ok) { ui.info('Cancelled'); return; }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const result = flags.result ? JSON.parse(flags.result) : {};
|
|
325
|
+
if (flags.reason) result.reason = flags.reason;
|
|
326
|
+
|
|
327
|
+
const s = ui.spinner('Updating case action');
|
|
328
|
+
try {
|
|
329
|
+
const data = await api.put(
|
|
330
|
+
`/notifications/${encodeURIComponent(notificationId)}/agent-case/actions/${encodeURIComponent(actionKey)}`,
|
|
331
|
+
{ status, result }
|
|
332
|
+
);
|
|
333
|
+
s.stop('Case action updated');
|
|
334
|
+
if (flags.json) { ui.json(data); return; }
|
|
335
|
+
ui.success(`${actionKey} -> ${data.action?.status || status}`);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
s.fail('Failed to update case action');
|
|
338
|
+
throw err;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
251
342
|
async function block(args, flags) {
|
|
252
343
|
requireAuth();
|
|
253
344
|
const ref = args[0];
|
|
@@ -328,10 +419,12 @@ function mcpPromptText(ref, flags = {}) {
|
|
|
328
419
|
: '2. For each task, call securenow_notifications_get and securenow_human_action_report for the notificationId and IP.',
|
|
329
420
|
'3. Read the AI report, finalDecision, DAG steps, findings, proofs, metadata paths/user agents/status codes, and trace IDs.',
|
|
330
421
|
'4. Open/inspect trace evidence with securenow_traces_show and securenow_logs_for_trace when trace IDs are available.',
|
|
331
|
-
'5. Decide one
|
|
422
|
+
'5. Decide one outcome: block the IP, mark a scoped false positive, recommend alert-rule tuning, or skip if evidence is ambiguous.',
|
|
332
423
|
'6. For block decisions, call securenow_human_action_block with confirm:true and a precise reason.',
|
|
333
424
|
'7. For false positives, call securenow_human_action_false_positive with confirm:true, the narrowest available conditions, and a precise reason.',
|
|
334
|
-
'8.
|
|
425
|
+
'8. For case-level tune_rule/create_exclusion rows, inspect the notification case and use securenow_human_case_action_update only when the proposed action is safe.',
|
|
426
|
+
'9. If many IPs share the same benign path/status/user-agent pattern, recommend tightening the alert rule with a precise guard instead of reviewing each IP as malicious.',
|
|
427
|
+
'10. Summarize each row handled, skipped, rule tuning needed, and still waiting. Do not globally trust an IP by default.',
|
|
335
428
|
'',
|
|
336
429
|
'Safety:',
|
|
337
430
|
'- Do not call write tools without confirm:true and a reason.',
|
|
@@ -359,6 +452,7 @@ async function work(args, flags) {
|
|
|
359
452
|
module.exports = {
|
|
360
453
|
list,
|
|
361
454
|
show,
|
|
455
|
+
action,
|
|
362
456
|
block,
|
|
363
457
|
fp,
|
|
364
458
|
prompt,
|
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,
|