securenow 7.6.9 → 7.7.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/NPM_README.md +12 -1
- package/SKILL-CLI.md +29 -16
- package/cli/automation.js +275 -0
- package/cli/firewall.js +29 -12
- package/cli/human.js +96 -2
- package/cli/security.js +171 -42
- package/cli.js +71 -28
- package/mcp/catalog.js +327 -15
- package/nextjs.js +22 -23
- package/nuxt-server-plugin.mjs +13 -8
- package/package.json +1 -1
- package/resolve-ip.js +135 -60
- package/tracing.js +25 -4
package/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({
|
|
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({
|
|
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({
|
|
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
|
-
|
|
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
|
|
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.
|
|
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,
|
package/nextjs.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
const { randomUUID } = require('crypto');
|
|
29
29
|
const appConfig = require('./app-config');
|
|
30
|
+
const { resolveClientIpWithDetails } = require('./resolve-ip');
|
|
30
31
|
const otelResources = require('@opentelemetry/resources');
|
|
31
32
|
|
|
32
33
|
const env = appConfig.env;
|
|
@@ -203,26 +204,11 @@ function registerSecureNow(options = {}) {
|
|
|
203
204
|
const headers = request.headers || {};
|
|
204
205
|
|
|
205
206
|
// ======== IP ADDRESS CAPTURE ========
|
|
206
|
-
|
|
207
|
-
const forwardedFor =
|
|
208
|
-
const realIp =
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
const socketIp = request.socket?.remoteAddress;
|
|
212
|
-
|
|
213
|
-
const PRIVATE_RE = /^(127\.|::1$|::ffff:127\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|f[cd][0-9a-f]{2}:)/;
|
|
214
|
-
const isProxied = socketIp && PRIVATE_RE.test(socketIp);
|
|
215
|
-
let primaryIp = socketIp || 'unknown';
|
|
216
|
-
if (isProxied) {
|
|
217
|
-
if (forwardedFor) {
|
|
218
|
-
const chain = forwardedFor.split(',').map(s => s.trim()).filter(Boolean);
|
|
219
|
-
for (let i = chain.length - 1; i >= 0; i--) {
|
|
220
|
-
if (!PRIVATE_RE.test(chain[i])) { primaryIp = chain[i]; break; }
|
|
221
|
-
}
|
|
222
|
-
} else {
|
|
223
|
-
primaryIp = realIp || cfConnectingIp || clientIp || primaryIp;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
207
|
+
const ipDetails = resolveClientIpWithDetails(request);
|
|
208
|
+
const forwardedFor = ipDetails.forwardedFor;
|
|
209
|
+
const realIp = ipDetails.realIp;
|
|
210
|
+
const socketIp = ipDetails.socketIp;
|
|
211
|
+
const primaryIp = ipDetails.ip || 'unknown';
|
|
226
212
|
|
|
227
213
|
// ======== PROTOCOL & CONNECTION ========
|
|
228
214
|
const scheme = headers['x-forwarded-proto'] ||
|
|
@@ -249,9 +235,16 @@ function registerSecureNow(options = {}) {
|
|
|
249
235
|
const attributes = {
|
|
250
236
|
// IP & Network
|
|
251
237
|
'http.client_ip': primaryIp,
|
|
238
|
+
'http.client_ip.source': ipDetails.source,
|
|
252
239
|
'http.forwarded_for': forwardedFor || '',
|
|
253
240
|
'http.real_ip': realIp || '',
|
|
254
241
|
'http.socket_ip': socketIp || '',
|
|
242
|
+
'http.proxy.trusted': String(!!ipDetails.trustedProxy),
|
|
243
|
+
'http.request.header.x_forwarded_for': forwardedFor || '',
|
|
244
|
+
'http.request.header.x_real_ip': realIp || '',
|
|
245
|
+
'http.request.header.cf_connecting_ip': ipDetails.cfConnectingIp || '',
|
|
246
|
+
'http.request.header.true_client_ip': ipDetails.trueClientIp || '',
|
|
247
|
+
'http.request.header.x_client_ip': ipDetails.clientIp || '',
|
|
255
248
|
|
|
256
249
|
// Protocol & Host
|
|
257
250
|
'http.scheme': scheme,
|
|
@@ -327,7 +320,7 @@ function registerSecureNow(options = {}) {
|
|
|
327
320
|
if (env('NODE_ENV') === 'development' || env('OTEL_LOG_LEVEL') === 'debug') {
|
|
328
321
|
console.log('[securenow] 📡 Captured IP: %s (from: %s)',
|
|
329
322
|
primaryIp,
|
|
330
|
-
|
|
323
|
+
ipDetails.source || 'unknown'
|
|
331
324
|
);
|
|
332
325
|
}
|
|
333
326
|
|
|
@@ -520,7 +513,8 @@ function registerSecureNow(options = {}) {
|
|
|
520
513
|
const reqSpanCtx = otelTrace.getSpanContext(reqCtx);
|
|
521
514
|
const duration = Date.now() - start;
|
|
522
515
|
const status = res.statusCode;
|
|
523
|
-
const
|
|
516
|
+
const ipDetails = resolveClientIpWithDetails(req);
|
|
517
|
+
const ip = ipDetails.ip || '-';
|
|
524
518
|
const ua = req.headers['user-agent'] || '-';
|
|
525
519
|
const body = `${method} ${url} ${status} ${duration}ms ip=${ip} ua=${ua}`;
|
|
526
520
|
const severity = status >= 500 ? SeverityNumber.ERROR : status >= 400 ? SeverityNumber.WARN : SeverityNumber.INFO;
|
|
@@ -536,7 +530,12 @@ function registerSecureNow(options = {}) {
|
|
|
536
530
|
'http.url': url,
|
|
537
531
|
'http.status_code': status,
|
|
538
532
|
'http.duration_ms': duration,
|
|
539
|
-
'http.client_ip':
|
|
533
|
+
'http.client_ip': ip,
|
|
534
|
+
'http.client_ip.source': ipDetails.source || 'unknown',
|
|
535
|
+
'http.socket_ip': ipDetails.socketIp || '',
|
|
536
|
+
'http.forwarded_for': ipDetails.forwardedFor || '',
|
|
537
|
+
'http.real_ip': ipDetails.realIp || '',
|
|
538
|
+
'http.proxy.trusted': String(!!ipDetails.trustedProxy),
|
|
540
539
|
'http.user_agent': ua,
|
|
541
540
|
},
|
|
542
541
|
...(reqSpanCtx && { context: reqCtx }),
|
package/nuxt-server-plugin.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
22
22
|
|
|
23
23
|
const nodeRequire = createRequire(import.meta.url);
|
|
24
24
|
const appConfig = nodeRequire('./app-config');
|
|
25
|
+
const { resolveClientIpWithDetails } = nodeRequire('./resolve-ip');
|
|
25
26
|
|
|
26
27
|
// ── Helpers ──
|
|
27
28
|
|
|
@@ -137,17 +138,21 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
137
138
|
requestHook: (span, request) => {
|
|
138
139
|
try {
|
|
139
140
|
const hdrs = request.headers || {};
|
|
140
|
-
const
|
|
141
|
-
const clientIp =
|
|
142
|
-
(fwd ? String(fwd).split(',')[0].trim() : null) ||
|
|
143
|
-
hdrs['x-real-ip'] ||
|
|
144
|
-
hdrs['cf-connecting-ip'] ||
|
|
145
|
-
hdrs['x-client-ip'] ||
|
|
146
|
-
request.socket?.remoteAddress ||
|
|
147
|
-
'unknown';
|
|
141
|
+
const ipDetails = resolveClientIpWithDetails(request);
|
|
142
|
+
const clientIp = ipDetails.ip || 'unknown';
|
|
148
143
|
|
|
149
144
|
span.setAttributes({
|
|
150
145
|
'http.client_ip': clientIp,
|
|
146
|
+
'http.client_ip.source': ipDetails.source,
|
|
147
|
+
'http.socket_ip': ipDetails.socketIp || '',
|
|
148
|
+
'http.forwarded_for': ipDetails.forwardedFor || '',
|
|
149
|
+
'http.real_ip': ipDetails.realIp || '',
|
|
150
|
+
'http.proxy.trusted': String(!!ipDetails.trustedProxy),
|
|
151
|
+
'http.request.header.x_forwarded_for': ipDetails.forwardedFor || '',
|
|
152
|
+
'http.request.header.x_real_ip': ipDetails.realIp || '',
|
|
153
|
+
'http.request.header.cf_connecting_ip': ipDetails.cfConnectingIp || '',
|
|
154
|
+
'http.request.header.true_client_ip': ipDetails.trueClientIp || '',
|
|
155
|
+
'http.request.header.x_client_ip': ipDetails.clientIp || '',
|
|
151
156
|
'http.user_agent': hdrs['user-agent'] || '',
|
|
152
157
|
'http.host': hdrs['x-forwarded-host'] || hdrs['host'] || '',
|
|
153
158
|
'http.scheme':
|
package/package.json
CHANGED