securenow 7.7.6 → 7.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NPM_README.md CHANGED
@@ -186,7 +186,7 @@ codex mcp add securenow -- npx securenow mcp
186
186
  npx -p securenow securenow-mcp
187
187
  ```
188
188
 
189
- The MCP surface exposes tools for applications, traces, logs, firewall, IP intelligence, forensics, notifications, blocklist, allowlist, trusted IPs, and docs-backed prompts/resources. Write actions require `confirm:true` and a reason.
189
+ The MCP surface exposes tools for applications, traces, logs, firewall, IP intelligence, forensics, notifications, blocklist, allowlist, trusted IPs, and docs-backed prompts/resources. Write actions require `confirm:true` and a reason. Use `securenow_blocklist_unblock` to stop firewall enforcement while keeping the block report/history; `securenow_blocklist_remove` is a compatibility alias.
190
190
 
191
191
  For hosted clients, SecureNow can expose the same surface at `https://api.securenow.ai/mcp`. The hosted endpoint uses the same API authentication and scope checks as the rest of SecureNow.
192
192
 
@@ -272,7 +272,9 @@ npx securenow ip traces 203.0.113.42
272
272
  # Manage the blocklist
273
273
  npx securenow blocklist
274
274
  npx securenow blocklist add 203.0.113.42 --reason "Brute force scanner"
275
- npx securenow blocklist remove <id>
275
+ npx securenow blocklist unblock <id> --reason "Reviewed as safe"
276
+ npx securenow blocklist remove <id> # compatibility alias
277
+ npx securenow blocklist list --status removed
276
278
  npx securenow blocklist stats
277
279
 
278
280
  # Manage trusted IPs
@@ -520,7 +522,8 @@ npx securenow logs --json --level error | jq '.logs'
520
522
  | | `automation dry-run <id>` | Preview automation matches without writing blocks |
521
523
  | | `automation execute <id> --yes` | Run an automation rule now |
522
524
  | | `blocklist add <ip>` | Block IP |
523
- | | `blocklist remove <id>` | Unblock IP |
525
+ | | `blocklist unblock <id>` | Unblock IP and retain report/history |
526
+ | | `blocklist remove <id>` | Compatibility alias for unblock |
524
527
  | | `blocklist stats` | Block stats |
525
528
  | | `allowlist` | Allowed IPs (restrict-mode) |
526
529
  | | `allowlist add <ip>` | Allow IP (`--label`, `--reason`) |
package/README.md CHANGED
@@ -191,6 +191,7 @@ npx securenow doctor # diagnose config + connectivity
191
191
  # Security
192
192
  npx securenow firewall status --env production
193
193
  npx securenow blocklist add 1.2.3.4 --reason "scanner"
194
+ npx securenow blocklist unblock <id> --reason "reviewed safe"
194
195
  npx securenow fp ai-fill --description "Stripe webhook POST /api/stripe/webhook"
195
196
 
196
197
  # Telemetry from shell (no SDK boot)
@@ -357,6 +358,7 @@ After install, the `securenow` CLI is available via `npx securenow` or globally
357
358
  |---|---|
358
359
  | `securenow blocklist` | List blocked IPs |
359
360
  | `securenow blocklist add <ip> [--reason ...]` | Block an IP |
361
+ | `securenow blocklist unblock <id> [--reason ...]` | Stop enforcement and keep block history |
360
362
  | `securenow allowlist add <ip>` | Allow an IP (restrict-mode) |
361
363
  | `securenow trusted add <ip>` | Mark an IP as trusted |
362
364
 
package/SKILL-API.md CHANGED
@@ -66,9 +66,25 @@ npx securenow api-key set snk_live_abc123...
66
66
 
67
67
  Both paths write the key to `.securenow/credentials.json` (auto-gitignored) and the firewall activates on next start. For production, run `npx securenow credentials runtime --env production` and mount/copy the tokenless file as `.securenow/credentials.json`.
68
68
 
