securenow 7.6.9 → 7.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -164,7 +164,7 @@ const COMMANDS = {
164
164
  },
165
165
  human: {
166
166
  desc: 'Work the human action queue prepared by SecureNow AI',
167
- usage: 'securenow human <list|show|block|fp|prompt|work> [row|notificationId:ip] [options]',
167
+ usage: 'securenow human <list|show|block|fp|action|prompt|work> [row|notificationId:ip] [options]',
168
168
  flags: {
169
169
  json: 'Output as JSON',
170
170
  page: 'Queue page number',
@@ -178,6 +178,9 @@ const COMMANDS = {
178
178
  'rule-scope': 'False-positive scope: this_rule | specific_rules | all_existing | any_rule',
179
179
  'create-exclusion': 'Create a restrictive exclusion when marking false positive',
180
180
  'apply-existing': 'Apply false-positive decision to existing matching rows',
181
+ status: 'Case action status: approved, rejected, executed, failed, or proposed',
182
+ 'action-key': 'Case-level proposed action key',
183
+ result: 'Case action result JSON',
181
184
  mode: 'Prompt mode label for MCP prompt output',
182
185
  },
183
186
  sub: {
@@ -185,6 +188,7 @@ const COMMANDS = {
185
188
  show: { desc: 'Show one row with AI report, proofs, DAG, and trace links', usage: 'securenow human show <row|notificationId:ip>', run: (a, f) => require('./cli/human').show(a, f) },
186
189
  block: { desc: 'Approve the AI block recommendation for a row', usage: 'securenow human block <row|notificationId:ip> --yes --reason "..."', run: (a, f) => require('./cli/human').block(a, f) },
187
190
  fp: { desc: 'Mark a row as a scoped false positive', usage: 'securenow human fp <row|notificationId:ip> --yes --reason "..."', run: (a, f) => require('./cli/human').fp(a, f) },
191
+ action: { desc: 'Approve/reject/execute a case-level proposed action', usage: 'securenow human action <row|notificationId> [actionKey] --status approved --yes --reason "..."', run: (a, f) => require('./cli/human').action(a, f) },
188
192
  prompt: { desc: 'Print a Codex/Claude MCP prompt for row or queue work', usage: 'securenow human prompt [row|notificationId:ip] [--limit 10]', run: (a, f) => require('./cli/human').prompt(a, f) },
189
193
  work: { desc: 'List the queue and print the MCP runbook to work it deeply', usage: 'securenow human work [--limit 10]', run: (a, f) => require('./cli/human').work(a, f) },
190
194
  },
@@ -226,47 +230,86 @@ const COMMANDS = {
226
230
  },
227
231
  defaultSub: 'list',
228
232
  },
229
- firewall: {
230
- desc: 'Firewall status, per-app toggle, and IP testing',
231
- usage: 'securenow firewall <subcommand> [options]',
232
- flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
233
+ firewall: {
234
+ desc: 'Firewall status, per-app toggle, and IP testing',
235
+ usage: 'securenow firewall <subcommand> [options]',
236
+ flags: { app: 'App key (defaults to logged-in app)', json: 'Output as JSON' },
233
237
  sub: {
234
238
  status: { desc: 'Show firewall status, layers, and blocklist info', flags: { env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').status(a, f) },
235
239
  apps: { desc: 'List apps with their firewall on/off state', run: (a, f) => require('./cli/firewall').appsList(a, f) },
236
240
  enable: { desc: 'Turn the firewall ON for an app environment', usage: 'securenow firewall enable [--app <key>] [--env production]', flags: { app: 'App key (defaults to logged-in app)', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').enable(a, f) },
237
241
  disable: { desc: 'Turn the firewall OFF for an app environment', usage: 'securenow firewall disable [--app <key>] [--env local]', flags: { app: 'App key (defaults to logged-in app)', env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').disable(a, f) },
238
242
  'test-ip': { desc: 'Check if an IP would be blocked', usage: 'securenow firewall test-ip <ip> [--env production]', flags: { env: 'Environment (default: production)', environment: 'Alias for --env' }, run: (a, f) => require('./cli/firewall').testIp(a, f) },
239
- },
240
- defaultSub: 'status',
241
- },
242
- blocklist: {
243
- desc: 'Manage IP blocklist',
244
- usage: 'securenow blocklist <subcommand> [options]',
245
- sub: {
246
- list: { desc: 'List blocked IPs', run: (a, f) => require('./cli/security').blocklistList(a, f) },
247
- add: { desc: 'Block an IP', usage: 'securenow blocklist add <ip> [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistAdd(a, f) },
243
+ },
244
+ defaultSub: 'status',
245
+ },
246
+ automation: {
247
+ desc: 'Manage automation rules for blocklist actions',
248
+ usage: 'securenow automation <list|show|create|update|dry-run|execute|delete> [rule-id] [options]',
249
+ flags: {
250
+ json: 'Output as JSON',
251
+ body: 'Full JSON body for create/update',
252
+ file: 'Read JSON body from file',
253
+ name: 'Rule name',
254
+ description: 'Rule description',
255
+ conditions: 'Conditions JSON array',
256
+ actions: 'Actions JSON array',
257
+ logic: 'Condition logic: AND or OR',
258
+ status: 'Rule status: active or disabled',
259
+ app: 'Comma-separated app key(s) to scope the rule',
260
+ apps: 'Alias for --app',
261
+ 'applications-all': 'Scope rule to all apps',
262
+ env: 'Comma-separated environment(s) to scope the rule',
263
+ environment: 'Alias for --env',
264
+ environments: 'Alias for --env',
265
+ 'environments-all': 'Scope rule to all environments',
266
+ limit: 'Dry-run notification scan limit',
267
+ 'sample-limit': 'Dry-run sample count',
268
+ yes: 'Confirm execute/delete',
269
+ force: 'Alias for --yes',
270
+ },
271
+ sub: {
272
+ list: { desc: 'List automation rules', run: (a, f) => require('./cli/automation').list(a, f) },
273
+ show: { desc: 'Show automation rule details', usage: 'securenow automation show <rule-id>', run: (a, f) => require('./cli/automation').show(a, f) },
274
+ create: { desc: 'Create automation rule', usage: 'securenow automation create --name "..." --conditions \'[...]\' --actions \'[...]\'', run: (a, f) => require('./cli/automation').create(a, f) },
275
+ update: { desc: 'Update automation rule', usage: 'securenow automation update <rule-id> --body \'{...}\'', run: (a, f) => require('./cli/automation').update(a, f) },
276
+ 'dry-run': { desc: 'Preview automation matches without writing blocklist entries', usage: 'securenow automation dry-run <rule-id>', run: (a, f) => require('./cli/automation').dryRun(a, f) },
277
+ execute: { desc: 'Execute an automation rule now', usage: 'securenow automation execute <rule-id> --yes', run: (a, f) => require('./cli/automation').execute(a, f) },
278
+ delete: { desc: 'Delete an automation rule', usage: 'securenow automation delete <rule-id> --yes', run: (a, f) => require('./cli/automation').remove(a, f) },
279
+ },
280
+ defaultSub: 'list',
281
+ },
282
+ blocklist: {
283
+ desc: 'Manage IP blocklist',
284
+ usage: 'securenow blocklist <subcommand> [options]',
285
+ flags: { app: 'Scope to app key', env: 'Scope to environment', environment: 'Alias for --env', page: 'Page number', limit: 'Max results', json: 'Output as JSON' },
286
+ sub: {
287
+ list: { desc: 'List blocked IPs', run: (a, f) => require('./cli/security').blocklistList(a, f) },
288
+ add: { desc: 'Block an IP', usage: 'securenow blocklist add <ip> [--app <key>] [--env production] [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistAdd(a, f) },
248
289
  remove: { desc: 'Unblock an IP', usage: 'securenow blocklist remove <id>', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
249
290
  stats: { desc: 'Blocklist statistics', run: (a, f) => require('./cli/security').blocklistStats(a, f) },
250
291
  },
251
292
  defaultSub: 'list',
252
293
  },
253
- allowlist: {
254
- desc: 'Manage IP allowlist (only allow listed IPs)',
255
- usage: 'securenow allowlist <subcommand> [options]',
256
- sub: {
257
- list: { desc: 'List allowed IPs', run: (a, f) => require('./cli/security').allowlistList(a, f) },
258
- add: { desc: 'Allow an IP', usage: 'securenow allowlist add <ip> [--label <label>] [--reason <reason>]', run: (a, f) => require('./cli/security').allowlistAdd(a, f) },
294
+ allowlist: {
295
+ desc: 'Manage IP allowlist (only allow listed IPs)',
296
+ usage: 'securenow allowlist <subcommand> [options]',
297
+ flags: { app: 'Scope to app key(s), comma-separated', env: 'Scope to environment', environment: 'Alias for --env', page: 'Page number', limit: 'Max results', json: 'Output as JSON' },
298
+ sub: {
299
+ list: { desc: 'List allowed IPs', run: (a, f) => require('./cli/security').allowlistList(a, f) },
300
+ add: { desc: 'Allow an IP', usage: 'securenow allowlist add <ip> [--app <key>] [--env local] [--label <label>] [--reason <reason>]', run: (a, f) => require('./cli/security').allowlistAdd(a, f) },
259
301
  remove: { desc: 'Remove an allowed IP', usage: 'securenow allowlist remove <id>', run: (a, f) => require('./cli/security').allowlistRemove(a, f) },
260
302
  stats: { desc: 'Allowlist statistics', run: (a, f) => require('./cli/security').allowlistStats(a, f) },
261
303
  },
262
304
  defaultSub: 'list',
263
305
  },
264
- trusted: {
265
- desc: 'Manage trusted IPs',
266
- usage: 'securenow trusted <subcommand> [options]',
267
- sub: {
268
- list: { desc: 'List trusted IPs', run: (a, f) => require('./cli/security').trustedList(a, f) },
269
- add: { desc: 'Add trusted IP', usage: 'securenow trusted add <ip> [--label <label>]', run: (a, f) => require('./cli/security').trustedAdd(a, f) },
306
+ trusted: {
307
+ desc: 'Manage trusted IPs',
308
+ usage: 'securenow trusted <subcommand> [options]',
309
+ flags: { app: 'Scope to app key(s), comma-separated', env: 'Scope to environment', environment: 'Alias for --env', json: 'Output as JSON' },
310
+ sub: {
311
+ list: { desc: 'List trusted IPs', run: (a, f) => require('./cli/security').trustedList(a, f) },
312
+ add: { desc: 'Add trusted IP', usage: 'securenow trusted add <ip> [--app <key>] [--env local] [--label <label>]', run: (a, f) => require('./cli/security').trustedAdd(a, f) },
270
313
  remove: { desc: 'Remove trusted IP', usage: 'securenow trusted remove <id>', run: (a, f) => require('./cli/security').trustedRemove(a, f) },
271
314
  },
272
315
  defaultSub: 'list',
@@ -499,8 +542,8 @@ function showHelp(commandName) {
499
542
  'Observe': ['traces', 'logs', 'analytics'],
500
543
  'Detect & Respond': ['human', 'notifications', 'alerts', 'fp'],
501
544
  'Investigate': ['ip', 'forensics'],
502
- 'Firewall': ['firewall'],
503
- 'Remediation': ['blocklist', 'allowlist', 'trusted'],
545
+ 'Firewall': ['firewall'],
546
+ 'Remediation': ['automation', 'blocklist', 'allowlist', 'trusted'],
504
547
  'Telemetry': ['log', 'test-span'],
505
548
  'Utilities': ['redact', 'cidr', 'doctor', 'env', 'mcp'],
506
549
  'Settings': ['instances', 'config', 'version'],
package/mcp/catalog.js CHANGED
@@ -230,8 +230,11 @@ const TOOLS = [
230
230
  readOnly: true,
231
231
  method: 'GET',
232
232
  endpoint: '/firewall/status',
233
- queryFields: ['environment'],
234
- inputSchema: objectSchema({ ...environmentInput }),
233
+ queryFields: ['environment', 'appKey'],
234
+ inputSchema: objectSchema({
235
+ appKey: string('Optional application key UUID to scope the status check.'),
236
+ ...environmentInput,
237
+ }),
235
238
  },
236
239
  {
237
240
  name: 'securenow_firewall_enable',
@@ -297,9 +300,10 @@ const TOOLS = [
297
300
  method: 'GET',
298
301
  endpoint: '/firewall/check/:ip',
299
302
  pathParams: ['ip'],
300
- queryFields: ['environment'],
303
+ queryFields: ['environment', 'appKey'],
301
304
  inputSchema: objectSchema({
302
305
  ip: string('IPv4 address to test.'),
306
+ appKey: string('Optional application key UUID to test the app/environment toggle and scoped lists.'),
303
307
  ...environmentInput,
304
308
  }, ['ip']),
305
309
  },
@@ -421,6 +425,19 @@ const TOOLS = [
421
425
  id: string('Notification id.'),
422
426
  }, ['id']),
423
427
  },
428
+ {
429
+ name: 'securenow_notifications_batch_get',
430
+ title: 'Get Notifications Batch',
431
+ description: 'Fetch multiple notification/case records in one request.',
432
+ scope: 'notifications:read',
433
+ readOnly: true,
434
+ method: 'POST',
435
+ endpoint: '/notifications/batch',
436
+ bodyFields: ['ids'],
437
+ inputSchema: objectSchema({
438
+ ids: arrayOfStrings('Notification ids to fetch, up to 50.'),
439
+ }, ['ids']),
440
+ },
424
441
  {
425
442
  name: 'securenow_human_actions_list',
426
443
  title: 'List Human Action Queue',
@@ -496,6 +513,26 @@ const TOOLS = [
496
513
  ...confirmSchema,
497
514
  }, ['notificationId', 'ip', 'confirm', 'reason']),
498
515
  },
516
+ {
517
+ name: 'securenow_human_case_action_update',
518
+ title: 'Update Case-Level Human Action',
519
+ description: 'Approve, reject, execute, or fail a case-level proposed action such as tune_rule or create_exclusion. Write action; requires confirmation.',
520
+ scope: 'notifications:write',
521
+ readOnly: false,
522
+ confirm: true,
523
+ method: 'PUT',
524
+ endpoint: '/notifications/:notificationId/agent-case/actions/:actionKey',
525
+ pathParams: ['notificationId', 'actionKey'],
526
+ bodyFields: ['status', 'result'],
527
+ reasonInResult: true,
528
+ inputSchema: objectSchema({
529
+ notificationId: string('Notification id from the case-action row.'),
530
+ actionKey: string('Proposed action key from the row, for example tune_rule:...'),
531
+ status: string('New status: proposed, approved, rejected, executed, or failed.'),
532
+ result: { type: 'object', additionalProperties: true, description: 'Optional structured result/audit details.' },
533
+ ...confirmSchema,
534
+ }, ['notificationId', 'actionKey', 'status', 'confirm', 'reason']),
535
+ },
499
536
  {
500
537
  name: 'securenow_ip_lookup',
501
538
  title: 'IP Intelligence Lookup',
@@ -567,6 +604,258 @@ const TOOLS = [
567
604
  instanceId: string('Optional ClickHouse instance id.'),
568
605
  }),
569
606
  },
607
+ {
608
+ name: 'securenow_automation_rules_list',
609
+ title: 'List Automation Rules',
610
+ description: 'List blocklist automation rules with app/environment scope and stats.',
611
+ scope: 'automation:read',
612
+ readOnly: true,
613
+ method: 'GET',
614
+ endpoint: '/automation-rules',
615
+ inputSchema: objectSchema({}),
616
+ },
617
+ {
618
+ name: 'securenow_automation_rule_get',
619
+ title: 'Get Automation Rule',
620
+ description: 'Fetch one automation rule.',
621
+ scope: 'automation:read',
622
+ readOnly: true,
623
+ method: 'GET',
624
+ endpoint: '/automation-rules/:id',
625
+ pathParams: ['id'],
626
+ inputSchema: objectSchema({
627
+ id: string('Automation rule id.'),
628
+ }, ['id']),
629
+ },
630
+ {
631
+ name: 'securenow_automation_rule_create',
632
+ title: 'Create Automation Rule',
633
+ description: 'Create a blocklist automation rule. Write action; requires confirmation.',
634
+ scope: 'automation:write',
635
+ readOnly: false,
636
+ confirm: true,
637
+ method: 'POST',
638
+ endpoint: '/automation-rules',
639
+ bodyFields: ['name', 'description', 'conditions', 'conditionLogic', 'actions', 'applicationsAll', 'applicationKeys', 'environmentsAll', 'environments'],
640
+ inputSchema: objectSchema({
641
+ name: string('Rule name.'),
642
+ description: string('Optional rule description.'),
643
+ conditions: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'Condition array. Fields include abuseConfidenceScore, riskScore, alertName, alertTag, attackType, path, environment.' },
644
+ conditionLogic: string('AND or OR.'),
645
+ actions: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'Action array, for example [{ "type":"addToBlocklist", "config":{ "reason":"...", "ttlHours":24 }}].' },
646
+ applicationsAll: boolean('Apply to all applications.'),
647
+ applicationKeys: arrayOfStrings('Application keys when not applying to all applications.'),
648
+ environmentsAll: boolean('Apply to all environments.'),
649
+ environments: arrayOfStrings('Deployment environments when not applying to all environments.'),
650
+ ...confirmSchema,
651
+ }, ['name', 'conditions', 'actions', 'confirm', 'reason']),
652
+ },
653
+ {
654
+ name: 'securenow_automation_rule_update',
655
+ title: 'Update Automation Rule',
656
+ description: 'Update a blocklist automation rule. Write action; requires confirmation.',
657
+ scope: 'automation:write',
658
+ readOnly: false,
659
+ confirm: true,
660
+ method: 'PUT',
661
+ endpoint: '/automation-rules/:id',
662
+ pathParams: ['id'],
663
+ bodyFields: ['name', 'description', 'conditions', 'conditionLogic', 'actions', 'status', 'applicationsAll', 'applicationKeys', 'environmentsAll', 'environments'],
664
+ inputSchema: objectSchema({
665
+ id: string('Automation rule id.'),
666
+ name: string('Rule name.'),
667
+ description: string('Optional rule description.'),
668
+ status: string('active or disabled.'),
669
+ conditions: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'Condition array.' },
670
+ conditionLogic: string('AND or OR.'),
671
+ actions: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'Action array.' },
672
+ applicationsAll: boolean('Apply to all applications.'),
673
+ applicationKeys: arrayOfStrings('Application keys when not applying to all applications.'),
674
+ environmentsAll: boolean('Apply to all environments.'),
675
+ environments: arrayOfStrings('Deployment environments when not applying to all environments.'),
676
+ ...confirmSchema,
677
+ }, ['id', 'confirm', 'reason']),
678
+ },
679
+ {
680
+ name: 'securenow_automation_rule_dry_run',
681
+ title: 'Dry-Run Automation Rule',
682
+ description: 'Preview automation matches without writing blocklist entries.',
683
+ scope: 'automation:read',
684
+ readOnly: true,
685
+ method: 'POST',
686
+ endpoint: '/automation-rules/:id/dry-run',
687
+ pathParams: ['id'],
688
+ bodyFields: ['limit', 'sampleLimit'],
689
+ inputSchema: objectSchema({
690
+ id: string('Automation rule id.'),
691
+ limit: number('Maximum notifications to scan.', { minimum: 1, maximum: 2000 }),
692
+ sampleLimit: number('Maximum sample matches to return.', { minimum: 1, maximum: 50 }),
693
+ }, ['id']),
694
+ },
695
+ {
696
+ name: 'securenow_automation_rule_execute',
697
+ title: 'Execute Automation Rule',
698
+ description: 'Execute an automation rule and add matching IPs to the blocklist. Write action; requires confirmation.',
699
+ scope: 'automation:write',
700
+ readOnly: false,
701
+ destructive: true,
702
+ confirm: true,
703
+ method: 'POST',
704
+ endpoint: '/automation-rules/:id/execute',
705
+ pathParams: ['id'],
706
+ inputSchema: objectSchema({
707
+ id: string('Automation rule id.'),
708
+ ...confirmSchema,
709
+ }, ['id', 'confirm', 'reason']),
710
+ },
711
+ {
712
+ name: 'securenow_automation_rule_delete',
713
+ title: 'Delete Automation Rule',
714
+ description: 'Delete an automation rule. Write action; requires confirmation.',
715
+ scope: 'automation:write',
716
+ readOnly: false,
717
+ destructive: true,
718
+ confirm: true,
719
+ method: 'DELETE',
720
+ endpoint: '/automation-rules/:id',
721
+ pathParams: ['id'],
722
+ inputSchema: objectSchema({
723
+ id: string('Automation rule id.'),
724
+ ...confirmSchema,
725
+ }, ['id', 'confirm', 'reason']),
726
+ },
727
+ {
728
+ name: 'securenow_alert_rules_list',
729
+ title: 'List Alert Rules',
730
+ description: 'List alert rules, including system rules and app scope.',
731
+ scope: 'alerts:read',
732
+ readOnly: true,
733
+ method: 'GET',
734
+ endpoint: '/alert-rules',
735
+ inputSchema: objectSchema({}),
736
+ },
737
+ {
738
+ name: 'securenow_alert_rule_get',
739
+ title: 'Get Alert Rule',
740
+ description: 'Fetch one alert rule.',
741
+ scope: 'alerts:read',
742
+ readOnly: true,
743
+ method: 'GET',
744
+ endpoint: '/alert-rules/:id',
745
+ pathParams: ['id'],
746
+ inputSchema: objectSchema({
747
+ id: string('Alert rule id.'),
748
+ }, ['id']),
749
+ },
750
+ {
751
+ name: 'securenow_alert_rule_update',
752
+ title: 'Update Alert Rule',
753
+ description: 'Update alert rule scope/status/schedule/throttle or record a review action. Write action; requires confirmation.',
754
+ scope: 'alerts:write',
755
+ readOnly: false,
756
+ confirm: true,
757
+ method: 'PUT',
758
+ endpoint: '/alert-rules/:id',
759
+ pathParams: ['id'],
760
+ bodyFields: ['name', 'description', 'status', 'applicationsAll', 'applications', 'schedule', 'throttle', 'alertChannelIds', 'reviewAction', 'reviewNote', 'reviewNotificationId'],
761
+ inputSchema: objectSchema({
762
+ id: string('Alert rule id.'),
763
+ name: string('Rule name for custom rules.'),
764
+ description: string('Rule description for custom rules.'),
765
+ status: string('Active, Disabled, or Paused.'),
766
+ applicationsAll: boolean('Scope rule to all applications.'),
767
+ applications: arrayOfStrings('Application keys when applicationsAll is false.'),
768
+ schedule: { type: 'object', additionalProperties: true, description: 'Schedule patch.' },
769
+ throttle: { type: 'object', additionalProperties: true, description: 'Throttle patch.' },
770
+ alertChannelIds: arrayOfStrings('Alert channel ids.'),
771
+ reviewAction: string('Rule review action, e.g. keep_active or saved_new_version.'),
772
+ reviewNote: string('Review note.'),
773
+ reviewNotificationId: string('Notification id tied to this review.'),
774
+ ...confirmSchema,
775
+ }, ['id', 'confirm', 'reason']),
776
+ },
777
+ {
778
+ name: 'securenow_alert_rule_test',
779
+ title: 'Test Alert Rule',
780
+ description: 'Start a live or dry-run alert rule test.',
781
+ scope: 'alerts:write',
782
+ readOnly: false,
783
+ confirm: true,
784
+ method: 'POST',
785
+ endpoint: '/alert-rules/:id/test',
786
+ pathParams: ['id'],
787
+ bodyFields: ['applicationKey', 'mode'],
788
+ inputSchema: objectSchema({
789
+ id: string('Alert rule id.'),
790
+ applicationKey: string('Application key to test.'),
791
+ mode: string('dry_run or live.'),
792
+ ...confirmSchema,
793
+ }, ['id', 'confirm', 'reason']),
794
+ },
795
+ {
796
+ name: 'securenow_alert_rule_test_result',
797
+ title: 'Get Alert Rule Test Result',
798
+ description: 'Poll alert rule test status and results.',
799
+ scope: 'alerts:read',
800
+ readOnly: true,
801
+ method: 'GET',
802
+ endpoint: '/alert-rules/:id/test/:testId',
803
+ pathParams: ['id', 'testId'],
804
+ inputSchema: objectSchema({
805
+ id: string('Alert rule id.'),
806
+ testId: string('Alert rule test id.'),
807
+ }, ['id', 'testId']),
808
+ },
809
+ {
810
+ name: 'securenow_alert_rule_exclusions_list',
811
+ title: 'List Alert Rule Exclusions',
812
+ description: 'List exclusions embedded on one alert rule.',
813
+ scope: 'alerts:read',
814
+ readOnly: true,
815
+ method: 'GET',
816
+ endpoint: '/alert-rules/:id/exclusions',
817
+ pathParams: ['id'],
818
+ inputSchema: objectSchema({
819
+ id: string('Alert rule id.'),
820
+ }, ['id']),
821
+ },
822
+ {
823
+ name: 'securenow_alert_rule_exclusion_add',
824
+ title: 'Add Alert Rule Exclusion',
825
+ description: 'Add a restrictive exclusion to one alert rule. Write action; requires confirmation.',
826
+ scope: 'alerts:write',
827
+ readOnly: false,
828
+ confirm: true,
829
+ method: 'POST',
830
+ endpoint: '/alert-rules/:id/exclusions',
831
+ pathParams: ['id'],
832
+ bodyFields: ['conditions', 'matchMode', 'reason', 'pathPattern', 'isActive'],
833
+ inputSchema: objectSchema({
834
+ id: string('Alert rule id.'),
835
+ conditions: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'Restrictive exclusion conditions.' },
836
+ matchMode: string('all or any.'),
837
+ reason: string('Exclusion reason.'),
838
+ pathPattern: string('Optional path pattern.'),
839
+ isActive: boolean('Whether the exclusion is active.'),
840
+ ...confirmSchema,
841
+ }, ['id', 'confirm', 'reason']),
842
+ },
843
+ {
844
+ name: 'securenow_alert_rule_exclusion_remove',
845
+ title: 'Remove Alert Rule Exclusion',
846
+ description: 'Remove an alert rule exclusion. Write action; requires confirmation.',
847
+ scope: 'alerts:write',
848
+ readOnly: false,
849
+ confirm: true,
850
+ method: 'DELETE',
851
+ endpoint: '/alert-rules/:id/exclusions/:exclusionId',
852
+ pathParams: ['id', 'exclusionId'],
853
+ inputSchema: objectSchema({
854
+ id: string('Alert rule id.'),
855
+ exclusionId: string('Exclusion id.'),
856
+ ...confirmSchema,
857
+ }, ['id', 'exclusionId', 'confirm', 'reason']),
858
+ },
570
859
  {
571
860
  name: 'securenow_blocklist_list',
572
861
  title: 'List Blocklist',
@@ -575,8 +864,12 @@ const TOOLS = [
575
864
  readOnly: true,
576
865
  method: 'GET',
577
866
  endpoint: '/blocklist',
578
- queryFields: ['page', 'limit'],
579
- inputSchema: objectSchema({ ...pagingInput }),
867
+ queryFields: ['page', 'limit', 'appKey', 'environment'],
868
+ inputSchema: objectSchema({
869
+ ...pagingInput,
870
+ appKey: string('Optional application key scope.'),
871
+ ...environmentInput,
872
+ }),
580
873
  },
581
874
  {
582
875
  name: 'securenow_blocklist_add',
@@ -587,12 +880,14 @@ const TOOLS = [
587
880
  confirm: true,
588
881
  method: 'POST',
589
882
  endpoint: '/blocklist',
590
- bodyFields: ['ip', 'reason', 'expiresAt', 'metadata'],
883
+ bodyFields: ['ip', 'reason', 'expiresAt', 'metadata', 'appKey', 'environment'],
591
884
  inputSchema: objectSchema({
592
885
  ip: string('IPv4 address or CIDR.'),
593
886
  reason: string('Reason for blocking.'),
594
887
  expiresAt: string('Optional expiry time as ISO 8601.'),
595
888
  metadata: { type: 'object', additionalProperties: true, description: 'Optional metadata.' },
889
+ appKey: string('Optional application key to scope this block. Omit for all apps.'),
890
+ ...environmentInput,
596
891
  ...confirmSchema,
597
892
  }, ['ip', 'confirm', 'reason']),
598
893
  },
@@ -629,8 +924,12 @@ const TOOLS = [
629
924
  readOnly: true,
630
925
  method: 'GET',
631
926
  endpoint: '/allowlist',
632
- queryFields: ['page', 'limit'],
633
- inputSchema: objectSchema({ ...pagingInput }),
927
+ queryFields: ['page', 'limit', 'appKey', 'environment'],
928
+ inputSchema: objectSchema({
929
+ ...pagingInput,
930
+ appKey: string('Optional application key scope.'),
931
+ ...environmentInput,
932
+ }),
634
933
  },
635
934
  {
636
935
  name: 'securenow_allowlist_add',
@@ -641,7 +940,7 @@ const TOOLS = [
641
940
  confirm: true,
642
941
  method: 'POST',
643
942
  endpoint: '/allowlist',
644
- bodyFields: ['ip', 'label', 'reason', 'expiresAt', 'applicationsAll', 'applicationKeys'],
943
+ bodyFields: ['ip', 'label', 'reason', 'expiresAt', 'applicationsAll', 'applicationKeys', 'environment'],
645
944
  inputSchema: objectSchema({
646
945
  ip: string('IPv4 address or CIDR.'),
647
946
  label: string('Human-readable label.'),
@@ -649,6 +948,7 @@ const TOOLS = [
649
948
  expiresAt: string('Optional expiry time as ISO 8601.'),
650
949
  applicationsAll: boolean('Apply to all applications.'),
651
950
  applicationKeys: arrayOfStrings('Application keys to scope this allowlist entry to.'),
951
+ ...environmentInput,
652
952
  ...confirmSchema,
653
953
  }, ['ip', 'confirm', 'reason']),
654
954
  },
@@ -675,7 +975,11 @@ const TOOLS = [
675
975
  readOnly: true,
676
976
  method: 'GET',
677
977
  endpoint: '/trusted-ips',
678
- inputSchema: objectSchema({}),
978
+ queryFields: ['appKey', 'environment'],
979
+ inputSchema: objectSchema({
980
+ appKey: string('Optional application key scope.'),
981
+ ...environmentInput,
982
+ }),
679
983
  },
680
984
  {
681
985
  name: 'securenow_trusted_add',
@@ -686,13 +990,14 @@ const TOOLS = [
686
990
  confirm: true,
687
991
  method: 'POST',
688
992
  endpoint: '/trusted-ips',
689
- bodyFields: ['ip', 'label', 'note', 'applicationsAll', 'applicationKeys'],
993
+ bodyFields: ['ip', 'label', 'note', 'applicationsAll', 'applicationKeys', 'environment'],
690
994
  inputSchema: objectSchema({
691
995
  ip: string('IPv4 address or CIDR.'),
692
996
  label: string('Human-readable label.'),
693
997
  note: string('Optional note.'),
694
998
  applicationsAll: boolean('Apply to all applications.'),
695
999
  applicationKeys: arrayOfStrings('Application keys to scope this trusted IP to.'),
1000
+ ...environmentInput,
696
1001
  ...confirmSchema,
697
1002
  }, ['ip', 'confirm', 'reason']),
698
1003
  },
@@ -883,10 +1188,11 @@ function promptMessages(name, args = {}) {
883
1188
  `Fetch page=${page}, limit=${limit} with securenow_human_actions_list, select row ${rowNumber}, then call securenow_notifications_get and securenow_human_action_report for that notificationId and IP.`,
884
1189
  'Read the AI report, finalDecision, DAG steps, findings, proofs, metadata paths/user agents/status codes, and trace IDs.',
885
1190
  'Open trace evidence with securenow_traces_show and correlated logs with securenow_logs_for_trace when trace IDs are available.',
886
- 'Return one of only two outcomes: Block IP or False Positive. If evidence is ambiguous, stop and explain what is missing.',
1191
+ 'Return one clear outcome: Block IP, False Positive, Rule Tuning Needed, or Ambiguous. If evidence is ambiguous, stop and explain what is missing.',
887
1192
  confirmWrites
888
1193
  ? 'The user requested execution. If evidence supports the decision, call securenow_human_action_block or securenow_human_action_false_positive with confirm:true and a precise reason.'
889
1194
  : 'Do not execute write tools yet. Prepare the recommended decision and exact tool call the user can approve.',
1195
+ 'If many IPs share the same benign path/status/user-agent pattern, recommend tightening the alert rule with a precise guard instead of reviewing each IP as malicious.',
890
1196
  'False positives must be narrow: app + alert rule + path + method/status/user-agent/body evidence where possible. Never globally trust an IP by default.',
891
1197
  ].join('\n'),
892
1198
  },
@@ -906,12 +1212,12 @@ function promptMessages(name, args = {}) {
906
1212
  'Work my SecureNow Requires Human queue like a senior security analyst using the MCP tools.',
907
1213
  `Review up to ${limit} row(s), most urgent first.${args.search ? ` Search filter: ${args.search}.` : ''}`,
908
1214
  'Start with securenow_human_actions_list. For each row, call securenow_notifications_get and securenow_human_action_report, inspect the AI report/DAG/proofs/trace IDs, and fetch trace/log evidence where useful.',
909
- 'For each row choose exactly one outcome: Block IP, False Positive, or Skip because evidence is insufficient. Explain skipped rows.',
1215
+ 'For each row choose exactly one outcome: Block IP, False Positive, Rule Tuning Needed, or Skip because evidence is insufficient. Explain skipped rows.',
910
1216
  confirmWrites
911
1217
  ? 'The user requested execution. For supported decisions, call the correct write tool with confirm:true and a precise reason, then continue.'
912
1218
  : 'Do not execute write tools yet. Produce a row-by-row action plan and exact MCP write calls for user approval.',
913
- 'For block decisions, use securenow_human_action_block. For false positives, use securenow_human_action_false_positive with restrictive conditions. Avoid broad/global trust.',
914
- 'End with counts: handled, proposed block, proposed false positive, skipped, still waiting.',
1219
+ 'For block decisions, use securenow_human_action_block. For false positives, use securenow_human_action_false_positive with restrictive conditions. For case-level tune_rule/create_exclusion rows, inspect securenow_notifications_get and then use securenow_human_case_action_update only when the action is safe to approve/reject.',
1220
+ 'End with counts: handled, proposed block, proposed false positive, rule tuning needed, skipped, still waiting.',
915
1221
  ].join('\n'),
916
1222
  },
917
1223
  },
@@ -991,6 +1297,12 @@ function buildApiRequest(tool, rawArgs = {}) {
991
1297
  if (tool.reasonAsNote && !body.note && args.reason) {
992
1298
  body.note = args.reason;
993
1299
  }
1300
+ if (tool.reasonInResult && args.reason) {
1301
+ body.result = {
1302
+ ...(body.result && typeof body.result === 'object' ? body.result : {}),
1303
+ reason: args.reason,
1304
+ };
1305
+ }
994
1306
 
995
1307
  return {
996
1308
  method: tool.method,