securenow 7.5.1 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CONSUMING-APPS-GUIDE.md +2 -0
  2. package/NPM_README.md +201 -237
  3. package/README.md +73 -26
  4. package/SKILL-API.md +209 -205
  5. package/SKILL-CLI.md +71 -64
  6. package/app-config.js +479 -83
  7. package/cli/apiKey.js +1 -1
  8. package/cli/apps.js +1 -1
  9. package/cli/config.js +31 -12
  10. package/cli/credentials.js +88 -0
  11. package/cli/diagnostics.js +68 -104
  12. package/cli/firewall.js +29 -14
  13. package/cli/init.js +208 -206
  14. package/cli/monitor.js +107 -43
  15. package/cli/security.js +24 -12
  16. package/cli/utils.js +2 -1
  17. package/cli.js +71 -39
  18. package/console-instrumentation.js +1 -1
  19. package/docs/ENVIRONMENT-VARIABLES.md +137 -863
  20. package/docs/ENVIRONMENTS.md +60 -0
  21. package/docs/EXPRESS-SETUP-GUIDE.md +3 -0
  22. package/docs/FIREWALL-GUIDE.md +3 -0
  23. package/docs/INDEX.md +6 -8
  24. package/docs/LOGGING-GUIDE.md +3 -0
  25. package/docs/MCP-GUIDE.md +8 -0
  26. package/docs/NEXTJS-GUIDE.md +3 -0
  27. package/docs/NEXTJS-QUICKSTART.md +24 -16
  28. package/docs/NUXT-GUIDE.md +3 -0
  29. package/docs/QUICKSTART-BODY-CAPTURE.md +3 -0
  30. package/docs/REQUEST-BODY-CAPTURE.md +3 -0
  31. package/firewall-cloud.js +10 -10
  32. package/firewall-only.js +25 -23
  33. package/firewall.js +47 -29
  34. package/free-trial-banner.js +1 -1
  35. package/mcp/catalog.js +104 -17
  36. package/nextjs-auto-capture.d.ts +7 -4
  37. package/nextjs-auto-capture.js +7 -7
  38. package/nextjs-middleware.js +4 -3
  39. package/nextjs-wrapper.js +6 -6
  40. package/nextjs.d.ts +36 -25
  41. package/nextjs.js +47 -55
  42. package/nuxt-server-plugin.mjs +35 -51
  43. package/nuxt.d.ts +29 -23
  44. package/package.json +1 -1
  45. package/postinstall.js +27 -61
  46. package/register.d.ts +19 -33
  47. package/register.js +8 -8
  48. package/resolve-ip.js +4 -5
  49. package/tracing.d.ts +21 -19
  50. package/tracing.js +34 -42
package/cli/security.js CHANGED
@@ -4,9 +4,13 @@ const { api, requireAuth } = require('./client');
4
4
  const config = require('./config');
5
5
  const ui = require('./ui');
6
6
 
7
- function resolveApp(flags) {
8
- return flags.app || config.getDefaultApp();
9
- }
7
+ function resolveApp(flags) {
8
+ return flags.app || config.getDefaultApp();
9
+ }
10
+
11
+ function resolveEnvironment(flags, fallback = null) {
12
+ return flags.env || flags.environment || fallback;
13
+ }
10
14
 
11
15
  // ── Alert Rules ──
12
16
 
