securenow 8.7.0 → 8.8.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/cli/security.js +49 -0
- package/cli.js +865 -865
- package/package.json +193 -193
package/cli.js
CHANGED
|
@@ -1,865 +1,865 @@
|
|
|
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 = {
|
|
42
|
-
init: {
|
|
43
|
-
desc: 'Set up SecureNow in the current project (instrumentation + runtime credentials)',
|
|
44
|
-
usage: 'securenow init [--env local] [--key <API_KEY>] [--ts|--js] [--src|--root] [--force]',
|
|
45
|
-
flags: {
|
|
46
|
-
env: 'Deployment environment to write into .securenow/runtime.json (default: local)',
|
|
47
|
-
environment: 'Alias for --env',
|
|
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
|
-
},
|
|
58
|
-
login: {
|
|
59
|
-
desc: 'Onboard SecureNow admin auth and app runtime config',
|
|
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)',
|
|
64
|
-
},
|
|
65
|
-
run: (a, f) => require('./cli/auth').login(a, f),
|
|
66
|
-
},
|
|
67
|
-
admin: {
|
|
68
|
-
desc: 'Manage admin/control-plane CLI and MCP auth',
|
|
69
|
-
usage: 'securenow admin <subcommand> [options]',
|
|
70
|
-
sub: {
|
|
71
|
-
login: {
|
|
72
|
-
desc: 'Authenticate admin/control-plane CLI and MCP tools',
|
|
73
|
-
usage: 'securenow admin login [--token <TOKEN>] [--global]',
|
|
74
|
-
flags: {
|
|
75
|
-
token: 'Authenticate with a token directly',
|
|
76
|
-
global: 'Save admin auth to ~/.securenow/admin.json',
|
|
77
|
-
},
|
|
78
|
-
run: (a, f) => require('./cli/auth').adminLogin(a, f),
|
|
79
|
-
},
|
|
80
|
-
logout: {
|
|
81
|
-
desc: 'Clear admin/control-plane auth',
|
|
82
|
-
usage: 'securenow admin logout [--global]',
|
|
83
|
-
flags: { global: 'Clear global admin auth (~/.securenow/admin.json)' },
|
|
84
|
-
run: (a, f) => require('./cli/auth').logout(a, f),
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
defaultSub: 'login',
|
|
88
|
-
},
|
|
89
|
-
app: {
|
|
90
|
-
desc: 'Connect the current project runtime to a SecureNow app',
|
|
91
|
-
usage: 'securenow app <subcommand> [options]',
|
|
92
|
-
sub: {
|
|
93
|
-
connect: {
|
|
94
|
-
desc: 'Select/create app, mint runtime API key, and write SDK runtime config',
|
|
95
|
-
usage: 'securenow app connect [--global]',
|
|
96
|
-
flags: { global: 'Save runtime config to ~/.securenow/runtime.json' },
|
|
97
|
-
run: (a, f) => require('./cli/auth').appConnect(a, f),
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
defaultSub: 'connect',
|
|
101
|
-
},
|
|
102
|
-
logout: {
|
|
103
|
-
desc: 'Clear admin/control-plane auth',
|
|
104
|
-
usage: 'securenow logout [--global]',
|
|
105
|
-
flags: { global: 'Clear global admin auth (~/.securenow/admin.json)' },
|
|
106
|
-
run: (a, f) => require('./cli/auth').logout(a, f),
|
|
107
|
-
},
|
|
108
|
-
whoami: {
|
|
109
|
-
desc: 'Show admin auth and SDK runtime status',
|
|
110
|
-
usage: 'securenow whoami',
|
|
111
|
-
run: () => require('./cli/auth').whoami(),
|
|
112
|
-
},
|
|
113
|
-
'api-key': {
|
|
114
|
-
desc: 'Manage the runtime API key stored in runtime credentials',
|
|
115
|
-
usage: 'securenow api-key <subcommand> [options]',
|
|
116
|
-
sub: {
|
|
117
|
-
create: {
|
|
118
|
-
desc: 'Create a runtime API key with your session token and save it',
|
|
119
|
-
usage: 'securenow api-key create [name] [--name <name>] [--preset runtime_app] [--app <appKeyOrId>] [--global]',
|
|
120
|
-
flags: {
|
|
121
|
-
name: 'Human-readable key name',
|
|
122
|
-
preset: 'API key preset to create (default: runtime_app)',
|
|
123
|
-
app: 'Application key or id to scope this key to (defaults to current runtime app)',
|
|
124
|
-
global: 'Save to ~/.securenow/ instead of project-local',
|
|
125
|
-
},
|
|
126
|
-
run: (a, f) => require('./cli/apiKey').create(a, f),
|
|
127
|
-
},
|
|
128
|
-
set: {
|
|
129
|
-
desc: 'Save an API key (snk_live_...) to runtime credentials',
|
|
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
|
-
},
|
|
146
|
-
defaultSub: 'show',
|
|
147
|
-
},
|
|
148
|
-
credentials: {
|
|
149
|
-
desc: 'Create production/runtime SecureNow credentials files',
|
|
150
|
-
usage: 'securenow credentials <subcommand> [options]',
|
|
151
|
-
sub: {
|
|
152
|
-
runtime: {
|
|
153
|
-
desc: 'Write tokenless runtime credentials for production file-based deploys',
|
|
154
|
-
usage: 'securenow credentials runtime [--env production] [--out .securenow/credentials.production.json] [--stdout]',
|
|
155
|
-
flags: {
|
|
156
|
-
env: 'Deployment environment for this runtime file (default: production)',
|
|
157
|
-
environment: 'Alias for --env',
|
|
158
|
-
out: 'Output path (default: .securenow/credentials.<env>.json)',
|
|
159
|
-
output: 'Alias for --out',
|
|
160
|
-
stdout: 'Print JSON to stdout instead of writing a file',
|
|
161
|
-
},
|
|
162
|
-
run: (a, f) => require('./cli/credentials').runtime(a, f),
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
defaultSub: 'runtime',
|
|
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: {
|
|
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
|
-
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
|
-
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: {
|
|
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
|
-
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
|
-
},
|
|
200
|
-
notifications: {
|
|
201
|
-
desc: 'Manage notifications',
|
|
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() },
|
|
208
|
-
},
|
|
209
|
-
defaultSub: 'list',
|
|
210
|
-
},
|
|
211
|
-
human: {
|
|
212
|
-
desc: 'Work the human action queue prepared by SecureNow AI',
|
|
213
|
-
usage: 'securenow human <list|show|block|fp|report|action|prompt|work> [row|notificationId:ip] [options]',
|
|
214
|
-
flags: {
|
|
215
|
-
json: 'Output as JSON',
|
|
216
|
-
page: 'Queue page number',
|
|
217
|
-
limit: 'Queue page size',
|
|
218
|
-
search: 'Search IP, rule, path, or verdict',
|
|
219
|
-
reason: 'Reason for block/false-positive decisions',
|
|
220
|
-
summary: 'Decision report summary to write to the IP history',
|
|
221
|
-
outcome: 'Decision report outcome: blocked, false_positive, clean, deferred, case_action, rule_tuning, new_alert_rule, ambiguous, skipped, other',
|
|
222
|
-
evidence: 'Decision evidence; repeat as newline/;; separated text or JSON array',
|
|
223
|
-
history: 'Reviewed history/proofs; newline/;; separated text or JSON array',
|
|
224
|
-
'trace-ids': 'Comma-separated trace IDs reviewed for the decision',
|
|
225
|
-
ips: 'Comma-separated IPs affected by a case-level decision report',
|
|
226
|
-
'missing-proof': 'Missing proof for skipped or ambiguous rows',
|
|
227
|
-
recommendations: 'Follow-up recommendations; newline/;; separated text or JSON array',
|
|
228
|
-
report: 'Full decision report JSON object',
|
|
229
|
-
yes: 'Confirm write actions without prompting',
|
|
230
|
-
force: 'Alias for --yes',
|
|
231
|
-
conditions: 'False-positive conditions JSON array',
|
|
232
|
-
'match-mode': 'False-positive match mode: all or any',
|
|
233
|
-
'rule-scope': 'False-positive scope: this_rule | specific_rules | all_existing | any_rule',
|
|
234
|
-
'create-exclusion': 'Create a restrictive exclusion when marking false positive',
|
|
235
|
-
'apply-existing': 'Apply false-positive decision to existing matching rows',
|
|
236
|
-
status: 'Case action status: approved, rejected, executed, failed, or proposed',
|
|
237
|
-
'action-key': 'Case-level proposed action key',
|
|
238
|
-
result: 'Case action result JSON',
|
|
239
|
-
mode: 'Prompt mode label for MCP prompt output',
|
|
240
|
-
},
|
|
241
|
-
sub: {
|
|
242
|
-
list: { desc: 'List human decisions AI prepared', run: (a, f) => require('./cli/human').list(a, f) },
|
|
243
|
-
show: { desc: 'Show one row with AI report, proofs, investigation steps, and trace links', usage: 'securenow human show <row|notificationId:ip>', run: (a, f) => require('./cli/human').show(a, f) },
|
|
244
|
-
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) },
|
|
245
|
-
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) },
|
|
246
|
-
report: { desc: 'Record a structured decision report on a row without changing status', usage: 'securenow human report <row|notificationId:ip> --yes --outcome ambiguous --summary "..."', run: (a, f) => require('./cli/human').report(a, f) },
|
|
247
|
-
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) },
|
|
248
|
-
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) },
|
|
249
|
-
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) },
|
|
250
|
-
},
|
|
251
|
-
defaultSub: 'list',
|
|
252
|
-
},
|
|
253
|
-
alerts: {
|
|
254
|
-
desc: 'Manage alerting',
|
|
255
|
-
usage: 'securenow alerts <subcommand> [options]',
|
|
256
|
-
sub: {
|
|
257
|
-
rules: {
|
|
258
|
-
desc: 'Create, list, show, update, promote/demote, set-sql, validate, delete, test, or tune alert rules',
|
|
259
|
-
usage: 'securenow alerts rules <list|create|show|update|promote|demote|set-sql|validate|delete|test|dry-run-query|tune-query|exclusions> [options]',
|
|
260
|
-
flags: {
|
|
261
|
-
json: 'Output as JSON',
|
|
262
|
-
name: 'With create: rule name',
|
|
263
|
-
description: 'With create: rule description',
|
|
264
|
-
nlp: 'With create: plain-English intent stored on the query',
|
|
265
|
-
text: 'Alias for --nlp',
|
|
266
|
-
category: 'With create: query category (default: custom)',
|
|
267
|
-
severity: 'With create: critical | high | medium | low',
|
|
268
|
-
schedule: 'With create: cron expression (default */15 * * * *)',
|
|
269
|
-
'throttle-minutes': 'With create: notification throttle window in minutes',
|
|
270
|
-
'no-throttle': 'With create: disable throttling',
|
|
271
|
-
'execution-mode': 'With create: scheduled | instant | hybrid',
|
|
272
|
-
channel: 'With create: comma-separated alert channel ids (default: in-app)',
|
|
273
|
-
'query-mapping-id': 'With create: reuse an existing saved query instead of --sql',
|
|
274
|
-
'applications-all': 'With create/update: scope rule to all apps',
|
|
275
|
-
'no-applications-all': 'With update: scope to explicit --apps list',
|
|
276
|
-
apps: 'Comma-separated app keys (with create/update)',
|
|
277
|
-
app: 'Application key for rule tests',
|
|
278
|
-
mode: 'With test: dry_run | live. With list/update: lifecycle test | prod (test = detect-only, no mitigation)',
|
|
279
|
-
status: 'With update: Active | Disabled | Paused. With list: filter by status',
|
|
280
|
-
enable: 'With update: shortcut for --status Active',
|
|
281
|
-
disable: 'With update: shortcut for --status Disabled',
|
|
282
|
-
pause: 'With update: shortcut for --status Paused',
|
|
283
|
-
active: 'With list: filter active (true) or non-active (false)',
|
|
284
|
-
system: 'With list: only system rules',
|
|
285
|
-
user: 'With list: only user (non-system) rules',
|
|
286
|
-
wait: 'Wait for rule test completion',
|
|
287
|
-
sql: 'Detection/candidate/replacement SQL, @file, or - for stdin (create, set-sql, update, validate, dry-run-query, tune-query)',
|
|
288
|
-
query: 'Alias for --sql',
|
|
289
|
-
file: 'Read SQL from a file',
|
|
290
|
-
'no-validate': 'With create/set-sql: skip the post-write dry-run + rollback',
|
|
291
|
-
reason: 'Audit reason for a write',
|
|
292
|
-
'apply-globally': 'Required for system query tuning',
|
|
293
|
-
'reactivate-paused': 'Reactivate paused system copies after tuning',
|
|
294
|
-
'expected-hash': 'Expected current SQL SHA-256 hash',
|
|
295
|
-
notification: 'Review notification id tied to tuning',
|
|
296
|
-
note: 'Review note tied to tuning',
|
|
297
|
-
yes: 'Confirm write prompts',
|
|
298
|
-
force: 'Confirm write prompts',
|
|
299
|
-
},
|
|
300
|
-
run: (a, f) => require('./cli/security').alertRulesRoute(a, f),
|
|
301
|
-
},
|
|
302
|
-
channels: { desc: 'List alert channels', run: (a, f) => require('./cli/security').alertChannelsList(a, f) },
|
|
303
|
-
history: { desc: 'View alert history', flags: { limit: 'Max results' }, run: (a, f) => require('./cli/security').alertHistoryList(a, f) },
|
|
304
|
-
},
|
|
305
|
-
defaultSub: 'rules',
|
|
306
|
-
},
|
|
307
|
-
fp: {
|
|
308
|
-
desc: 'Manage false-positive exclusion rules',
|
|
309
|
-
usage: 'securenow fp <subcommand> [options]',
|
|
310
|
-
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)' },
|
|
311
|
-
sub: {
|
|
312
|
-
list: { desc: 'List all exclusion rules', run: (a, f) => require('./cli/fp').list(a, f) },
|
|
313
|
-
show: { desc: 'Show exclusion rule details', usage: 'securenow fp show <id>', run: (a, f) => require('./cli/fp').show(a, f) },
|
|
314
|
-
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) },
|
|
315
|
-
edit: { desc: 'Edit an exclusion rule', usage: 'securenow fp edit <id> [--active true/false] [--conditions \'[...]\']', run: (a, f) => require('./cli/fp').edit(a, f) },
|
|
316
|
-
delete: { desc: 'Delete an exclusion rule', usage: 'securenow fp delete <id> [--yes]', run: (a, f) => require('./cli/fp').remove(a, f) },
|
|
317
|
-
'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) },
|
|
318
|
-
'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) },
|
|
319
|
-
'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) },
|
|
320
|
-
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) },
|
|
321
|
-
},
|
|
322
|
-
defaultSub: 'list',
|
|
323
|
-
},
|
|
324
|
-
firewall: {
|
|
325
|
-
desc: 'Firewall status, per-app toggle, and IP testing',
|
|
326
|
-
usage: 'securenow firewall <subcommand> [options]',
|
|
327
|
-
flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
|
|
328
|
-
sub: {
|
|
329
|
-
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) },
|
|
330
|
-
apps: { desc: 'List apps with their firewall on/off state', run: (a, f) => require('./cli/firewall').appsList(a, f) },
|
|
331
|
-
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) },
|
|
332
|
-
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) },
|
|
333
|
-
'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) },
|
|
334
|
-
},
|
|
335
|
-
defaultSub: 'status',
|
|
336
|
-
},
|
|
337
|
-
ratelimit: {
|
|
338
|
-
desc: 'Manage soft rate-limit remediation rules',
|
|
339
|
-
usage: 'securenow ratelimit <list|add|parse|from-text|show|test|enable|disable|remove> [options]',
|
|
340
|
-
flags: {
|
|
341
|
-
app: 'Scope to app key (defaults to logged-in app)',
|
|
342
|
-
env: 'Scope to environment (default for create/test: production)',
|
|
343
|
-
environment: 'Alias for --env',
|
|
344
|
-
json: 'Output as JSON',
|
|
345
|
-
limit: 'Allowed requests per window',
|
|
346
|
-
window: 'Window size, e.g. 30s, 1m, 1h',
|
|
347
|
-
duration: 'Expiry, e.g. 24h or 7d',
|
|
348
|
-
route: 'Path pattern such as /api/login',
|
|
349
|
-
path: 'Alias for --route',
|
|
350
|
-
mode: 'Path mode: exact, prefix, or regex',
|
|
351
|
-
method: 'HTTP method, or ALL',
|
|
352
|
-
reason: 'Audit reason',
|
|
353
|
-
text: 'Natural-language rate-limit request',
|
|
354
|
-
},
|
|
355
|
-
sub: {
|
|
356
|
-
list: { desc: 'List rate-limit remediation rules', run: (a, f) => require('./cli/rateLimits').list(a, f) },
|
|
357
|
-
add: { desc: 'Create a rate-limit rule', usage: 'securenow ratelimit add [ip] --route /api/login --limit 5 --window 1m --duration 24h', run: (a, f) => require('./cli/rateLimits').add(a, f) },
|
|
358
|
-
parse: { desc: 'Parse natural language into a rate-limit draft', usage: 'securenow ratelimit parse "rate limit /api/login to 2 attempts per minute"', run: (a, f) => require('./cli/rateLimits').parseText(a, f) },
|
|
359
|
-
'from-text': { desc: 'Create a rate-limit rule from natural language', usage: 'securenow ratelimit from-text "rate limit /api/login to 2 attempts per minute" --yes', run: (a, f) => require('./cli/rateLimits').fromText(a, f) },
|
|
360
|
-
show: { desc: 'Show one rate-limit rule', usage: 'securenow ratelimit show <id>', run: (a, f) => require('./cli/rateLimits').show(a, f) },
|
|
361
|
-
test: { desc: 'Check whether a request would match rate-limit rules', usage: 'securenow ratelimit test <ip> --path /api/login --method POST', run: (a, f) => require('./cli/rateLimits').test(a, f) },
|
|
362
|
-
enable: { desc: 'Enable a rate-limit rule', usage: 'securenow ratelimit enable <id>', run: (a, f) => require('./cli/rateLimits').enable(a, f) },
|
|
363
|
-
disable: { desc: 'Disable a rate-limit rule', usage: 'securenow ratelimit disable <id>', run: (a, f) => require('./cli/rateLimits').disable(a, f) },
|
|
364
|
-
remove: { desc: 'Remove a rate-limit rule', usage: 'securenow ratelimit remove <id> [--reason "..."]', run: (a, f) => require('./cli/rateLimits').remove(a, f) },
|
|
365
|
-
},
|
|
366
|
-
defaultSub: 'list',
|
|
367
|
-
},
|
|
368
|
-
'rate-limit': {
|
|
369
|
-
desc: 'Alias for ratelimit',
|
|
370
|
-
usage: 'securenow rate-limit <list|add|parse|from-text|show|test|enable|disable|remove> [options]',
|
|
371
|
-
flags: {
|
|
372
|
-
app: 'Scope to app key (defaults to logged-in app)',
|
|
373
|
-
env: 'Scope to environment (default for create/test: production)',
|
|
374
|
-
environment: 'Alias for --env',
|
|
375
|
-
json: 'Output as JSON',
|
|
376
|
-
limit: 'Allowed requests per window',
|
|
377
|
-
window: 'Window size, e.g. 30s, 1m, 1h',
|
|
378
|
-
duration: 'Expiry, e.g. 24h or 7d',
|
|
379
|
-
route: 'Path pattern such as /api/login',
|
|
380
|
-
path: 'Alias for --route',
|
|
381
|
-
mode: 'Path mode: exact, prefix, or regex',
|
|
382
|
-
method: 'HTTP method, or ALL',
|
|
383
|
-
reason: 'Audit reason',
|
|
384
|
-
text: 'Natural-language rate-limit request',
|
|
385
|
-
},
|
|
386
|
-
sub: {
|
|
387
|
-
list: { desc: 'List rate-limit remediation rules', run: (a, f) => require('./cli/rateLimits').list(a, f) },
|
|
388
|
-
add: { desc: 'Create a rate-limit rule', usage: 'securenow rate-limit add [ip] --route /api/login --limit 5 --window 1m --duration 24h', run: (a, f) => require('./cli/rateLimits').add(a, f) },
|
|
389
|
-
parse: { desc: 'Parse natural language into a rate-limit draft', usage: 'securenow rate-limit parse "rate limit /api/login to 2 attempts per minute"', run: (a, f) => require('./cli/rateLimits').parseText(a, f) },
|
|
390
|
-
'from-text': { desc: 'Create a rate-limit rule from natural language', usage: 'securenow rate-limit from-text "rate limit /api/login to 2 attempts per minute" --yes', run: (a, f) => require('./cli/rateLimits').fromText(a, f) },
|
|
391
|
-
show: { desc: 'Show one rate-limit rule', usage: 'securenow rate-limit show <id>', run: (a, f) => require('./cli/rateLimits').show(a, f) },
|
|
392
|
-
test: { desc: 'Check whether a request would match rate-limit rules', usage: 'securenow rate-limit test <ip> --path /api/login --method POST', run: (a, f) => require('./cli/rateLimits').test(a, f) },
|
|
393
|
-
enable: { desc: 'Enable a rate-limit rule', usage: 'securenow rate-limit enable <id>', run: (a, f) => require('./cli/rateLimits').enable(a, f) },
|
|
394
|
-
disable: { desc: 'Disable a rate-limit rule', usage: 'securenow rate-limit disable <id>', run: (a, f) => require('./cli/rateLimits').disable(a, f) },
|
|
395
|
-
remove: { desc: 'Remove a rate-limit rule', usage: 'securenow rate-limit remove <id> [--reason "..."]', run: (a, f) => require('./cli/rateLimits').remove(a, f) },
|
|
396
|
-
},
|
|
397
|
-
defaultSub: 'list',
|
|
398
|
-
},
|
|
399
|
-
challenge: {
|
|
400
|
-
desc: 'Manage CAPTCHA / proof-of-work challenge remediation rules',
|
|
401
|
-
usage: 'securenow challenge <list|add|show|test|enable|disable|remove> [options]',
|
|
402
|
-
flags: {
|
|
403
|
-
app: 'Scope to app key (defaults to logged-in app)',
|
|
404
|
-
env: 'Scope to environment (default for create/test: production)',
|
|
405
|
-
environment: 'Alias for --env',
|
|
406
|
-
json: 'Output as JSON',
|
|
407
|
-
difficulty: 'Proof-of-work strength in leading zero bits (4-28, default 14)',
|
|
408
|
-
clearance: 'How long a solve clears, e.g. 30m, 1h (default 30m)',
|
|
409
|
-
route: 'Path pattern such as /login',
|
|
410
|
-
path: 'Alias for --route',
|
|
411
|
-
mode: 'Path mode: exact, prefix, or regex',
|
|
412
|
-
method: 'HTTP method, or ALL',
|
|
413
|
-
duration: 'Rule expiry, e.g. 24h or 7d',
|
|
414
|
-
reason: 'Reason note',
|
|
415
|
-
'escalate-to-block': 'Promote to a hard block after repeated failures',
|
|
416
|
-
'fail-threshold': 'Failures before escalation (default 10)',
|
|
417
|
-
'block-ttl-hours': 'Block duration when escalating (default 24)',
|
|
418
|
-
},
|
|
419
|
-
sub: {
|
|
420
|
-
list: { desc: 'List challenge remediation rules', run: (a, f) => require('./cli/challenges').list(a, f) },
|
|
421
|
-
add: { desc: 'Create a challenge rule', usage: 'securenow challenge add [ip] --route /login --difficulty 16 --clearance 30m', run: (a, f) => require('./cli/challenges').add(a, f) },
|
|
422
|
-
show: { desc: 'Show one challenge rule', usage: 'securenow challenge show <id>', run: (a, f) => require('./cli/challenges').show(a, f) },
|
|
423
|
-
test: { desc: 'Check whether a request would be challenged', usage: 'securenow challenge test <ip> --path /login --method GET', run: (a, f) => require('./cli/challenges').test(a, f) },
|
|
424
|
-
enable: { desc: 'Enable a challenge rule', usage: 'securenow challenge enable <id>', run: (a, f) => require('./cli/challenges').enable(a, f) },
|
|
425
|
-
disable: { desc: 'Disable a challenge rule', usage: 'securenow challenge disable <id>', run: (a, f) => require('./cli/challenges').disable(a, f) },
|
|
426
|
-
remove: { desc: 'Remove a challenge rule', usage: 'securenow challenge remove <id> [--reason "..."]', run: (a, f) => require('./cli/challenges').remove(a, f) },
|
|
427
|
-
},
|
|
428
|
-
defaultSub: 'list',
|
|
429
|
-
},
|
|
430
|
-
automation: {
|
|
431
|
-
desc: 'Manage automation rules for blocklist actions',
|
|
432
|
-
usage: 'securenow automation <list|defaults|show|create|update|dry-run|execute|delete> [rule-id] [options]',
|
|
433
|
-
flags: {
|
|
434
|
-
json: 'Output as JSON',
|
|
435
|
-
body: 'Full JSON body for create/update',
|
|
436
|
-
file: 'Read JSON body from file',
|
|
437
|
-
name: 'Rule name',
|
|
438
|
-
description: 'Rule description',
|
|
439
|
-
conditions: 'Conditions JSON array',
|
|
440
|
-
actions: 'Actions JSON array',
|
|
441
|
-
logic: 'Condition logic: AND or OR',
|
|
442
|
-
status: 'Rule status: active or disabled',
|
|
443
|
-
app: 'Comma-separated app key(s) to scope the rule',
|
|
444
|
-
apps: 'Alias for --app',
|
|
445
|
-
'applications-all': 'Scope rule to all apps',
|
|
446
|
-
env: 'Comma-separated environment(s) to scope the rule',
|
|
447
|
-
environment: 'Alias for --env',
|
|
448
|
-
environments: 'Alias for --env',
|
|
449
|
-
'environments-all': 'Scope rule to all environments',
|
|
450
|
-
limit: 'Dry-run notification scan limit',
|
|
451
|
-
'sample-limit': 'Dry-run sample count',
|
|
452
|
-
yes: 'Confirm execute/delete',
|
|
453
|
-
force: 'Alias for --yes',
|
|
454
|
-
},
|
|
455
|
-
sub: {
|
|
456
|
-
list: { desc: 'List automation rules', run: (a, f) => require('./cli/automation').list(a, f) },
|
|
457
|
-
defaults: { desc: 'Ensure SecureNow default risk-score automation rules', usage: 'securenow automation defaults [--force-enable] [--yes]', run: (a, f) => require('./cli/automation').defaults(a, f) },
|
|
458
|
-
show: { desc: 'Show automation rule details', usage: 'securenow automation show <rule-id>', run: (a, f) => require('./cli/automation').show(a, f) },
|
|
459
|
-
create: { desc: 'Create automation rule', usage: 'securenow automation create --name "..." --conditions \'[...]\' --actions \'[...]\'', run: (a, f) => require('./cli/automation').create(a, f) },
|
|
460
|
-
update: { desc: 'Update automation rule', usage: 'securenow automation update <rule-id> --body \'{...}\'', run: (a, f) => require('./cli/automation').update(a, f) },
|
|
461
|
-
'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) },
|
|
462
|
-
execute: { desc: 'Execute an automation rule now', usage: 'securenow automation execute <rule-id> --yes', run: (a, f) => require('./cli/automation').execute(a, f) },
|
|
463
|
-
delete: { desc: 'Delete an automation rule', usage: 'securenow automation delete <rule-id> --yes', run: (a, f) => require('./cli/automation').remove(a, f) },
|
|
464
|
-
},
|
|
465
|
-
defaultSub: 'list',
|
|
466
|
-
},
|
|
467
|
-
blocklist: {
|
|
468
|
-
desc: 'Manage IP blocklist',
|
|
469
|
-
usage: 'securenow blocklist <subcommand> [options]',
|
|
470
|
-
flags: { app: 'Scope to app key', env: 'Scope to environment', environment: 'Alias for --env', page: 'Page number', limit: 'Max results', status: 'Entry status: active or removed', 'approval-status': 'Approval filter: pending, approved, or rejected', search: 'IP prefix search', view: 'List view: all or operational', reason: 'Audit reason for unblock', route: 'Route pattern such as /admin*', path: 'Alias for --route', pattern: 'Alias for --route', mode: 'Route match mode: prefix, exact, or regex', 'path-mode': 'Alias for --mode', method: 'HTTP method, or ALL', duration: 'Expiry, e.g. 24h or 7d', json: 'Output as JSON' },
|
|
471
|
-
sub: {
|
|
472
|
-
list: { desc: 'List blocked IPs', run: (a, f) => require('./cli/security').blocklistList(a, f) },
|
|
473
|
-
add: { desc: 'Block an IP', usage: 'securenow blocklist add <ip> [--route /admin*] [--mode prefix] [--method GET] [--app <key>] [--env production] [--duration 24h] [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistAdd(a, f) },
|
|
474
|
-
unblock: { desc: 'Unblock an IP and keep block history', usage: 'securenow blocklist unblock <id> [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
|
|
475
|
-
remove: { desc: 'Unblock an IP (compatibility alias)', usage: 'securenow blocklist remove <id> [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
|
|
476
|
-
stats: { desc: 'Blocklist statistics', run: (a, f) => require('./cli/security').blocklistStats(a, f) },
|
|
477
|
-
},
|
|
478
|
-
defaultSub: 'list',
|
|
479
|
-
},
|
|
480
|
-
revoke: {
|
|
481
|
-
desc: 'Revoke sessions/users (kill stolen sessions; the SDK enforces via securenow/sessions)',
|
|
482
|
-
usage: 'securenow revoke <session <id> | user <id> | list | restore <id>> [options]',
|
|
483
|
-
flags: { reason: 'Audit reason', duration: 'Expiry, e.g. 24h or 7d (default 7d)', app: 'Scope to app key', env: 'Scope to environment', environment: 'Alias for --env', type: 'List filter: session or user', status: 'List filter: active or restored', json: 'Output as JSON' },
|
|
484
|
-
sub: {
|
|
485
|
-
session: { desc: 'Revoke a session id', usage: 'securenow revoke session <session-id> [--reason <r>] [--duration 24h] [--app <key>]', run: (a, f) => require('./cli/security').revokeRoute(['session', ...a], f) },
|
|
486
|
-
user: { desc: 'Revoke all sessions of a user id', usage: 'securenow revoke user <user-id> [--reason <r>] [--duration 24h] [--app <key>]', run: (a, f) => require('./cli/security').revokeRoute(['user', ...a], f) },
|
|
487
|
-
list: { desc: 'List active revocations', run: (a, f) => require('./cli/security').revokeRoute(['list', ...a], f) },
|
|
488
|
-
restore: { desc: 'Lift a revocation', usage: 'securenow revoke restore <id> [--reason <r>]', run: (a, f) => require('./cli/security').revokeRoute(['restore', ...a], f) },
|
|
489
|
-
},
|
|
490
|
-
defaultSub: 'list',
|
|
491
|
-
},
|
|
492
|
-
allowlist: {
|
|
493
|
-
desc: 'Manage IP allowlist (only allow listed IPs)',
|
|
494
|
-
usage: 'securenow allowlist <subcommand> [options]',
|
|
495
|
-
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' },
|
|
496
|
-
sub: {
|
|
497
|
-
list: { desc: 'List allowed IPs', run: (a, f) => require('./cli/security').allowlistList(a, f) },
|
|
498
|
-
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) },
|
|
499
|
-
remove: { desc: 'Remove an allowed IP', usage: 'securenow allowlist remove <id>', run: (a, f) => require('./cli/security').allowlistRemove(a, f) },
|
|
500
|
-
stats: { desc: 'Allowlist statistics', run: (a, f) => require('./cli/security').allowlistStats(a, f) },
|
|
501
|
-
},
|
|
502
|
-
defaultSub: 'list',
|
|
503
|
-
},
|
|
504
|
-
trusted: {
|
|
505
|
-
desc: 'Manage trusted IPs',
|
|
506
|
-
usage: 'securenow trusted <subcommand> [options]',
|
|
507
|
-
flags: { app: 'Scope to app key(s), comma-separated', env: 'Scope to environment', environment: 'Alias for --env', json: 'Output as JSON' },
|
|
508
|
-
sub: {
|
|
509
|
-
list: { desc: 'List trusted IPs', run: (a, f) => require('./cli/security').trustedList(a, f) },
|
|
510
|
-
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) },
|
|
511
|
-
remove: { desc: 'Remove trusted IP', usage: 'securenow trusted remove <id>', run: (a, f) => require('./cli/security').trustedRemove(a, f) },
|
|
512
|
-
},
|
|
513
|
-
defaultSub: 'list',
|
|
514
|
-
},
|
|
515
|
-
ip: {
|
|
516
|
-
desc: 'IP intelligence lookup',
|
|
517
|
-
usage: 'securenow ip <ip-address>',
|
|
518
|
-
sub: {
|
|
519
|
-
lookup: { desc: 'Look up IP intelligence', run: (a, f) => require('./cli/security').ipLookup(a, f) },
|
|
520
|
-
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) },
|
|
521
|
-
},
|
|
522
|
-
defaultAction: (a, f) => require('./cli/security').ipLookup(a, f),
|
|
523
|
-
},
|
|
524
|
-
forensics: {
|
|
525
|
-
desc: 'Run forensic queries (natural language → SQL)',
|
|
526
|
-
usage: 'securenow forensics <query> [--app <key>]',
|
|
527
|
-
sub: {
|
|
528
|
-
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) },
|
|
529
|
-
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) },
|
|
530
|
-
library: { desc: 'View saved queries', run: (a, f) => require('./cli/security').forensicsLibrary(a, f) },
|
|
531
|
-
},
|
|
532
|
-
defaultAction: (a, f) => require('./cli/security').forensicsQuery(a, f),
|
|
533
|
-
},
|
|
534
|
-
instances: {
|
|
535
|
-
desc: 'Manage ClickHouse instances',
|
|
536
|
-
usage: 'securenow instances <subcommand> [options]',
|
|
537
|
-
sub: {
|
|
538
|
-
list: { desc: 'List instances', run: (a, f) => require('./cli/security').instancesList(a, f) },
|
|
539
|
-
test: { desc: 'Test instance connection', usage: 'securenow instances test <id>', run: (a, f) => require('./cli/security').instancesTest(a, f) },
|
|
540
|
-
},
|
|
541
|
-
defaultSub: 'list',
|
|
542
|
-
},
|
|
543
|
-
analytics: {
|
|
544
|
-
desc: 'View response analytics',
|
|
545
|
-
usage: 'securenow analytics [--app <key>]',
|
|
546
|
-
run: (a, f) => require('./cli/security').analytics(a, f),
|
|
547
|
-
},
|
|
548
|
-
status: {
|
|
549
|
-
desc: 'Dashboard overview',
|
|
550
|
-
usage: 'securenow status [--app <key>] [--env local|production|all]',
|
|
551
|
-
flags: {
|
|
552
|
-
app: 'App key to highlight',
|
|
553
|
-
env: 'Environment to display in protection summary (default: credentials file, use all for every env)',
|
|
554
|
-
environment: 'Alias for --env',
|
|
555
|
-
},
|
|
556
|
-
run: (a, f) => require('./cli/monitor').status(a, f),
|
|
557
|
-
},
|
|
558
|
-
config: {
|
|
559
|
-
desc: 'Manage CLI configuration',
|
|
560
|
-
usage: 'securenow config <set|get> [key] [value]',
|
|
561
|
-
sub: {
|
|
562
|
-
set: {
|
|
563
|
-
desc: 'Set a config value',
|
|
564
|
-
usage: 'securenow config set <key> <value>',
|
|
565
|
-
run: (a) => {
|
|
566
|
-
const conf = require('./cli/config');
|
|
567
|
-
const [key, ...rest] = a;
|
|
568
|
-
const value = rest.join(' ');
|
|
569
|
-
if (!key || !value) { ui.error('Usage: securenow config set <key> <value>'); process.exit(1); }
|
|
570
|
-
conf.setConfigValue(key, value);
|
|
571
|
-
ui.success(`${key} = ${value}`);
|
|
572
|
-
},
|
|
573
|
-
},
|
|
574
|
-
get: {
|
|
575
|
-
desc: 'Get a config value',
|
|
576
|
-
usage: 'securenow config get [key]',
|
|
577
|
-
run: (a) => {
|
|
578
|
-
const conf = require('./cli/config');
|
|
579
|
-
const key = a[0];
|
|
580
|
-
if (key) {
|
|
581
|
-
const val = conf.getConfigValue(key);
|
|
582
|
-
console.log(val != null ? val : ui.c.dim('(not set)'));
|
|
583
|
-
} else {
|
|
584
|
-
const all = conf.loadConfig();
|
|
585
|
-
console.log('');
|
|
586
|
-
ui.keyValue(Object.entries(all).map(([k, v]) => [k, v != null ? String(v) : ui.c.dim('(not set)')]));
|
|
587
|
-
console.log('');
|
|
588
|
-
}
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
path: {
|
|
592
|
-
desc: 'Show config file path',
|
|
593
|
-
run: () => {
|
|
594
|
-
const conf = require('./cli/config');
|
|
595
|
-
console.log(`Config: ${conf.CONFIG_FILE}`);
|
|
596
|
-
console.log(`Credentials: ${conf.CREDENTIALS_FILE}`);
|
|
597
|
-
if (conf.hasLocalCredentials()) {
|
|
598
|
-
console.log(`Local creds: ${conf.LOCAL_CREDENTIALS_FILE} ${require('./cli/ui').c.green('(active)')}`);
|
|
599
|
-
}
|
|
600
|
-
console.log(`Auth source: ${conf.getAuthSource()}`);
|
|
601
|
-
},
|
|
602
|
-
},
|
|
603
|
-
},
|
|
604
|
-
},
|
|
605
|
-
run: {
|
|
606
|
-
desc: 'Run a Node.js app with automatic OTel instrumentation',
|
|
607
|
-
usage: 'securenow run [node-flags] [--firewall-only] <script> [app-args]',
|
|
608
|
-
flags: {
|
|
609
|
-
watch: 'Enable Node.js watch mode',
|
|
610
|
-
inspect: 'Enable Node.js inspector',
|
|
611
|
-
'firewall-only': 'Preload firewall without OTel tracing overhead',
|
|
612
|
-
},
|
|
613
|
-
rawArgv: true,
|
|
614
|
-
run: (rawArgs) => require('./cli/run').run(rawArgs),
|
|
615
|
-
},
|
|
616
|
-
redact: {
|
|
617
|
-
desc: 'Redact sensitive fields from a JSON payload',
|
|
618
|
-
usage: "securenow redact '<json>' [--fields f1,f2] [--json]",
|
|
619
|
-
flags: { fields: 'Comma-separated extra field names to redact (added to defaults)' },
|
|
620
|
-
run: (a, f) => require('./cli/utils').redact(a, f),
|
|
621
|
-
},
|
|
622
|
-
cidr: {
|
|
623
|
-
desc: 'CIDR utilities (match and parse)',
|
|
624
|
-
usage: 'securenow cidr <match|parse> ...',
|
|
625
|
-
sub: {
|
|
626
|
-
match: {
|
|
627
|
-
desc: 'Check if an IP matches a CIDR list',
|
|
628
|
-
usage: 'securenow cidr match <ip> <cidr1,cidr2,...>',
|
|
629
|
-
run: (a, f) => require('./cli/utils').cidrMatch(a, f),
|
|
630
|
-
},
|
|
631
|
-
parse: {
|
|
632
|
-
desc: 'Parse a CIDR and show network/broadcast/size',
|
|
633
|
-
usage: 'securenow cidr parse <cidr>',
|
|
634
|
-
run: (a, f) => require('./cli/utils').cidrParse(a, f),
|
|
635
|
-
},
|
|
636
|
-
},
|
|
637
|
-
},
|
|
638
|
-
log: {
|
|
639
|
-
desc: 'Emit logs via OTLP (for scripts, cron, debugging)',
|
|
640
|
-
usage: 'securenow log send <message> [--level info|warn|error] [--attrs k=v,k=v]',
|
|
641
|
-
sub: {
|
|
642
|
-
send: {
|
|
643
|
-
desc: 'Send a single log record to the OTLP collector',
|
|
644
|
-
flags: {
|
|
645
|
-
env: 'Deployment environment for this log (defaults to credentials file)',
|
|
646
|
-
environment: 'Alias for --env',
|
|
647
|
-
level: 'Severity (trace|debug|info|warn|error|fatal)',
|
|
648
|
-
attrs: 'Comma-separated key=value attributes',
|
|
649
|
-
},
|
|
650
|
-
run: (a, f) => require('./cli/diagnostics').logSend(a, f),
|
|
651
|
-
},
|
|
652
|
-
},
|
|
653
|
-
defaultSub: 'send',
|
|
654
|
-
},
|
|
655
|
-
event: {
|
|
656
|
-
desc: 'Emit a custom security event to SecureNow (for scripts, testing, non-JS apps)',
|
|
657
|
-
usage: 'securenow event send <type> [--user <id>] [--session <id>] [--ip <ip>] [--attrs k=v,k=v]',
|
|
658
|
-
sub: {
|
|
659
|
-
send: {
|
|
660
|
-
desc: 'Send a single custom event to the /v1/events ingest',
|
|
661
|
-
flags: {
|
|
662
|
-
env: 'Deployment environment for this event (defaults to credentials file)',
|
|
663
|
-
environment: 'Alias for --env',
|
|
664
|
-
user: 'End-user / account id (enduser.id)',
|
|
665
|
-
session: 'Session id (session.id)',
|
|
666
|
-
ip: 'End-user client IP',
|
|
667
|
-
'user-agent': 'End-user agent string',
|
|
668
|
-
level: 'Severity (trace|debug|info|warn|error|fatal)',
|
|
669
|
-
attrs: 'Comma-separated key=value attributes',
|
|
670
|
-
},
|
|
671
|
-
run: (a, f) => require('./cli/diagnostics').eventSend(a, f),
|
|
672
|
-
},
|
|
673
|
-
},
|
|
674
|
-
defaultSub: 'send',
|
|
675
|
-
},
|
|
676
|
-
'test-span': {
|
|
677
|
-
desc: 'Emit a test span to verify collector connectivity',
|
|
678
|
-
usage: 'securenow test-span [<span-name>] [--env local|production]',
|
|
679
|
-
flags: {
|
|
680
|
-
env: 'Deployment environment for this span (defaults to credentials file)',
|
|
681
|
-
environment: 'Alias for --env',
|
|
682
|
-
},
|
|
683
|
-
run: (a, f) => require('./cli/diagnostics').testSpan(a, f),
|
|
684
|
-
},
|
|
685
|
-
doctor: {
|
|
686
|
-
desc: 'Diagnose SecureNow configuration and collector connectivity',
|
|
687
|
-
usage: 'securenow doctor [--json]',
|
|
688
|
-
run: (a, f) => require('./cli/diagnostics').doctor(a, f),
|
|
689
|
-
},
|
|
690
|
-
env: {
|
|
691
|
-
desc: 'Show resolved SecureNow configuration (service name, endpoints, credentials)',
|
|
692
|
-
usage: 'securenow env [--json]',
|
|
693
|
-
run: (a, f) => require('./cli/diagnostics').env(a, f),
|
|
694
|
-
},
|
|
695
|
-
mcp: {
|
|
696
|
-
desc: 'Start the SecureNow MCP server over stdio',
|
|
697
|
-
usage: 'securenow mcp',
|
|
698
|
-
run: () => require('./mcp/server'),
|
|
699
|
-
},
|
|
700
|
-
version: {
|
|
701
|
-
desc: 'Show CLI version',
|
|
702
|
-
run: () => {
|
|
703
|
-
try {
|
|
704
|
-
const pkg = require('./package.json');
|
|
705
|
-
console.log(`securenow v${pkg.version}`);
|
|
706
|
-
} catch {
|
|
707
|
-
console.log('securenow (version unknown)');
|
|
708
|
-
}
|
|
709
|
-
},
|
|
710
|
-
},
|
|
711
|
-
};
|
|
712
|
-
|
|
713
|
-
// ── Help System ──
|
|
714
|
-
|
|
715
|
-
function showBanner() {
|
|
716
|
-
console.log('');
|
|
717
|
-
console.log(` ${ui.c.bold(ui.c.cyan('SecureNow CLI'))} ${ui.c.dim('— Security observability from the terminal')}`);
|
|
718
|
-
console.log('');
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
function showHelp(commandName) {
|
|
722
|
-
if (commandName && COMMANDS[commandName]) {
|
|
723
|
-
const cmd = COMMANDS[commandName];
|
|
724
|
-
showBanner();
|
|
725
|
-
console.log(` ${ui.c.bold(commandName)} — ${cmd.desc}`);
|
|
726
|
-
console.log('');
|
|
727
|
-
if (cmd.usage) {
|
|
728
|
-
console.log(` ${ui.c.bold('USAGE')}`);
|
|
729
|
-
console.log(` ${cmd.usage}`);
|
|
730
|
-
console.log('');
|
|
731
|
-
}
|
|
732
|
-
if (cmd.sub) {
|
|
733
|
-
console.log(` ${ui.c.bold('SUBCOMMANDS')}`);
|
|
734
|
-
const entries = Object.entries(cmd.sub);
|
|
735
|
-
const maxLen = entries.reduce((m, [k]) => Math.max(m, k.length), 0);
|
|
736
|
-
for (const [name, sub] of entries) {
|
|
737
|
-
console.log(` ${ui.c.cyan(name.padEnd(maxLen + 2))} ${sub.desc}`);
|
|
738
|
-
}
|
|
739
|
-
console.log('');
|
|
740
|
-
}
|
|
741
|
-
if (cmd.flags) {
|
|
742
|
-
console.log(` ${ui.c.bold('FLAGS')}`);
|
|
743
|
-
for (const [flag, desc] of Object.entries(cmd.flags)) {
|
|
744
|
-
console.log(` --${ui.c.cyan(flag.padEnd(16))} ${desc}`);
|
|
745
|
-
}
|
|
746
|
-
console.log('');
|
|
747
|
-
}
|
|
748
|
-
console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
|
|
749
|
-
console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
|
|
750
|
-
console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help`);
|
|
751
|
-
console.log('');
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
showBanner();
|
|
756
|
-
|
|
757
|
-
const groups = {
|
|
758
|
-
'Run': ['run'],
|
|
759
|
-
'Authentication': ['login', 'logout', 'whoami', 'credentials'],
|
|
760
|
-
'Applications': ['apps', 'init', 'status'],
|
|
761
|
-
'Observe': ['traces', 'logs', 'analytics'],
|
|
762
|
-
'Detect & Respond': ['human', 'notifications', 'alerts', 'fp'],
|
|
763
|
-
'Investigate': ['ip', 'forensics'],
|
|
764
|
-
'Firewall': ['firewall'],
|
|
765
|
-
'Remediation': ['automation', 'ratelimit', 'challenge', 'blocklist', 'revoke', 'allowlist', 'trusted'],
|
|
766
|
-
'Telemetry': ['log', 'event', 'test-span'],
|
|
767
|
-
'Utilities': ['redact', 'cidr', 'doctor', 'env', 'mcp'],
|
|
768
|
-
'Settings': ['instances', 'config', 'version'],
|
|
769
|
-
};
|
|
770
|
-
|
|
771
|
-
for (const [group, cmds] of Object.entries(groups)) {
|
|
772
|
-
console.log(` ${ui.c.bold(group)}`);
|
|
773
|
-
for (const name of cmds) {
|
|
774
|
-
const cmd = COMMANDS[name];
|
|
775
|
-
if (cmd) {
|
|
776
|
-
console.log(` ${ui.c.cyan(name.padEnd(18))} ${cmd.desc}`);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
console.log('');
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
|
|
783
|
-
console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
|
|
784
|
-
console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help for a command`);
|
|
785
|
-
console.log('');
|
|
786
|
-
console.log(` ${ui.c.dim('Run')} securenow help <command> ${ui.c.dim('for detailed usage')}`);
|
|
787
|
-
console.log('');
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// ── Main Router ──
|
|
791
|
-
|
|
792
|
-
async function main() {
|
|
793
|
-
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
794
|
-
const cmdName = positional[0] || 'help';
|
|
795
|
-
|
|
796
|
-
if (cmdName === 'help' || flags.help) {
|
|
797
|
-
showHelp(flags.help === true ? positional[0] : flags.help || positional[1] || (cmdName !== 'help' ? cmdName : null));
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
const cmd = COMMANDS[cmdName];
|
|
802
|
-
if (!cmd) {
|
|
803
|
-
// Auto-detect: if the first arg looks like a file path, treat it as `securenow run <file>`
|
|
804
|
-
if (/\.(m?[jt]sx?|cjs)$/.test(cmdName) || cmdName.includes('/') || cmdName.includes('\\')) {
|
|
805
|
-
return COMMANDS.run.run(process.argv.slice(2));
|
|
806
|
-
}
|
|
807
|
-
ui.error(`Unknown command: ${cmdName}`);
|
|
808
|
-
ui.info('Run `securenow help` for a list of commands.');
|
|
809
|
-
process.exit(1);
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
if (cmd.rawArgv) {
|
|
813
|
-
// Pass raw argv (everything after the command name) so the command can
|
|
814
|
-
// forward flags like --watch, --inspect verbatim to child processes.
|
|
815
|
-
const cmdIdx = process.argv.indexOf(cmdName);
|
|
816
|
-
const rawArgs = cmdIdx !== -1 ? process.argv.slice(cmdIdx + 1) : positional.slice(1);
|
|
817
|
-
await cmd.run(rawArgs);
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
if (cmd.run && !cmd.sub) {
|
|
822
|
-
await cmd.run(positional.slice(1), flags);
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
if (cmd.sub) {
|
|
827
|
-
let subName = positional[1];
|
|
828
|
-
let subArgs = positional.slice(2);
|
|
829
|
-
|
|
830
|
-
if (subName && cmd.sub[subName]) {
|
|
831
|
-
if (flags.help) {
|
|
832
|
-
showHelp(cmdName);
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
await cmd.sub[subName].run(subArgs, flags);
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
if (cmd.defaultAction) {
|
|
840
|
-
const allArgs = subName ? [subName, ...subArgs] : [];
|
|
841
|
-
await cmd.defaultAction(allArgs, flags);
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
if (cmd.defaultSub) {
|
|
846
|
-
const allArgs = subName ? [subName, ...subArgs] : [];
|
|
847
|
-
await cmd.sub[cmd.defaultSub].run(allArgs, flags);
|
|
848
|
-
return;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
showHelp(cmdName);
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
if (cmd.run) {
|
|
856
|
-
await cmd.run(positional.slice(1), flags);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
main().catch((err) => {
|
|
861
|
-
if (err.name !== 'CLIError') {
|
|
862
|
-
ui.error(err.message || 'An unexpected error occurred');
|
|
863
|
-
}
|
|
864
|
-
process.exit(1);
|
|
865
|
-
});
|
|
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 = {
|
|
42
|
+
init: {
|
|
43
|
+
desc: 'Set up SecureNow in the current project (instrumentation + runtime credentials)',
|
|
44
|
+
usage: 'securenow init [--env local] [--key <API_KEY>] [--ts|--js] [--src|--root] [--force]',
|
|
45
|
+
flags: {
|
|
46
|
+
env: 'Deployment environment to write into .securenow/runtime.json (default: local)',
|
|
47
|
+
environment: 'Alias for --env',
|
|
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
|
+
},
|
|
58
|
+
login: {
|
|
59
|
+
desc: 'Onboard SecureNow admin auth and app runtime config',
|
|
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)',
|
|
64
|
+
},
|
|
65
|
+
run: (a, f) => require('./cli/auth').login(a, f),
|
|
66
|
+
},
|
|
67
|
+
admin: {
|
|
68
|
+
desc: 'Manage admin/control-plane CLI and MCP auth',
|
|
69
|
+
usage: 'securenow admin <subcommand> [options]',
|
|
70
|
+
sub: {
|
|
71
|
+
login: {
|
|
72
|
+
desc: 'Authenticate admin/control-plane CLI and MCP tools',
|
|
73
|
+
usage: 'securenow admin login [--token <TOKEN>] [--global]',
|
|
74
|
+
flags: {
|
|
75
|
+
token: 'Authenticate with a token directly',
|
|
76
|
+
global: 'Save admin auth to ~/.securenow/admin.json',
|
|
77
|
+
},
|
|
78
|
+
run: (a, f) => require('./cli/auth').adminLogin(a, f),
|
|
79
|
+
},
|
|
80
|
+
logout: {
|
|
81
|
+
desc: 'Clear admin/control-plane auth',
|
|
82
|
+
usage: 'securenow admin logout [--global]',
|
|
83
|
+
flags: { global: 'Clear global admin auth (~/.securenow/admin.json)' },
|
|
84
|
+
run: (a, f) => require('./cli/auth').logout(a, f),
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
defaultSub: 'login',
|
|
88
|
+
},
|
|
89
|
+
app: {
|
|
90
|
+
desc: 'Connect the current project runtime to a SecureNow app',
|
|
91
|
+
usage: 'securenow app <subcommand> [options]',
|
|
92
|
+
sub: {
|
|
93
|
+
connect: {
|
|
94
|
+
desc: 'Select/create app, mint runtime API key, and write SDK runtime config',
|
|
95
|
+
usage: 'securenow app connect [--global]',
|
|
96
|
+
flags: { global: 'Save runtime config to ~/.securenow/runtime.json' },
|
|
97
|
+
run: (a, f) => require('./cli/auth').appConnect(a, f),
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
defaultSub: 'connect',
|
|
101
|
+
},
|
|
102
|
+
logout: {
|
|
103
|
+
desc: 'Clear admin/control-plane auth',
|
|
104
|
+
usage: 'securenow logout [--global]',
|
|
105
|
+
flags: { global: 'Clear global admin auth (~/.securenow/admin.json)' },
|
|
106
|
+
run: (a, f) => require('./cli/auth').logout(a, f),
|
|
107
|
+
},
|
|
108
|
+
whoami: {
|
|
109
|
+
desc: 'Show admin auth and SDK runtime status',
|
|
110
|
+
usage: 'securenow whoami',
|
|
111
|
+
run: () => require('./cli/auth').whoami(),
|
|
112
|
+
},
|
|
113
|
+
'api-key': {
|
|
114
|
+
desc: 'Manage the runtime API key stored in runtime credentials',
|
|
115
|
+
usage: 'securenow api-key <subcommand> [options]',
|
|
116
|
+
sub: {
|
|
117
|
+
create: {
|
|
118
|
+
desc: 'Create a runtime API key with your session token and save it',
|
|
119
|
+
usage: 'securenow api-key create [name] [--name <name>] [--preset runtime_app] [--app <appKeyOrId>] [--global]',
|
|
120
|
+
flags: {
|
|
121
|
+
name: 'Human-readable key name',
|
|
122
|
+
preset: 'API key preset to create (default: runtime_app)',
|
|
123
|
+
app: 'Application key or id to scope this key to (defaults to current runtime app)',
|
|
124
|
+
global: 'Save to ~/.securenow/ instead of project-local',
|
|
125
|
+
},
|
|
126
|
+
run: (a, f) => require('./cli/apiKey').create(a, f),
|
|
127
|
+
},
|
|
128
|
+
set: {
|
|
129
|
+
desc: 'Save an API key (snk_live_...) to runtime credentials',
|
|
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
|
+
},
|
|
146
|
+
defaultSub: 'show',
|
|
147
|
+
},
|
|
148
|
+
credentials: {
|
|
149
|
+
desc: 'Create production/runtime SecureNow credentials files',
|
|
150
|
+
usage: 'securenow credentials <subcommand> [options]',
|
|
151
|
+
sub: {
|
|
152
|
+
runtime: {
|
|
153
|
+
desc: 'Write tokenless runtime credentials for production file-based deploys',
|
|
154
|
+
usage: 'securenow credentials runtime [--env production] [--out .securenow/credentials.production.json] [--stdout]',
|
|
155
|
+
flags: {
|
|
156
|
+
env: 'Deployment environment for this runtime file (default: production)',
|
|
157
|
+
environment: 'Alias for --env',
|
|
158
|
+
out: 'Output path (default: .securenow/credentials.<env>.json)',
|
|
159
|
+
output: 'Alias for --out',
|
|
160
|
+
stdout: 'Print JSON to stdout instead of writing a file',
|
|
161
|
+
},
|
|
162
|
+
run: (a, f) => require('./cli/credentials').runtime(a, f),
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
defaultSub: 'runtime',
|
|
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: {
|
|
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
|
+
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
|
+
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: {
|
|
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
|
+
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
|
+
},
|
|
200
|
+
notifications: {
|
|
201
|
+
desc: 'Manage notifications',
|
|
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() },
|
|
208
|
+
},
|
|
209
|
+
defaultSub: 'list',
|
|
210
|
+
},
|
|
211
|
+
human: {
|
|
212
|
+
desc: 'Work the human action queue prepared by SecureNow AI',
|
|
213
|
+
usage: 'securenow human <list|show|block|fp|report|action|prompt|work> [row|notificationId:ip] [options]',
|
|
214
|
+
flags: {
|
|
215
|
+
json: 'Output as JSON',
|
|
216
|
+
page: 'Queue page number',
|
|
217
|
+
limit: 'Queue page size',
|
|
218
|
+
search: 'Search IP, rule, path, or verdict',
|
|
219
|
+
reason: 'Reason for block/false-positive decisions',
|
|
220
|
+
summary: 'Decision report summary to write to the IP history',
|
|
221
|
+
outcome: 'Decision report outcome: blocked, false_positive, clean, deferred, case_action, rule_tuning, new_alert_rule, ambiguous, skipped, other',
|
|
222
|
+
evidence: 'Decision evidence; repeat as newline/;; separated text or JSON array',
|
|
223
|
+
history: 'Reviewed history/proofs; newline/;; separated text or JSON array',
|
|
224
|
+
'trace-ids': 'Comma-separated trace IDs reviewed for the decision',
|
|
225
|
+
ips: 'Comma-separated IPs affected by a case-level decision report',
|
|
226
|
+
'missing-proof': 'Missing proof for skipped or ambiguous rows',
|
|
227
|
+
recommendations: 'Follow-up recommendations; newline/;; separated text or JSON array',
|
|
228
|
+
report: 'Full decision report JSON object',
|
|
229
|
+
yes: 'Confirm write actions without prompting',
|
|
230
|
+
force: 'Alias for --yes',
|
|
231
|
+
conditions: 'False-positive conditions JSON array',
|
|
232
|
+
'match-mode': 'False-positive match mode: all or any',
|
|
233
|
+
'rule-scope': 'False-positive scope: this_rule | specific_rules | all_existing | any_rule',
|
|
234
|
+
'create-exclusion': 'Create a restrictive exclusion when marking false positive',
|
|
235
|
+
'apply-existing': 'Apply false-positive decision to existing matching rows',
|
|
236
|
+
status: 'Case action status: approved, rejected, executed, failed, or proposed',
|
|
237
|
+
'action-key': 'Case-level proposed action key',
|
|
238
|
+
result: 'Case action result JSON',
|
|
239
|
+
mode: 'Prompt mode label for MCP prompt output',
|
|
240
|
+
},
|
|
241
|
+
sub: {
|
|
242
|
+
list: { desc: 'List human decisions AI prepared', run: (a, f) => require('./cli/human').list(a, f) },
|
|
243
|
+
show: { desc: 'Show one row with AI report, proofs, investigation steps, and trace links', usage: 'securenow human show <row|notificationId:ip>', run: (a, f) => require('./cli/human').show(a, f) },
|
|
244
|
+
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) },
|
|
245
|
+
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) },
|
|
246
|
+
report: { desc: 'Record a structured decision report on a row without changing status', usage: 'securenow human report <row|notificationId:ip> --yes --outcome ambiguous --summary "..."', run: (a, f) => require('./cli/human').report(a, f) },
|
|
247
|
+
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) },
|
|
248
|
+
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) },
|
|
249
|
+
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) },
|
|
250
|
+
},
|
|
251
|
+
defaultSub: 'list',
|
|
252
|
+
},
|
|
253
|
+
alerts: {
|
|
254
|
+
desc: 'Manage alerting',
|
|
255
|
+
usage: 'securenow alerts <subcommand> [options]',
|
|
256
|
+
sub: {
|
|
257
|
+
rules: {
|
|
258
|
+
desc: 'Create, list, show, update, duplicate, promote/demote, set-sql, validate, delete, test, or tune alert rules',
|
|
259
|
+
usage: 'securenow alerts rules <list|create|show|update|duplicate|promote|demote|set-sql|validate|delete|test|dry-run-query|tune-query|exclusions> [options]',
|
|
260
|
+
flags: {
|
|
261
|
+
json: 'Output as JSON',
|
|
262
|
+
name: 'With create: rule name',
|
|
263
|
+
description: 'With create: rule description',
|
|
264
|
+
nlp: 'With create: plain-English intent stored on the query',
|
|
265
|
+
text: 'Alias for --nlp',
|
|
266
|
+
category: 'With create: query category (default: custom)',
|
|
267
|
+
severity: 'With create: critical | high | medium | low',
|
|
268
|
+
schedule: 'With create: cron expression (default */15 * * * *)',
|
|
269
|
+
'throttle-minutes': 'With create: notification throttle window in minutes',
|
|
270
|
+
'no-throttle': 'With create: disable throttling',
|
|
271
|
+
'execution-mode': 'With create: scheduled | instant | hybrid',
|
|
272
|
+
channel: 'With create: comma-separated alert channel ids (default: in-app)',
|
|
273
|
+
'query-mapping-id': 'With create: reuse an existing saved query instead of --sql',
|
|
274
|
+
'applications-all': 'With create/update: scope rule to all apps',
|
|
275
|
+
'no-applications-all': 'With update: scope to explicit --apps list',
|
|
276
|
+
apps: 'Comma-separated app keys (with create/update)',
|
|
277
|
+
app: 'Application key for rule tests',
|
|
278
|
+
mode: 'With test: dry_run | live. With list/update: lifecycle test | prod (test = detect-only, no mitigation)',
|
|
279
|
+
status: 'With update: Active | Disabled | Paused. With list: filter by status',
|
|
280
|
+
enable: 'With update: shortcut for --status Active',
|
|
281
|
+
disable: 'With update: shortcut for --status Disabled',
|
|
282
|
+
pause: 'With update: shortcut for --status Paused',
|
|
283
|
+
active: 'With list: filter active (true) or non-active (false)',
|
|
284
|
+
system: 'With list: only system rules',
|
|
285
|
+
user: 'With list: only user (non-system) rules',
|
|
286
|
+
wait: 'Wait for rule test completion',
|
|
287
|
+
sql: 'Detection/candidate/replacement SQL, @file, or - for stdin (create, set-sql, update, validate, dry-run-query, tune-query)',
|
|
288
|
+
query: 'Alias for --sql',
|
|
289
|
+
file: 'Read SQL from a file',
|
|
290
|
+
'no-validate': 'With create/set-sql: skip the post-write dry-run + rollback',
|
|
291
|
+
reason: 'Audit reason for a write',
|
|
292
|
+
'apply-globally': 'Required for system query tuning',
|
|
293
|
+
'reactivate-paused': 'Reactivate paused system copies after tuning',
|
|
294
|
+
'expected-hash': 'Expected current SQL SHA-256 hash',
|
|
295
|
+
notification: 'Review notification id tied to tuning',
|
|
296
|
+
note: 'Review note tied to tuning',
|
|
297
|
+
yes: 'Confirm write prompts',
|
|
298
|
+
force: 'Confirm write prompts',
|
|
299
|
+
},
|
|
300
|
+
run: (a, f) => require('./cli/security').alertRulesRoute(a, f),
|
|
301
|
+
},
|
|
302
|
+
channels: { desc: 'List alert channels', run: (a, f) => require('./cli/security').alertChannelsList(a, f) },
|
|
303
|
+
history: { desc: 'View alert history', flags: { limit: 'Max results' }, run: (a, f) => require('./cli/security').alertHistoryList(a, f) },
|
|
304
|
+
},
|
|
305
|
+
defaultSub: 'rules',
|
|
306
|
+
},
|
|
307
|
+
fp: {
|
|
308
|
+
desc: 'Manage false-positive exclusion rules',
|
|
309
|
+
usage: 'securenow fp <subcommand> [options]',
|
|
310
|
+
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)' },
|
|
311
|
+
sub: {
|
|
312
|
+
list: { desc: 'List all exclusion rules', run: (a, f) => require('./cli/fp').list(a, f) },
|
|
313
|
+
show: { desc: 'Show exclusion rule details', usage: 'securenow fp show <id>', run: (a, f) => require('./cli/fp').show(a, f) },
|
|
314
|
+
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) },
|
|
315
|
+
edit: { desc: 'Edit an exclusion rule', usage: 'securenow fp edit <id> [--active true/false] [--conditions \'[...]\']', run: (a, f) => require('./cli/fp').edit(a, f) },
|
|
316
|
+
delete: { desc: 'Delete an exclusion rule', usage: 'securenow fp delete <id> [--yes]', run: (a, f) => require('./cli/fp').remove(a, f) },
|
|
317
|
+
'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) },
|
|
318
|
+
'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) },
|
|
319
|
+
'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) },
|
|
320
|
+
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) },
|
|
321
|
+
},
|
|
322
|
+
defaultSub: 'list',
|
|
323
|
+
},
|
|
324
|
+
firewall: {
|
|
325
|
+
desc: 'Firewall status, per-app toggle, and IP testing',
|
|
326
|
+
usage: 'securenow firewall <subcommand> [options]',
|
|
327
|
+
flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
|
|
328
|
+
sub: {
|
|
329
|
+
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) },
|
|
330
|
+
apps: { desc: 'List apps with their firewall on/off state', run: (a, f) => require('./cli/firewall').appsList(a, f) },
|
|
331
|
+
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) },
|
|
332
|
+
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) },
|
|
333
|
+
'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) },
|
|
334
|
+
},
|
|
335
|
+
defaultSub: 'status',
|
|
336
|
+
},
|
|
337
|
+
ratelimit: {
|
|
338
|
+
desc: 'Manage soft rate-limit remediation rules',
|
|
339
|
+
usage: 'securenow ratelimit <list|add|parse|from-text|show|test|enable|disable|remove> [options]',
|
|
340
|
+
flags: {
|
|
341
|
+
app: 'Scope to app key (defaults to logged-in app)',
|
|
342
|
+
env: 'Scope to environment (default for create/test: production)',
|
|
343
|
+
environment: 'Alias for --env',
|
|
344
|
+
json: 'Output as JSON',
|
|
345
|
+
limit: 'Allowed requests per window',
|
|
346
|
+
window: 'Window size, e.g. 30s, 1m, 1h',
|
|
347
|
+
duration: 'Expiry, e.g. 24h or 7d',
|
|
348
|
+
route: 'Path pattern such as /api/login',
|
|
349
|
+
path: 'Alias for --route',
|
|
350
|
+
mode: 'Path mode: exact, prefix, or regex',
|
|
351
|
+
method: 'HTTP method, or ALL',
|
|
352
|
+
reason: 'Audit reason',
|
|
353
|
+
text: 'Natural-language rate-limit request',
|
|
354
|
+
},
|
|
355
|
+
sub: {
|
|
356
|
+
list: { desc: 'List rate-limit remediation rules', run: (a, f) => require('./cli/rateLimits').list(a, f) },
|
|
357
|
+
add: { desc: 'Create a rate-limit rule', usage: 'securenow ratelimit add [ip] --route /api/login --limit 5 --window 1m --duration 24h', run: (a, f) => require('./cli/rateLimits').add(a, f) },
|
|
358
|
+
parse: { desc: 'Parse natural language into a rate-limit draft', usage: 'securenow ratelimit parse "rate limit /api/login to 2 attempts per minute"', run: (a, f) => require('./cli/rateLimits').parseText(a, f) },
|
|
359
|
+
'from-text': { desc: 'Create a rate-limit rule from natural language', usage: 'securenow ratelimit from-text "rate limit /api/login to 2 attempts per minute" --yes', run: (a, f) => require('./cli/rateLimits').fromText(a, f) },
|
|
360
|
+
show: { desc: 'Show one rate-limit rule', usage: 'securenow ratelimit show <id>', run: (a, f) => require('./cli/rateLimits').show(a, f) },
|
|
361
|
+
test: { desc: 'Check whether a request would match rate-limit rules', usage: 'securenow ratelimit test <ip> --path /api/login --method POST', run: (a, f) => require('./cli/rateLimits').test(a, f) },
|
|
362
|
+
enable: { desc: 'Enable a rate-limit rule', usage: 'securenow ratelimit enable <id>', run: (a, f) => require('./cli/rateLimits').enable(a, f) },
|
|
363
|
+
disable: { desc: 'Disable a rate-limit rule', usage: 'securenow ratelimit disable <id>', run: (a, f) => require('./cli/rateLimits').disable(a, f) },
|
|
364
|
+
remove: { desc: 'Remove a rate-limit rule', usage: 'securenow ratelimit remove <id> [--reason "..."]', run: (a, f) => require('./cli/rateLimits').remove(a, f) },
|
|
365
|
+
},
|
|
366
|
+
defaultSub: 'list',
|
|
367
|
+
},
|
|
368
|
+
'rate-limit': {
|
|
369
|
+
desc: 'Alias for ratelimit',
|
|
370
|
+
usage: 'securenow rate-limit <list|add|parse|from-text|show|test|enable|disable|remove> [options]',
|
|
371
|
+
flags: {
|
|
372
|
+
app: 'Scope to app key (defaults to logged-in app)',
|
|
373
|
+
env: 'Scope to environment (default for create/test: production)',
|
|
374
|
+
environment: 'Alias for --env',
|
|
375
|
+
json: 'Output as JSON',
|
|
376
|
+
limit: 'Allowed requests per window',
|
|
377
|
+
window: 'Window size, e.g. 30s, 1m, 1h',
|
|
378
|
+
duration: 'Expiry, e.g. 24h or 7d',
|
|
379
|
+
route: 'Path pattern such as /api/login',
|
|
380
|
+
path: 'Alias for --route',
|
|
381
|
+
mode: 'Path mode: exact, prefix, or regex',
|
|
382
|
+
method: 'HTTP method, or ALL',
|
|
383
|
+
reason: 'Audit reason',
|
|
384
|
+
text: 'Natural-language rate-limit request',
|
|
385
|
+
},
|
|
386
|
+
sub: {
|
|
387
|
+
list: { desc: 'List rate-limit remediation rules', run: (a, f) => require('./cli/rateLimits').list(a, f) },
|
|
388
|
+
add: { desc: 'Create a rate-limit rule', usage: 'securenow rate-limit add [ip] --route /api/login --limit 5 --window 1m --duration 24h', run: (a, f) => require('./cli/rateLimits').add(a, f) },
|
|
389
|
+
parse: { desc: 'Parse natural language into a rate-limit draft', usage: 'securenow rate-limit parse "rate limit /api/login to 2 attempts per minute"', run: (a, f) => require('./cli/rateLimits').parseText(a, f) },
|
|
390
|
+
'from-text': { desc: 'Create a rate-limit rule from natural language', usage: 'securenow rate-limit from-text "rate limit /api/login to 2 attempts per minute" --yes', run: (a, f) => require('./cli/rateLimits').fromText(a, f) },
|
|
391
|
+
show: { desc: 'Show one rate-limit rule', usage: 'securenow rate-limit show <id>', run: (a, f) => require('./cli/rateLimits').show(a, f) },
|
|
392
|
+
test: { desc: 'Check whether a request would match rate-limit rules', usage: 'securenow rate-limit test <ip> --path /api/login --method POST', run: (a, f) => require('./cli/rateLimits').test(a, f) },
|
|
393
|
+
enable: { desc: 'Enable a rate-limit rule', usage: 'securenow rate-limit enable <id>', run: (a, f) => require('./cli/rateLimits').enable(a, f) },
|
|
394
|
+
disable: { desc: 'Disable a rate-limit rule', usage: 'securenow rate-limit disable <id>', run: (a, f) => require('./cli/rateLimits').disable(a, f) },
|
|
395
|
+
remove: { desc: 'Remove a rate-limit rule', usage: 'securenow rate-limit remove <id> [--reason "..."]', run: (a, f) => require('./cli/rateLimits').remove(a, f) },
|
|
396
|
+
},
|
|
397
|
+
defaultSub: 'list',
|
|
398
|
+
},
|
|
399
|
+
challenge: {
|
|
400
|
+
desc: 'Manage CAPTCHA / proof-of-work challenge remediation rules',
|
|
401
|
+
usage: 'securenow challenge <list|add|show|test|enable|disable|remove> [options]',
|
|
402
|
+
flags: {
|
|
403
|
+
app: 'Scope to app key (defaults to logged-in app)',
|
|
404
|
+
env: 'Scope to environment (default for create/test: production)',
|
|
405
|
+
environment: 'Alias for --env',
|
|
406
|
+
json: 'Output as JSON',
|
|
407
|
+
difficulty: 'Proof-of-work strength in leading zero bits (4-28, default 14)',
|
|
408
|
+
clearance: 'How long a solve clears, e.g. 30m, 1h (default 30m)',
|
|
409
|
+
route: 'Path pattern such as /login',
|
|
410
|
+
path: 'Alias for --route',
|
|
411
|
+
mode: 'Path mode: exact, prefix, or regex',
|
|
412
|
+
method: 'HTTP method, or ALL',
|
|
413
|
+
duration: 'Rule expiry, e.g. 24h or 7d',
|
|
414
|
+
reason: 'Reason note',
|
|
415
|
+
'escalate-to-block': 'Promote to a hard block after repeated failures',
|
|
416
|
+
'fail-threshold': 'Failures before escalation (default 10)',
|
|
417
|
+
'block-ttl-hours': 'Block duration when escalating (default 24)',
|
|
418
|
+
},
|
|
419
|
+
sub: {
|
|
420
|
+
list: { desc: 'List challenge remediation rules', run: (a, f) => require('./cli/challenges').list(a, f) },
|
|
421
|
+
add: { desc: 'Create a challenge rule', usage: 'securenow challenge add [ip] --route /login --difficulty 16 --clearance 30m', run: (a, f) => require('./cli/challenges').add(a, f) },
|
|
422
|
+
show: { desc: 'Show one challenge rule', usage: 'securenow challenge show <id>', run: (a, f) => require('./cli/challenges').show(a, f) },
|
|
423
|
+
test: { desc: 'Check whether a request would be challenged', usage: 'securenow challenge test <ip> --path /login --method GET', run: (a, f) => require('./cli/challenges').test(a, f) },
|
|
424
|
+
enable: { desc: 'Enable a challenge rule', usage: 'securenow challenge enable <id>', run: (a, f) => require('./cli/challenges').enable(a, f) },
|
|
425
|
+
disable: { desc: 'Disable a challenge rule', usage: 'securenow challenge disable <id>', run: (a, f) => require('./cli/challenges').disable(a, f) },
|
|
426
|
+
remove: { desc: 'Remove a challenge rule', usage: 'securenow challenge remove <id> [--reason "..."]', run: (a, f) => require('./cli/challenges').remove(a, f) },
|
|
427
|
+
},
|
|
428
|
+
defaultSub: 'list',
|
|
429
|
+
},
|
|
430
|
+
automation: {
|
|
431
|
+
desc: 'Manage automation rules for blocklist actions',
|
|
432
|
+
usage: 'securenow automation <list|defaults|show|create|update|dry-run|execute|delete> [rule-id] [options]',
|
|
433
|
+
flags: {
|
|
434
|
+
json: 'Output as JSON',
|
|
435
|
+
body: 'Full JSON body for create/update',
|
|
436
|
+
file: 'Read JSON body from file',
|
|
437
|
+
name: 'Rule name',
|
|
438
|
+
description: 'Rule description',
|
|
439
|
+
conditions: 'Conditions JSON array',
|
|
440
|
+
actions: 'Actions JSON array',
|
|
441
|
+
logic: 'Condition logic: AND or OR',
|
|
442
|
+
status: 'Rule status: active or disabled',
|
|
443
|
+
app: 'Comma-separated app key(s) to scope the rule',
|
|
444
|
+
apps: 'Alias for --app',
|
|
445
|
+
'applications-all': 'Scope rule to all apps',
|
|
446
|
+
env: 'Comma-separated environment(s) to scope the rule',
|
|
447
|
+
environment: 'Alias for --env',
|
|
448
|
+
environments: 'Alias for --env',
|
|
449
|
+
'environments-all': 'Scope rule to all environments',
|
|
450
|
+
limit: 'Dry-run notification scan limit',
|
|
451
|
+
'sample-limit': 'Dry-run sample count',
|
|
452
|
+
yes: 'Confirm execute/delete',
|
|
453
|
+
force: 'Alias for --yes',
|
|
454
|
+
},
|
|
455
|
+
sub: {
|
|
456
|
+
list: { desc: 'List automation rules', run: (a, f) => require('./cli/automation').list(a, f) },
|
|
457
|
+
defaults: { desc: 'Ensure SecureNow default risk-score automation rules', usage: 'securenow automation defaults [--force-enable] [--yes]', run: (a, f) => require('./cli/automation').defaults(a, f) },
|
|
458
|
+
show: { desc: 'Show automation rule details', usage: 'securenow automation show <rule-id>', run: (a, f) => require('./cli/automation').show(a, f) },
|
|
459
|
+
create: { desc: 'Create automation rule', usage: 'securenow automation create --name "..." --conditions \'[...]\' --actions \'[...]\'', run: (a, f) => require('./cli/automation').create(a, f) },
|
|
460
|
+
update: { desc: 'Update automation rule', usage: 'securenow automation update <rule-id> --body \'{...}\'', run: (a, f) => require('./cli/automation').update(a, f) },
|
|
461
|
+
'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) },
|
|
462
|
+
execute: { desc: 'Execute an automation rule now', usage: 'securenow automation execute <rule-id> --yes', run: (a, f) => require('./cli/automation').execute(a, f) },
|
|
463
|
+
delete: { desc: 'Delete an automation rule', usage: 'securenow automation delete <rule-id> --yes', run: (a, f) => require('./cli/automation').remove(a, f) },
|
|
464
|
+
},
|
|
465
|
+
defaultSub: 'list',
|
|
466
|
+
},
|
|
467
|
+
blocklist: {
|
|
468
|
+
desc: 'Manage IP blocklist',
|
|
469
|
+
usage: 'securenow blocklist <subcommand> [options]',
|
|
470
|
+
flags: { app: 'Scope to app key', env: 'Scope to environment', environment: 'Alias for --env', page: 'Page number', limit: 'Max results', status: 'Entry status: active or removed', 'approval-status': 'Approval filter: pending, approved, or rejected', search: 'IP prefix search', view: 'List view: all or operational', reason: 'Audit reason for unblock', route: 'Route pattern such as /admin*', path: 'Alias for --route', pattern: 'Alias for --route', mode: 'Route match mode: prefix, exact, or regex', 'path-mode': 'Alias for --mode', method: 'HTTP method, or ALL', duration: 'Expiry, e.g. 24h or 7d', json: 'Output as JSON' },
|
|
471
|
+
sub: {
|
|
472
|
+
list: { desc: 'List blocked IPs', run: (a, f) => require('./cli/security').blocklistList(a, f) },
|
|
473
|
+
add: { desc: 'Block an IP', usage: 'securenow blocklist add <ip> [--route /admin*] [--mode prefix] [--method GET] [--app <key>] [--env production] [--duration 24h] [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistAdd(a, f) },
|
|
474
|
+
unblock: { desc: 'Unblock an IP and keep block history', usage: 'securenow blocklist unblock <id> [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
|
|
475
|
+
remove: { desc: 'Unblock an IP (compatibility alias)', usage: 'securenow blocklist remove <id> [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
|
|
476
|
+
stats: { desc: 'Blocklist statistics', run: (a, f) => require('./cli/security').blocklistStats(a, f) },
|
|
477
|
+
},
|
|
478
|
+
defaultSub: 'list',
|
|
479
|
+
},
|
|
480
|
+
revoke: {
|
|
481
|
+
desc: 'Revoke sessions/users (kill stolen sessions; the SDK enforces via securenow/sessions)',
|
|
482
|
+
usage: 'securenow revoke <session <id> | user <id> | list | restore <id>> [options]',
|
|
483
|
+
flags: { reason: 'Audit reason', duration: 'Expiry, e.g. 24h or 7d (default 7d)', app: 'Scope to app key', env: 'Scope to environment', environment: 'Alias for --env', type: 'List filter: session or user', status: 'List filter: active or restored', json: 'Output as JSON' },
|
|
484
|
+
sub: {
|
|
485
|
+
session: { desc: 'Revoke a session id', usage: 'securenow revoke session <session-id> [--reason <r>] [--duration 24h] [--app <key>]', run: (a, f) => require('./cli/security').revokeRoute(['session', ...a], f) },
|
|
486
|
+
user: { desc: 'Revoke all sessions of a user id', usage: 'securenow revoke user <user-id> [--reason <r>] [--duration 24h] [--app <key>]', run: (a, f) => require('./cli/security').revokeRoute(['user', ...a], f) },
|
|
487
|
+
list: { desc: 'List active revocations', run: (a, f) => require('./cli/security').revokeRoute(['list', ...a], f) },
|
|
488
|
+
restore: { desc: 'Lift a revocation', usage: 'securenow revoke restore <id> [--reason <r>]', run: (a, f) => require('./cli/security').revokeRoute(['restore', ...a], f) },
|
|
489
|
+
},
|
|
490
|
+
defaultSub: 'list',
|
|
491
|
+
},
|
|
492
|
+
allowlist: {
|
|
493
|
+
desc: 'Manage IP allowlist (only allow listed IPs)',
|
|
494
|
+
usage: 'securenow allowlist <subcommand> [options]',
|
|
495
|
+
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' },
|
|
496
|
+
sub: {
|
|
497
|
+
list: { desc: 'List allowed IPs', run: (a, f) => require('./cli/security').allowlistList(a, f) },
|
|
498
|
+
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) },
|
|
499
|
+
remove: { desc: 'Remove an allowed IP', usage: 'securenow allowlist remove <id>', run: (a, f) => require('./cli/security').allowlistRemove(a, f) },
|
|
500
|
+
stats: { desc: 'Allowlist statistics', run: (a, f) => require('./cli/security').allowlistStats(a, f) },
|
|
501
|
+
},
|
|
502
|
+
defaultSub: 'list',
|
|
503
|
+
},
|
|
504
|
+
trusted: {
|
|
505
|
+
desc: 'Manage trusted IPs',
|
|
506
|
+
usage: 'securenow trusted <subcommand> [options]',
|
|
507
|
+
flags: { app: 'Scope to app key(s), comma-separated', env: 'Scope to environment', environment: 'Alias for --env', json: 'Output as JSON' },
|
|
508
|
+
sub: {
|
|
509
|
+
list: { desc: 'List trusted IPs', run: (a, f) => require('./cli/security').trustedList(a, f) },
|
|
510
|
+
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) },
|
|
511
|
+
remove: { desc: 'Remove trusted IP', usage: 'securenow trusted remove <id>', run: (a, f) => require('./cli/security').trustedRemove(a, f) },
|
|
512
|
+
},
|
|
513
|
+
defaultSub: 'list',
|
|
514
|
+
},
|
|
515
|
+
ip: {
|
|
516
|
+
desc: 'IP intelligence lookup',
|
|
517
|
+
usage: 'securenow ip <ip-address>',
|
|
518
|
+
sub: {
|
|
519
|
+
lookup: { desc: 'Look up IP intelligence', run: (a, f) => require('./cli/security').ipLookup(a, f) },
|
|
520
|
+
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) },
|
|
521
|
+
},
|
|
522
|
+
defaultAction: (a, f) => require('./cli/security').ipLookup(a, f),
|
|
523
|
+
},
|
|
524
|
+
forensics: {
|
|
525
|
+
desc: 'Run forensic queries (natural language → SQL)',
|
|
526
|
+
usage: 'securenow forensics <query> [--app <key>]',
|
|
527
|
+
sub: {
|
|
528
|
+
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) },
|
|
529
|
+
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) },
|
|
530
|
+
library: { desc: 'View saved queries', run: (a, f) => require('./cli/security').forensicsLibrary(a, f) },
|
|
531
|
+
},
|
|
532
|
+
defaultAction: (a, f) => require('./cli/security').forensicsQuery(a, f),
|
|
533
|
+
},
|
|
534
|
+
instances: {
|
|
535
|
+
desc: 'Manage ClickHouse instances',
|
|
536
|
+
usage: 'securenow instances <subcommand> [options]',
|
|
537
|
+
sub: {
|
|
538
|
+
list: { desc: 'List instances', run: (a, f) => require('./cli/security').instancesList(a, f) },
|
|
539
|
+
test: { desc: 'Test instance connection', usage: 'securenow instances test <id>', run: (a, f) => require('./cli/security').instancesTest(a, f) },
|
|
540
|
+
},
|
|
541
|
+
defaultSub: 'list',
|
|
542
|
+
},
|
|
543
|
+
analytics: {
|
|
544
|
+
desc: 'View response analytics',
|
|
545
|
+
usage: 'securenow analytics [--app <key>]',
|
|
546
|
+
run: (a, f) => require('./cli/security').analytics(a, f),
|
|
547
|
+
},
|
|
548
|
+
status: {
|
|
549
|
+
desc: 'Dashboard overview',
|
|
550
|
+
usage: 'securenow status [--app <key>] [--env local|production|all]',
|
|
551
|
+
flags: {
|
|
552
|
+
app: 'App key to highlight',
|
|
553
|
+
env: 'Environment to display in protection summary (default: credentials file, use all for every env)',
|
|
554
|
+
environment: 'Alias for --env',
|
|
555
|
+
},
|
|
556
|
+
run: (a, f) => require('./cli/monitor').status(a, f),
|
|
557
|
+
},
|
|
558
|
+
config: {
|
|
559
|
+
desc: 'Manage CLI configuration',
|
|
560
|
+
usage: 'securenow config <set|get> [key] [value]',
|
|
561
|
+
sub: {
|
|
562
|
+
set: {
|
|
563
|
+
desc: 'Set a config value',
|
|
564
|
+
usage: 'securenow config set <key> <value>',
|
|
565
|
+
run: (a) => {
|
|
566
|
+
const conf = require('./cli/config');
|
|
567
|
+
const [key, ...rest] = a;
|
|
568
|
+
const value = rest.join(' ');
|
|
569
|
+
if (!key || !value) { ui.error('Usage: securenow config set <key> <value>'); process.exit(1); }
|
|
570
|
+
conf.setConfigValue(key, value);
|
|
571
|
+
ui.success(`${key} = ${value}`);
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
get: {
|
|
575
|
+
desc: 'Get a config value',
|
|
576
|
+
usage: 'securenow config get [key]',
|
|
577
|
+
run: (a) => {
|
|
578
|
+
const conf = require('./cli/config');
|
|
579
|
+
const key = a[0];
|
|
580
|
+
if (key) {
|
|
581
|
+
const val = conf.getConfigValue(key);
|
|
582
|
+
console.log(val != null ? val : ui.c.dim('(not set)'));
|
|
583
|
+
} else {
|
|
584
|
+
const all = conf.loadConfig();
|
|
585
|
+
console.log('');
|
|
586
|
+
ui.keyValue(Object.entries(all).map(([k, v]) => [k, v != null ? String(v) : ui.c.dim('(not set)')]));
|
|
587
|
+
console.log('');
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
path: {
|
|
592
|
+
desc: 'Show config file path',
|
|
593
|
+
run: () => {
|
|
594
|
+
const conf = require('./cli/config');
|
|
595
|
+
console.log(`Config: ${conf.CONFIG_FILE}`);
|
|
596
|
+
console.log(`Credentials: ${conf.CREDENTIALS_FILE}`);
|
|
597
|
+
if (conf.hasLocalCredentials()) {
|
|
598
|
+
console.log(`Local creds: ${conf.LOCAL_CREDENTIALS_FILE} ${require('./cli/ui').c.green('(active)')}`);
|
|
599
|
+
}
|
|
600
|
+
console.log(`Auth source: ${conf.getAuthSource()}`);
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
run: {
|
|
606
|
+
desc: 'Run a Node.js app with automatic OTel instrumentation',
|
|
607
|
+
usage: 'securenow run [node-flags] [--firewall-only] <script> [app-args]',
|
|
608
|
+
flags: {
|
|
609
|
+
watch: 'Enable Node.js watch mode',
|
|
610
|
+
inspect: 'Enable Node.js inspector',
|
|
611
|
+
'firewall-only': 'Preload firewall without OTel tracing overhead',
|
|
612
|
+
},
|
|
613
|
+
rawArgv: true,
|
|
614
|
+
run: (rawArgs) => require('./cli/run').run(rawArgs),
|
|
615
|
+
},
|
|
616
|
+
redact: {
|
|
617
|
+
desc: 'Redact sensitive fields from a JSON payload',
|
|
618
|
+
usage: "securenow redact '<json>' [--fields f1,f2] [--json]",
|
|
619
|
+
flags: { fields: 'Comma-separated extra field names to redact (added to defaults)' },
|
|
620
|
+
run: (a, f) => require('./cli/utils').redact(a, f),
|
|
621
|
+
},
|
|
622
|
+
cidr: {
|
|
623
|
+
desc: 'CIDR utilities (match and parse)',
|
|
624
|
+
usage: 'securenow cidr <match|parse> ...',
|
|
625
|
+
sub: {
|
|
626
|
+
match: {
|
|
627
|
+
desc: 'Check if an IP matches a CIDR list',
|
|
628
|
+
usage: 'securenow cidr match <ip> <cidr1,cidr2,...>',
|
|
629
|
+
run: (a, f) => require('./cli/utils').cidrMatch(a, f),
|
|
630
|
+
},
|
|
631
|
+
parse: {
|
|
632
|
+
desc: 'Parse a CIDR and show network/broadcast/size',
|
|
633
|
+
usage: 'securenow cidr parse <cidr>',
|
|
634
|
+
run: (a, f) => require('./cli/utils').cidrParse(a, f),
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
log: {
|
|
639
|
+
desc: 'Emit logs via OTLP (for scripts, cron, debugging)',
|
|
640
|
+
usage: 'securenow log send <message> [--level info|warn|error] [--attrs k=v,k=v]',
|
|
641
|
+
sub: {
|
|
642
|
+
send: {
|
|
643
|
+
desc: 'Send a single log record to the OTLP collector',
|
|
644
|
+
flags: {
|
|
645
|
+
env: 'Deployment environment for this log (defaults to credentials file)',
|
|
646
|
+
environment: 'Alias for --env',
|
|
647
|
+
level: 'Severity (trace|debug|info|warn|error|fatal)',
|
|
648
|
+
attrs: 'Comma-separated key=value attributes',
|
|
649
|
+
},
|
|
650
|
+
run: (a, f) => require('./cli/diagnostics').logSend(a, f),
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
defaultSub: 'send',
|
|
654
|
+
},
|
|
655
|
+
event: {
|
|
656
|
+
desc: 'Emit a custom security event to SecureNow (for scripts, testing, non-JS apps)',
|
|
657
|
+
usage: 'securenow event send <type> [--user <id>] [--session <id>] [--ip <ip>] [--attrs k=v,k=v]',
|
|
658
|
+
sub: {
|
|
659
|
+
send: {
|
|
660
|
+
desc: 'Send a single custom event to the /v1/events ingest',
|
|
661
|
+
flags: {
|
|
662
|
+
env: 'Deployment environment for this event (defaults to credentials file)',
|
|
663
|
+
environment: 'Alias for --env',
|
|
664
|
+
user: 'End-user / account id (enduser.id)',
|
|
665
|
+
session: 'Session id (session.id)',
|
|
666
|
+
ip: 'End-user client IP',
|
|
667
|
+
'user-agent': 'End-user agent string',
|
|
668
|
+
level: 'Severity (trace|debug|info|warn|error|fatal)',
|
|
669
|
+
attrs: 'Comma-separated key=value attributes',
|
|
670
|
+
},
|
|
671
|
+
run: (a, f) => require('./cli/diagnostics').eventSend(a, f),
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
defaultSub: 'send',
|
|
675
|
+
},
|
|
676
|
+
'test-span': {
|
|
677
|
+
desc: 'Emit a test span to verify collector connectivity',
|
|
678
|
+
usage: 'securenow test-span [<span-name>] [--env local|production]',
|
|
679
|
+
flags: {
|
|
680
|
+
env: 'Deployment environment for this span (defaults to credentials file)',
|
|
681
|
+
environment: 'Alias for --env',
|
|
682
|
+
},
|
|
683
|
+
run: (a, f) => require('./cli/diagnostics').testSpan(a, f),
|
|
684
|
+
},
|
|
685
|
+
doctor: {
|
|
686
|
+
desc: 'Diagnose SecureNow configuration and collector connectivity',
|
|
687
|
+
usage: 'securenow doctor [--json]',
|
|
688
|
+
run: (a, f) => require('./cli/diagnostics').doctor(a, f),
|
|
689
|
+
},
|
|
690
|
+
env: {
|
|
691
|
+
desc: 'Show resolved SecureNow configuration (service name, endpoints, credentials)',
|
|
692
|
+
usage: 'securenow env [--json]',
|
|
693
|
+
run: (a, f) => require('./cli/diagnostics').env(a, f),
|
|
694
|
+
},
|
|
695
|
+
mcp: {
|
|
696
|
+
desc: 'Start the SecureNow MCP server over stdio',
|
|
697
|
+
usage: 'securenow mcp',
|
|
698
|
+
run: () => require('./mcp/server'),
|
|
699
|
+
},
|
|
700
|
+
version: {
|
|
701
|
+
desc: 'Show CLI version',
|
|
702
|
+
run: () => {
|
|
703
|
+
try {
|
|
704
|
+
const pkg = require('./package.json');
|
|
705
|
+
console.log(`securenow v${pkg.version}`);
|
|
706
|
+
} catch {
|
|
707
|
+
console.log('securenow (version unknown)');
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// ── Help System ──
|
|
714
|
+
|
|
715
|
+
function showBanner() {
|
|
716
|
+
console.log('');
|
|
717
|
+
console.log(` ${ui.c.bold(ui.c.cyan('SecureNow CLI'))} ${ui.c.dim('— Security observability from the terminal')}`);
|
|
718
|
+
console.log('');
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function showHelp(commandName) {
|
|
722
|
+
if (commandName && COMMANDS[commandName]) {
|
|
723
|
+
const cmd = COMMANDS[commandName];
|
|
724
|
+
showBanner();
|
|
725
|
+
console.log(` ${ui.c.bold(commandName)} — ${cmd.desc}`);
|
|
726
|
+
console.log('');
|
|
727
|
+
if (cmd.usage) {
|
|
728
|
+
console.log(` ${ui.c.bold('USAGE')}`);
|
|
729
|
+
console.log(` ${cmd.usage}`);
|
|
730
|
+
console.log('');
|
|
731
|
+
}
|
|
732
|
+
if (cmd.sub) {
|
|
733
|
+
console.log(` ${ui.c.bold('SUBCOMMANDS')}`);
|
|
734
|
+
const entries = Object.entries(cmd.sub);
|
|
735
|
+
const maxLen = entries.reduce((m, [k]) => Math.max(m, k.length), 0);
|
|
736
|
+
for (const [name, sub] of entries) {
|
|
737
|
+
console.log(` ${ui.c.cyan(name.padEnd(maxLen + 2))} ${sub.desc}`);
|
|
738
|
+
}
|
|
739
|
+
console.log('');
|
|
740
|
+
}
|
|
741
|
+
if (cmd.flags) {
|
|
742
|
+
console.log(` ${ui.c.bold('FLAGS')}`);
|
|
743
|
+
for (const [flag, desc] of Object.entries(cmd.flags)) {
|
|
744
|
+
console.log(` --${ui.c.cyan(flag.padEnd(16))} ${desc}`);
|
|
745
|
+
}
|
|
746
|
+
console.log('');
|
|
747
|
+
}
|
|
748
|
+
console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
|
|
749
|
+
console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
|
|
750
|
+
console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help`);
|
|
751
|
+
console.log('');
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
showBanner();
|
|
756
|
+
|
|
757
|
+
const groups = {
|
|
758
|
+
'Run': ['run'],
|
|
759
|
+
'Authentication': ['login', 'logout', 'whoami', 'credentials'],
|
|
760
|
+
'Applications': ['apps', 'init', 'status'],
|
|
761
|
+
'Observe': ['traces', 'logs', 'analytics'],
|
|
762
|
+
'Detect & Respond': ['human', 'notifications', 'alerts', 'fp'],
|
|
763
|
+
'Investigate': ['ip', 'forensics'],
|
|
764
|
+
'Firewall': ['firewall'],
|
|
765
|
+
'Remediation': ['automation', 'ratelimit', 'challenge', 'blocklist', 'revoke', 'allowlist', 'trusted'],
|
|
766
|
+
'Telemetry': ['log', 'event', 'test-span'],
|
|
767
|
+
'Utilities': ['redact', 'cidr', 'doctor', 'env', 'mcp'],
|
|
768
|
+
'Settings': ['instances', 'config', 'version'],
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
for (const [group, cmds] of Object.entries(groups)) {
|
|
772
|
+
console.log(` ${ui.c.bold(group)}`);
|
|
773
|
+
for (const name of cmds) {
|
|
774
|
+
const cmd = COMMANDS[name];
|
|
775
|
+
if (cmd) {
|
|
776
|
+
console.log(` ${ui.c.cyan(name.padEnd(18))} ${cmd.desc}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
console.log('');
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
|
|
783
|
+
console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
|
|
784
|
+
console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help for a command`);
|
|
785
|
+
console.log('');
|
|
786
|
+
console.log(` ${ui.c.dim('Run')} securenow help <command> ${ui.c.dim('for detailed usage')}`);
|
|
787
|
+
console.log('');
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// ── Main Router ──
|
|
791
|
+
|
|
792
|
+
async function main() {
|
|
793
|
+
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
794
|
+
const cmdName = positional[0] || 'help';
|
|
795
|
+
|
|
796
|
+
if (cmdName === 'help' || flags.help) {
|
|
797
|
+
showHelp(flags.help === true ? positional[0] : flags.help || positional[1] || (cmdName !== 'help' ? cmdName : null));
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const cmd = COMMANDS[cmdName];
|
|
802
|
+
if (!cmd) {
|
|
803
|
+
// Auto-detect: if the first arg looks like a file path, treat it as `securenow run <file>`
|
|
804
|
+
if (/\.(m?[jt]sx?|cjs)$/.test(cmdName) || cmdName.includes('/') || cmdName.includes('\\')) {
|
|
805
|
+
return COMMANDS.run.run(process.argv.slice(2));
|
|
806
|
+
}
|
|
807
|
+
ui.error(`Unknown command: ${cmdName}`);
|
|
808
|
+
ui.info('Run `securenow help` for a list of commands.');
|
|
809
|
+
process.exit(1);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (cmd.rawArgv) {
|
|
813
|
+
// Pass raw argv (everything after the command name) so the command can
|
|
814
|
+
// forward flags like --watch, --inspect verbatim to child processes.
|
|
815
|
+
const cmdIdx = process.argv.indexOf(cmdName);
|
|
816
|
+
const rawArgs = cmdIdx !== -1 ? process.argv.slice(cmdIdx + 1) : positional.slice(1);
|
|
817
|
+
await cmd.run(rawArgs);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (cmd.run && !cmd.sub) {
|
|
822
|
+
await cmd.run(positional.slice(1), flags);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (cmd.sub) {
|
|
827
|
+
let subName = positional[1];
|
|
828
|
+
let subArgs = positional.slice(2);
|
|
829
|
+
|
|
830
|
+
if (subName && cmd.sub[subName]) {
|
|
831
|
+
if (flags.help) {
|
|
832
|
+
showHelp(cmdName);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
await cmd.sub[subName].run(subArgs, flags);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (cmd.defaultAction) {
|
|
840
|
+
const allArgs = subName ? [subName, ...subArgs] : [];
|
|
841
|
+
await cmd.defaultAction(allArgs, flags);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (cmd.defaultSub) {
|
|
846
|
+
const allArgs = subName ? [subName, ...subArgs] : [];
|
|
847
|
+
await cmd.sub[cmd.defaultSub].run(allArgs, flags);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
showHelp(cmdName);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (cmd.run) {
|
|
856
|
+
await cmd.run(positional.slice(1), flags);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
main().catch((err) => {
|
|
861
|
+
if (err.name !== 'CLIError') {
|
|
862
|
+
ui.error(err.message || 'An unexpected error occurred');
|
|
863
|
+
}
|
|
864
|
+
process.exit(1);
|
|
865
|
+
});
|