69
- The firewall syncs your blocklist and enforces it on every request — zero code changes.
70
-
71
- ---
69
+ The firewall syncs your blocklist and enforces it on every request — zero code changes.
70
+
71
+ Blocklist unblocks are audit-preserving: dashboard/API/CLI/MCP unblock actions
72
+ mark the active block as `removed`, invalidate firewall sync, clear expiry to
73
+ avoid TTL deletion, and retain block reports/history for future review or
74
+ reblock context.
75
+
76
+ For near-realtime propagation after a block/unblock, set
77
+ `SECURENOW_FIREWALL_VERSION_INTERVAL=1` or `2` in the protected app. The SDK
78
+ polls `/firewall/sync` with ETag/304, so unchanged checks are lightweight; keep
79
+ `SECURENOW_FIREWALL_SYNC_INTERVAL` high as a safety-net full refresh.
80
+
81
+ Default automation is active for new and existing customers. The API
82
+ idempotently provisions risk-score rules for all apps/environments:
83
+ `riskScore >= 95` blocks for 7 days, `90-94` for 72h, and `85-89` for 24h.
84
+ Run `securenow automation defaults --yes` or the API backfill script when an
85
+ operator needs to ensure those defaults immediately.
86
+
87
+ ---
72
88
 
73
89
  ## Import Map
74
90
 
package/SKILL-CLI.md CHANGED
@@ -300,13 +300,21 @@ securenow firewall test-ip <ip> --app <key> --env local # check if IP would be
300
300
  securenow blocklist # list blocked IPs
301
301
  securenow blocklist list
302
302
  securenow blocklist add <ip> --app <key> --env production --reason "Brute force"
303
- securenow blocklist remove <id>
303
+ securenow blocklist unblock <id> --reason "False alarm after review"
304
+ securenow blocklist remove <id> # compatibility alias for unblock
305
+ securenow blocklist list --status removed # audit retained unblocks
304
306
  securenow blocklist stats # block counts, top reasons
305
307
  ```
306
308
 
309
+ Unblock stops firewall enforcement but preserves the block report, history, and
310
+ unblock audit fields. Reblocking the same IP later creates a fresh active block
311
+ without erasing the previous investigation trail.
312
+
307
313
  MCP exposes legacy pending-block cleanup separately from current Requires Human
308
314
  work:
309
315
 
316
+ - `securenow_blocklist_unblock`
317
+ - `securenow_blocklist_remove` (compatibility alias for unblock)
310
318
  - `securenow_blocklist_pending_list`
311
319
  - `securenow_blocklist_pending_approve`
312
320
  - `securenow_blocklist_pending_reject`
@@ -318,15 +326,17 @@ work:
318
326
 
319
327
  ```bash
320
328
  securenow automation # list blocklist automation rules
329
+ securenow automation defaults --yes # ensure built-in default risk-score rules
321
330
  securenow automation show <id>
322
331
  securenow automation dry-run <id> --limit 500
323
332
  securenow automation execute <id> --yes