@@ -523,7 +527,7 @@ async function forensicsQuery(args, flags) {
523
527
 
524
528
  const s = ui.spinner('Submitting forensic query');
525
529
  try {
526
- const body = { query };
530
+ const body = { query, environment: resolveEnvironment(flags, 'production') };
527
531
  const resolved = await resolveAppId(flags);
528
532
  if (resolved) {
529
533
  body.applicationId = resolved.id;
@@ -650,7 +654,10 @@ async function forensicsChat(args, flags) {
650
654
  console.log(ui.c.dim(` App: ${resolved.key} (${resolved.id})`));
651
655
  console.log(ui.c.dim(' Type your question, or "exit" to quit.\n'));
652
656
 
653
- let conversationId = null;
657
+ const environment = resolveEnvironment(flags, 'production');
658
+ console.log(ui.c.dim(` Env: ${environment}`));
659
+
660
+ let conversationId = null;
654
661
 
655
662
  const readline = require('readline');
656
663
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
@@ -670,7 +677,7 @@ async function forensicsChat(args, flags) {
670
677
 
671
678
  const s = ui.spinner('Thinking');
672
679
  try {
673
- const body = { message, applicationKey: resolved.key };
680
+ const body = { message, applicationKey: resolved.key, environment };
674
681
  if (conversationId) body.conversationId = conversationId;
675
682
 
676
683
  const chatRes = await api.post('/forensics/chat', body);
@@ -680,7 +687,7 @@ async function forensicsChat(args, flags) {
680
687
  let result;
681
688
  for (let i = 0; i < 150; i++) {
682
689
  await new Promise(r => setTimeout(r, 2000));
683
- result = await api.get(`/forensics/chat/status/${conversationId}`);
690
+ result = await api.get(`/forensics/chat/status/${conversationId}`, { query: { environment } });
684
691
  if (result.status === 'complete' || result.status === 'failed' || result.status === 'awaiting_confirmation') break;
685
692
  const progress = result._progress;
686
693
  if (progress?.action) s.update(progress.action);
@@ -698,11 +705,11 @@ async function forensicsChat(args, flags) {
698
705
  const proceed = await ui.confirm('Proceed with this query?');
699
706
  if (proceed) {
700
707
  const cs = ui.spinner('Executing query');
701
- await api.post(`/forensics/chat/confirm/${conversationId}`);
708
+ await api.post(`/forensics/chat/confirm/${conversationId}`, { environment });
702
709
  let confirmResult;
703
710
  for (let i = 0; i < 90; i++) {
704
711
  await new Promise(r => setTimeout(r, 2000));
705
- confirmResult = await api.get(`/forensics/chat/status/${conversationId}`);
712
+ confirmResult = await api.get(`/forensics/chat/status/${conversationId}`, { query: { environment } });
706
713
  if (confirmResult.status !== 'processing') break;
707
714
  }
708
715
  cs.stop('Done');
@@ -836,9 +843,14 @@ async function ipTraces(args, flags) {
836
843
  process.exit(1);
837
844
  }
838
845
 
839
- const s = ui.spinner(`Fetching traces for ${ip}`);
840
- try {
841
- const data = await api.get(`/ip/${ip}/traces`);
846
+ const s = ui.spinner(`Fetching traces for ${ip}`);
847
+ try {
848
+ const query = {};
849
+ const appKey = resolveApp(flags);
850
+ if (appKey) query.appKeys = appKey;
851
+ const environment = resolveEnvironment(flags, null);
852
+ if (environment) query.environment = environment;
853
+ const data = await api.get(`/ip/${ip}/traces`, { query });
842
854
  const traces = data.traces || [];
843
855
  s.stop(`Found ${traces.length} trace${traces.length !== 1 ? 's' : ''}`);
844
856
 
package/cli/utils.js CHANGED
@@ -4,6 +4,7 @@ const fs = require('fs');
4
4
  const ui = require('./ui');
5
5
  const cidrLib = require('../cidr');
6
6
  const { redactSensitiveData, DEFAULT_SENSITIVE_FIELDS } = require('../nextjs-middleware');
7
+ const appConfig = require('../app-config');
7
8
 
8
9
  // ── redact ──
9
10
 
@@ -38,7 +39,7 @@ function redact(args, flags) {
38
39
  const extra = flags.fields
39
40
  ? String(flags.fields).split(',').map((s) => s.trim()).filter(Boolean)
40
41
  : [];
41
- const envExtra = (process.env.SECURENOW_SENSITIVE_FIELDS || '')
42
+ const envExtra = (appConfig.env('SECURENOW_SENSITIVE_FIELDS') || '')
42
43
  .split(',').map((s) => s.trim()).filter(Boolean);
43
44
  const fields = [...DEFAULT_SENSITIVE_FIELDS, ...envExtra, ...extra];
44
45
 
package/cli.js CHANGED
@@ -39,11 +39,13 @@ function parseArgs(argv) {
39
39
  // ── Command Registry ──
40
40
 
41
41
  const COMMANDS = {
42
- init: {
43
- desc: 'Set up SecureNow in the current project (instrumentation, config, env)',
44
- usage: 'securenow init [--key <API_KEY>] [--ts|--js] [--src|--root] [--force]',
45
- flags: {
46
- key: 'API key to write to .env',
42
+ init: {
43
+ desc: 'Set up SecureNow in the current project (instrumentation + .securenow credentials)',
44
+ usage: 'securenow init [--env local] [--key <API_KEY>] [--ts|--js] [--src|--root] [--force]',
45
+ flags: {
46
+ env: 'Deployment environment to write into .securenow/credentials.json (default: local)',
47
+ environment: 'Alias for --env',
48
+ key: 'Firewall API key to write to .securenow/credentials.json',
47
49
  'api-key': 'Alias for --key',
48
50
  typescript: 'Force TypeScript',
49
51
  javascript: 'Force JavaScript',
@@ -73,7 +75,7 @@ const COMMANDS = {
73
75
  usage: 'securenow whoami',
74
76
  run: () => require('./cli/auth').whoami(),
75
77
  },
76
- 'api-key': {
78
+ 'api-key': {
77
79
  desc: 'Manage the firewall API key stored in .securenow/credentials.json',
78
80
  usage: 'securenow api-key <subcommand> [options]',
79
81
  sub: {
@@ -95,8 +97,27 @@ const COMMANDS = {
95
97
  run: () => require('./cli/apiKey').show(),
96
98
  },
97
99
  },
98
- defaultSub: 'show',
99
- },
100
+ defaultSub: 'show',
101
+ },
102
+ credentials: {
103
+ desc: 'Create production/runtime SecureNow credentials files',
104
+ usage: 'securenow credentials <subcommand> [options]',
105
+ sub: {
106
+ runtime: {
107
+ desc: 'Write tokenless runtime credentials for production file-based deploys',
108
+ usage: 'securenow credentials runtime [--env production] [--out .securenow/credentials.production.json] [--stdout]',
109
+ flags: {
110
+ env: 'Deployment environment for this runtime file (default: production)',
111
+ environment: 'Alias for --env',
112
+ out: 'Output path (default: .securenow/credentials.<env>.json)',
113
+ output: 'Alias for --out',
114
+ stdout: 'Print JSON to stdout instead of writing a file',
115
+ },
116
+ run: (a, f) => require('./cli/credentials').runtime(a, f),
117
+ },
118
+ },
119
+ defaultSub: 'runtime',
120
+ },
100
121
  apps: {
101
122
  desc: 'Manage applications',
102
123
  usage: 'securenow apps <subcommand> [options]',
@@ -115,9 +136,9 @@ const COMMANDS = {
115
136
  desc: 'View and analyze traces',
116
137
  usage: 'securenow traces <subcommand> [options]',
117
138
  sub: {
118
- list: { desc: 'List recent traces', flags: { app: 'App key', limit: 'Max results', start: 'Start time', end: 'End time' }, run: (a, f) => require('./cli/monitor').tracesList(a, f) },
119
- show: { desc: 'Show trace details', usage: 'securenow traces show <traceId>', run: (a, f) => require('./cli/monitor').tracesShow(a, f) },
120
- analyze: { desc: 'AI-analyze a trace', usage: 'securenow traces analyze <traceId>', run: (a, f) => require('./cli/monitor').tracesAnalyze(a, f) },
139
+ list: { desc: 'List recent traces', flags: { app: 'App key', env: 'Environment (production, staging, preview, local, or all)', environment: 'Alias for --env', limit: 'Max results', start: 'Start time', end: 'End time' }, run: (a, f) => require('./cli/monitor').tracesList(a, f) },
140
+ show: { desc: 'Show trace details', usage: 'securenow traces show <traceId>', flags: { app: 'App key', env: 'Environment', environment: 'Alias for --env' }, run: (a, f) => require('./cli/monitor').tracesShow(a, f) },
141
+ analyze: { desc: 'AI-analyze a trace', usage: 'securenow traces analyze <traceId>', flags: { app: 'App key', env: 'Environment', environment: 'Alias for --env' }, run: (a, f) => require('./cli/monitor').tracesAnalyze(a, f) },
121
142
  },
122
143
  defaultSub: 'list',
123
144
  },
@@ -125,8 +146,8 @@ const COMMANDS = {
125
146
  desc: 'View application logs',
126
147
  usage: 'securenow logs [options]',
127
148
  sub: {
128
- list: { desc: 'List recent logs', flags: { app: 'App key', limit: 'Max results (default 200)', since: 'Time window (e.g. 30m, 6h, 2d)', minutes: 'Time window in minutes (alias for --since Nm)', level: 'Filter by level (error, warn, info, debug)', start: 'Start time (ISO 8601)', end: 'End time (ISO 8601)' }, run: (a, f) => require('./cli/monitor').logsList(a, f) },
129
- trace: { desc: 'Show logs for a trace', usage: 'securenow logs trace <traceId>', run: (a, f) => require('./cli/monitor').logsTrace(a, f) },
149
+ list: { desc: 'List recent logs', flags: { app: 'App key', env: 'Environment (production, staging, preview, local, or all)', environment: 'Alias for --env', limit: 'Max results (default 200)', since: 'Time window (e.g. 30m, 6h, 2d)', minutes: 'Time window in minutes (alias for --since Nm)', level: 'Filter by level (error, warn, info, debug)', start: 'Start time (ISO 8601)', end: 'End time (ISO 8601)' }, run: (a, f) => require('./cli/monitor').logsList(a, f) },
150
+ trace: { desc: 'Show logs for a trace', usage: 'securenow logs trace <traceId>', flags: { app: 'App key', env: 'Environment', environment: 'Alias for --env' }, run: (a, f) => require('./cli/monitor').logsTrace(a, f) },
130
151
  },
131
152
  defaultSub: 'list',
132
153
  },
@@ -182,11 +203,11 @@ const COMMANDS = {
182
203
  usage: 'securenow firewall <subcommand> [options]',
183
204
  flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
184
205
  sub: {
185
- status: { desc: 'Show firewall status, layers, and blocklist info', run: (a, f) => require('./cli/firewall').status(a, f) },
206
+ status: { desc: 'Show firewall status, layers, and blocklist info', flags: { env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').status(a, f) },
186
207
  apps: { desc: 'List apps with their firewall on/off state', run: (a, f) => require('./cli/firewall').appsList(a, f) },
187
- enable: { desc: 'Turn the firewall ON for an app', usage: 'securenow firewall enable [--app <key>]', flags: { app: 'App key (defaults to logged-in app)' }, run: (a, f) => require('./cli/firewall').enable(a, f) },
188
- disable: { desc: 'Turn the firewall OFF for an app', usage: 'securenow firewall disable [--app <key>]', flags: { app: 'App key (defaults to logged-in app)' }, run: (a, f) => require('./cli/firewall').disable(a, f) },
189
- 'test-ip': { desc: 'Check if an IP would be blocked', usage: 'securenow firewall test-ip <ip>', run: (a, f) => require('./cli/firewall').testIp(a, f) },
208
+ enable: { desc: 'Turn the firewall ON for an app environment', usage: 'securenow firewall enable [--app <key>] [--env production]', flags: { app: 'App key (defaults to logged-in app)', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').enable(a, f) },
209
+ disable: { desc: 'Turn the firewall OFF for an app environment', usage: 'securenow firewall disable [--app <key>] [--env local]', flags: { app: 'App key (defaults to logged-in app)', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').disable(a, f) },
210
+ 'test-ip': { desc: 'Check if an IP would be blocked', usage: 'securenow firewall test-ip <ip> [--env production]', flags: { env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').testIp(a, f) },
190
211
  },
191
212
  defaultSub: 'status',
192
213
  },
@@ -227,7 +248,7 @@ const COMMANDS = {
227
248
  usage: 'securenow ip <ip-address>',
228
249
  sub: {
229
250
  lookup: { desc: 'Look up IP intelligence', run: (a, f) => require('./cli/security').ipLookup(a, f) },
230
- traces: { desc: 'Show traces for an IP', usage: 'securenow ip traces <ip>', run: (a, f) => require('./cli/security').ipTraces(a, f) },
251
+ traces: { desc: 'Show traces for an IP', usage: 'securenow ip traces <ip> [--env production]', flags: { app: 'App key', env: 'Environment', environment: 'Alias for --env' }, run: (a, f) => require('./cli/security').ipTraces(a, f) },
231
252
  },
232
253
  defaultAction: (a, f) => require('./cli/security').ipLookup(a, f),
233
254
  },
@@ -235,8 +256,8 @@ const COMMANDS = {
235
256
  desc: 'Run forensic queries (natural language → SQL)',
236
257
  usage: 'securenow forensics <query> [--app <key>]',
237
258
  sub: {
238
- query: { desc: 'Run a forensic query', flags: { app: 'App key to scope query' }, run: (a, f) => require('./cli/security').forensicsQuery(a, f) },
239
- chat: { desc: 'Interactive forensics chat (scoped to an app)', usage: 'securenow forensics chat --app <key>', flags: { app: 'App key to chat with' }, run: (a, f) => require('./cli/security').forensicsChat(a, f) },
259
+ query: { desc: 'Run a forensic query', flags: { app: 'App key to scope query', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/security').forensicsQuery(a, f) },
260
+ chat: { desc: 'Interactive forensics chat (scoped to an app)', usage: 'securenow forensics chat --app <key> [--env production]', flags: { app: 'App key to chat with', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/security').forensicsChat(a, f) },
240
261
  library: { desc: 'View saved queries', run: (a, f) => require('./cli/security').forensicsLibrary(a, f) },
241
262
  },
242
263
  defaultAction: (a, f) => require('./cli/security').forensicsQuery(a, f),
@@ -255,11 +276,16 @@ const COMMANDS = {
255
276
  usage: 'securenow analytics [--app <key>]',
256
277
  run: (a, f) => require('./cli/security').analytics(a, f),
257
278
  },
258
- status: {
259
- desc: 'Dashboard overview',
260
- usage: 'securenow status [--app <key>]',
261
- run: (a, f) => require('./cli/monitor').status(a, f),
262
- },
279
+ status: {
280
+ desc: 'Dashboard overview',
281
+ usage: 'securenow status [--app <key>] [--env local|production|all]',
282
+ flags: {
283
+ app: 'App key to highlight',
284
+ env: 'Environment to display in protection summary (default: credentials file, use all for every env)',
285
+ environment: 'Alias for --env',
286
+ },
287
+ run: (a, f) => require('./cli/monitor').status(a, f),
288
+ },
263
289
  config: {
264
290
  desc: 'Manage CLI configuration',
265
291
  usage: 'securenow config <set|get> [key] [value]',
@@ -343,30 +369,36 @@ const COMMANDS = {
343
369
  log: {
344
370
  desc: 'Emit logs via OTLP (for scripts, cron, debugging)',
345
371
  usage: 'securenow log send <message> [--level info|warn|error] [--attrs k=v,k=v]',
346
- sub: {
347
- send: {
348
- desc: 'Send a single log record to the OTLP collector',
349
- flags: {
350
- level: 'Severity (trace|debug|info|warn|error|fatal)',
351
- attrs: 'Comma-separated key=value attributes',
352
- },
372
+ sub: {
373
+ send: {
374
+ desc: 'Send a single log record to the OTLP collector',
375
+ flags: {
376
+ env: 'Deployment environment for this log (defaults to credentials file)',
377
+ environment: 'Alias for --env',
378
+ level: 'Severity (trace|debug|info|warn|error|fatal)',
379
+ attrs: 'Comma-separated key=value attributes',
380
+ },
353
381
  run: (a, f) => require('./cli/diagnostics').logSend(a, f),
354
382
  },
355
383
  },
356
384
  defaultSub: 'send',
357
385
  },
358
- 'test-span': {
359
- desc: 'Emit a test span to verify collector connectivity',
360
- usage: 'securenow test-span [<span-name>]',
361
- run: (a, f) => require('./cli/diagnostics').testSpan(a, f),
362
- },
386
+ 'test-span': {
387
+ desc: 'Emit a test span to verify collector connectivity',
388
+ usage: 'securenow test-span [<span-name>] [--env local|production]',
389
+ flags: {
390
+ env: 'Deployment environment for this span (defaults to credentials file)',
391
+ environment: 'Alias for --env',
392
+ },
393
+ run: (a, f) => require('./cli/diagnostics').testSpan(a, f),
394
+ },
363
395
  doctor: {
364
396
  desc: 'Diagnose SecureNow configuration and collector connectivity',
365
397
  usage: 'securenow doctor [--json]',
366
398
  run: (a, f) => require('./cli/diagnostics').doctor(a, f),
367
399
  },
368
400
  env: {
369
- desc: 'Show resolved SecureNow configuration (service name, endpoints, env vars)',
401
+ desc: 'Show resolved SecureNow configuration (service name, endpoints, credentials)',
370
402
  usage: 'securenow env [--json]',
371
403
  run: (a, f) => require('./cli/diagnostics').env(a, f),
372
404
  },
@@ -434,7 +466,7 @@ function showHelp(commandName) {
434
466
 
435
467
  const groups = {
436
468
  'Run': ['run'],
437
- 'Authentication': ['login', 'logout', 'whoami'],
469
+ 'Authentication': ['login', 'logout', 'whoami', 'credentials'],
438
470
  'Applications': ['apps', 'init', 'status'],
439
471
  'Observe': ['traces', 'logs', 'analytics'],
440
472
  'Detect & Respond': ['notifications', 'alerts', 'fp'],
@@ -7,7 +7,7 @@
7
7
  * to automatically send logs to OpenTelemetry / any OTLP-compatible backend.
8
8
  *
9
9
  * Usage:
10
- * 1. Enable logging: SECURENOW_LOGGING_ENABLED=1
10
+ * 1. Run `npx securenow login` / `npx securenow init` so credentials defaults exist
11
11
  * 2. Import this file AFTER securenow is initialized
12
12
  * 3. Use console.log/info/warn/error as normal
13
13
  *