324
333
  ```
325
334
 
326
- For default automation, prefer production-scoped `riskScore` policies:
327
- `riskScore >= 90` for autonomous AI malicious blocking, `riskScore >= 80`
328
- plus `alertTag in xss` for short-lived XSS blocks, and keep raw SecureNow IPDB
329
- confidence as supporting reputation evidence.
335
+ Built-in default automation is enabled by default for all apps/environments and
336
+ uses the canonical `riskScore`: 95-100 blocks for 7 days, 90-94 blocks for 72h,
337
+ 85-89 blocks for 24h, and scores below 85 stay in investigation/review unless
338
+ the customer adds a stricter custom rule. Raw SecureNow IPDB confidence remains
339
+ supporting evidence, not the primary automation score.
330
340
 
331
341
  ### Allowlist — Restrict to Known IPs
332
342
 
package/cli/automation.js CHANGED
@@ -249,6 +249,34 @@ async function execute(args, flags) {
249
249
  }
250
250
  }
251
251
 
252
+ async function defaults(args, flags) {
253
+ requireAuth();
254
+ if ((flags['force-enable'] || flags.force) && !flags.yes) {
255
+ const ok = await ui.confirm('Re-enable existing SecureNow default automation rules that were disabled?');
256
+ if (!ok) { ui.info('Cancelled'); return; }
257
+ }
258
+
259
+ const s = ui.spinner('Ensuring default automation rules');
260
+ try {
261
+ const data = await api.post('/automation-rules/defaults/ensure', {
262
+ forceEnableExisting: !!(flags['force-enable'] || flags.force),
263
+ });
264
+ s.stop('Default automation rules ensured');
265
+ if (flags.json) { ui.json(data); return; }
266
+
267
+ const result = data.result || {};
268
+ ui.keyValue([
269
+ ['Version', String(result.version ?? '-')],
270
+ ['Created', String(result.created ?? 0)],
271
+ ['Updated', String(result.updated ?? 0)],
272
+ ['Already present', String(result.alreadyProvisioned ?? 0)],
273
+ ]);
274
+ } catch (err) {
275
+ s.fail('Failed to ensure default automation rules');
276
+ throw err;
277
+ }
278
+ }
279
+
252
280
  async function remove(args, flags) {
253
281
  requireAuth();
254
282
  const id = args[0];
@@ -272,4 +300,4 @@ async function remove(args, flags) {
272
300
  }
273
301
  }
274
302
 
275
- module.exports = { list, show, create, update, dryRun, execute, remove };
303
+ module.exports = { list, show, create, update, dryRun, execute, defaults, remove };
package/cli/security.js CHANGED
@@ -339,6 +339,12 @@ async function blocklistList(args, flags) {
339
339
  if (flags.limit) query.limit = flags.limit;
340
340
  if (flags.app) query.appKey = flags.app;
341
341
  if (flags.env || flags.environment) query.environment = flags.env || flags.environment;
342
+ if (flags.status) query.status = flags.status;
343
+ if (flags.search) query.search = flags.search;
344
+ if (flags.view) query.view = flags.view;
345
+ if (flags.approvalStatus || flags['approval-status']) {
346
+ query.approvalStatus = flags.approvalStatus || flags['approval-status'];
347
+ }
342
348
  const data = await api.get('/blocklist', { query });
343
349
  const items = data.blockedIps || [];
344
350
  s.stop(`Found ${items.length} blocked IP${items.length !== 1 ? 's' : ''}${data.total ? ` (${data.total} total)` : ''}`);
@@ -348,15 +354,17 @@ async function blocklistList(args, flags) {
348
354
  console.log('');
349
355
  const rows = items.map(b => [
350
356
  ui.c.dim(ui.truncate(b._id, 12)),
351
- ui.c.red(b.ip || b.cidr || '—'),
352
- ui.truncate(b.reason || '', 40),
357
+ ui.c.red(b.ip || b.cidr || '—'),
358
+ ui.truncate(b.reason || '', 40),
353
359
  b.source || '—',
360
+ b.status || 'active',
354
361
  b.applicationKey || ui.c.dim('all apps'),
355
362
  b.environment || ui.c.dim('all envs'),
356
363
  ui.timeAgo(b.createdAt),
364
+ b.unblockedAt ? ui.timeAgo(b.unblockedAt) : ui.c.dim('—'),
357
365
  b.expiresAt ? new Date(b.expiresAt).toLocaleDateString() : ui.c.dim('permanent'),
358
366
  ]);
359
- ui.table(['ID', 'IP/CIDR', 'Reason', 'Source', 'App', 'Env', 'Added', 'Expires'], rows);
367
+ ui.table(['ID', 'IP/CIDR', 'Reason', 'Source', 'Status', 'App', 'Env', 'Added', 'Unblocked', 'Expires'], rows);
360
368
  console.log('');
361
369
  } catch (err) {
362
370
  s.fail('Failed to fetch blocklist');
@@ -390,26 +398,28 @@ async function blocklistAdd(args, flags) {
390
398
 
391
399
  async function blocklistRemove(args, flags) {
392
400
  requireAuth();
393
- const id = args[0];
394
- if (!id) {
395
- ui.error('Blocklist entry ID required. Usage: securenow blocklist remove <id>');
396
- process.exit(1);
397
- }
398
-
399
- if (!flags.force && !flags.yes) {
400
- const ok = await ui.confirm('Remove this IP from blocklist?');
401
- if (!ok) { ui.info('Cancelled'); return; }
402
- }
403
-
404
- const s = ui.spinner('Removing from blocklist');
405
- try {
406
- await api.delete(`/blocklist/${id}`);
407
- s.stop('Removed from blocklist');
408
- } catch (err) {
409
- s.fail('Failed to remove from blocklist');
410
- throw err;
411
- }
412
- }
401
+ const id = args[0];
402
+ if (!id) {
403
+ ui.error('Blocklist entry ID required. Usage: securenow blocklist unblock <id> [--reason <reason>]');
404
+ process.exit(1);
405
+ }
406
+
407
+ if (!flags.force && !flags.yes) {
408
+ const ok = await ui.confirm('Unblock this IP? Firewall enforcement stops, but block report and history stay saved.');
409
+ if (!ok) { ui.info('Cancelled'); return; }
410
+ }
411
+
412
+ const s = ui.spinner('Unblocking IP');
413
+ try {
414
+ const body = {};
415
+ if (flags.reason) body.reason = flags.reason;
416
+ await api.post(`/blocklist/${id}/unblock`, body);
417
+ s.stop('Unblocked; block report and history retained');
418
+ } catch (err) {
419
+ s.fail('Failed to unblock IP');
420
+ throw err;
421
+ }
422
+ }
413
423
 
414
424
  async function blocklistStats(args, flags) {
415
425
  requireAuth();
package/cli.js CHANGED
@@ -255,7 +255,7 @@ const COMMANDS = {
255
255
  },
256
256
  automation: {
257
257
  desc: 'Manage automation rules for blocklist actions',
258
- usage: 'securenow automation <list|show|create|update|dry-run|execute|delete> [rule-id] [options]',
258
+ usage: 'securenow automation <list|defaults|show|create|update|dry-run|execute|delete> [rule-id] [options]',
259
259
  flags: {
260
260
  json: 'Output as JSON',
261
261
  body: 'Full JSON body for create/update',
@@ -280,6 +280,7 @@ const COMMANDS = {
280
280
  },
281
281
  sub: {
282
282
  list: { desc: 'List automation rules', run: (a, f) => require('./cli/automation').list(a, f) },
283
+ 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) },
283
284
  show: { desc: 'Show automation rule details', usage: 'securenow automation show <rule-id>', run: (a, f) => require('./cli/automation').show(a, f) },
284
285
  create: { desc: 'Create automation rule', usage: 'securenow automation create --name "..." --conditions \'[...]\' --actions \'[...]\'', run: (a, f) => require('./cli/automation').create(a, f) },
285
286
  update: { desc: 'Update automation rule', usage: 'securenow automation update <rule-id> --body \'{...}\'', run: (a, f) => require('./cli/automation').update(a, f) },
@@ -292,13 +293,14 @@ const COMMANDS = {
292
293
  blocklist: {
293
294
  desc: 'Manage IP blocklist',
294
295
  usage: 'securenow blocklist <subcommand> [options]',
295
- flags: { app: 'Scope to app key', env: 'Scope to environment', environment: 'Alias for --env', page: 'Page number', limit: 'Max results', json: 'Output as JSON' },
296
+ 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', json: 'Output as JSON' },
296
297
  sub: {
297
298
  list: { desc: 'List blocked IPs', run: (a, f) => require('./cli/security').blocklistList(a, f) },
298
299
  add: { desc: 'Block an IP', usage: 'securenow blocklist add <ip> [--app <key>] [--env production] [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistAdd(a, f) },
299
- remove: { desc: 'Unblock an IP', usage: 'securenow blocklist remove <id>', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
300
- stats: { desc: 'Blocklist statistics', run: (a, f) => require('./cli/security').blocklistStats(a, f) },
301
- },
300
+ 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) },
301
+ remove: { desc: 'Unblock an IP (compatibility alias)', usage: 'securenow blocklist remove <id> [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
302
+ stats: { desc: 'Blocklist statistics', run: (a, f) => require('./cli/security').blocklistStats(a, f) },
303
+ },
302
304
  defaultSub: 'list',
303
305
  },
304
306
  allowlist: {
package/mcp/catalog.js CHANGED
@@ -676,6 +676,21 @@ const TOOLS = [
676
676
  endpoint: '/automation-rules',
677
677
  inputSchema: objectSchema({}),
678
678
  },
679
+ {
680
+ name: 'securenow_automation_defaults_ensure',
681
+ title: 'Ensure Default Automation Rules',
682
+ description: 'Create or refresh SecureNow default risk-score automation rules for the current account. Write action; requires confirmation.',
683
+ scope: 'automation:write',
684
+ readOnly: false,
685
+ confirm: true,
686
+ method: 'POST',
687
+ endpoint: '/automation-rules/defaults/ensure',
688
+ bodyFields: ['forceEnableExisting'],
689
+ inputSchema: objectSchema({
690
+ forceEnableExisting: boolean('Re-enable existing SecureNow default rules if the customer disabled them. Defaults to false.'),
691
+ ...confirmSchema,
692
+ }, ['confirm', 'reason']),
693
+ },
679
694
  {
680
695
  name: 'securenow_automation_rule_get',
681
696
  title: 'Get Automation Rule',
@@ -1040,14 +1055,31 @@ const TOOLS = [
1040
1055
  },
1041
1056
  {
1042
1057
  name: 'securenow_blocklist_remove',
1043
- title: 'Remove Blocked IP',
1044
- description: 'Remove a blocklist entry. Write action; requires confirmation.',
1058
+ title: 'Unblock IP (Compat Alias)',
1059
+ description: 'Compatibility alias for unblocking a blocklist entry while retaining block report/history. Write action; requires confirmation.',
1045
1060
  scope: 'blocklist:write',
1046
1061
  readOnly: false,
1047
1062
  confirm: true,
1048
- method: 'DELETE',
1049
- endpoint: '/blocklist/:id',
1063
+ method: 'POST',
1064
+ endpoint: '/blocklist/:id/unblock',
1050
1065
  pathParams: ['id'],
1066
+ bodyFields: ['reason'],
1067
+ inputSchema: objectSchema({
1068
+ id: string('Blocklist entry id.'),
1069
+ ...confirmSchema,
1070
+ }, ['id', 'confirm', 'reason']),
1071
+ },
1072
+ {
1073
+ name: 'securenow_blocklist_unblock',
1074
+ title: 'Unblock IP And Keep History',
1075
+ description: 'Unblock a blocklist entry so firewall enforcement stops, while retaining the block report, history, and unblock audit trail. Write action; requires confirmation.',
1076
+ scope: 'blocklist:write',
1077
+ readOnly: false,
1078
+ confirm: true,
1079
+ method: 'POST',
1080
+ endpoint: '/blocklist/:id/unblock',
1081
+ pathParams: ['id'],
1082
+ bodyFields: ['reason'],
1051
1083
  inputSchema: objectSchema({
1052
1084
  id: string('Blocklist entry id.'),
1053
1085
  ...confirmSchema,
@@ -1428,16 +1460,16 @@ function promptMessages(name, args = {}) {
1428
1460
  type: 'text',
1429
1461
  text: [
1430
1462
  'Configure SecureNow default automation using the MCP tools.',
1431
- `Default environment scope: ${environment}.`,
1432
- 'Use one canonical product score for automation decisions: riskScore. Treat SecureNow IPDB / AbuseIPDB score and AI confidence as supporting evidence, not the primary UI score.',
1433
- 'Desired defaults:',
1434
- '- Auto-block High-Confidence AI Malicious IPs: aiDecision=pending_approval AND isMalicious=true AND riskScore>=90, TTL 168h.',
1435
- '- Auto-block High-Confidence XSS IPs: alertTag in xss AND riskScore>=80, TTL 24h.',
1436
- '- High-reputation IPDB automation may remain enabled only as an explicit reputation policy: abuseConfidenceScore>=80.',
1437
- 'Start with securenow_automation_rules_list. Reuse/update existing rules where names/conditions match instead of creating duplicates.',
1438
- 'Create new rules with status=disabled when possible, dry-run each changed or created rule with securenow_automation_rule_dry_run, then enable only after the sample matches look safe.',
1463
+ `Review environment context: ${environment}. Built-in defaults apply to all apps and all environments unless the customer narrows them later.`,
1464
+ 'Use one canonical product score for automation decisions: riskScore. SecureNow IPDB / AbuseIPDB score and AI confidence remain supporting evidence.',
1465
+ 'Built-in defaults are active by default:',
1466
+ '- Critical Risk Auto-Block: riskScore>=95, TTL 168h.',
1467
+ '- High Risk Auto-Block: riskScore>=90 AND riskScore<95, TTL 72h.',
1468
+ '- Elevated Risk Auto-Block: riskScore>=85 AND riskScore<90, TTL 24h.',
1469
+ 'Start with securenow_automation_defaults_ensure({ confirm:true, reason:"Provision SecureNow default risk-score automation" }) when writes are confirmed, otherwise start with securenow_automation_rules_list and report what would be ensured.',
1470
+ 'After ensuring defaults, dry-run each default rule with securenow_automation_rule_dry_run to inspect sample matches. Disable or tune only if evidence shows customer-specific false positives.',
1439
1471
  confirmWrites
1440
- ? 'The user requested execution. Create/update rules with confirm:true, production scope, clear names, and precise reasons. For new rules, prefer status=disabled, dry-run, then update status=active after review. Re-list rules after writes.'
1472
+ ? 'The user requested execution. Ensure defaults with confirm:true, do not force-enable previously disabled defaults unless the user explicitly asked, and re-list rules after writes.'
1441
1473
  : 'Do not execute writes yet. Return exact MCP update/create calls and dry-run calls for approval.',
1442
1474
  'End with active rules, disabled rules that should stay disabled, and any risk from automation scope.',
1443
1475
  ].join('\n'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "7.7.6",
3
+ "version": "7.7.7",
4
4
  "description": "OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt - Send traces and logs to any OTLP-compatible backend",
5
5
  "type": "commonjs",
6
6
  "main": "register.js",