securenow 7.7.16 → 8.0.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/mcp/catalog.js CHANGED
@@ -10,14 +10,15 @@ const MARKDOWN = 'text/markdown';
10
10
  const UNIVERSAL_SECURENOW_SETUP_PROMPT = `You are working in an existing JavaScript or TypeScript app. Set up SecureNow end-to-end for the framework/runtime already used by this repo. Treat this as a real onboarding, not just a package install.
11
11
 
12
12
  Primary goals:
13
- - Use the latest published SecureNow npm package. Require securenow@7.5.1 or newer.
13
+ - Use the latest published SecureNow npm package. Require securenow@7.8.0 or newer for split admin/runtime credentials.
14
14
  - By default, enable tracing, logs, POST request body capture, multipart metadata capture, and the SecureNow firewall.
15
15
  - If I explicitly ask for firewall-only mode, keep the same install/login/verification gates, but use firewall-only preload and do not add tracing, logging, or OTel instrumentation.
16
- - The firewall must protect the selected SecureNow app, use SecureNow's own blocklist/allowlist/IPDB data, and respect that app's SecureNow IPDB confidence threshold. Do not add custom IP reputation providers or custom auto-blocking.
16
+ - The firewall must protect the selected SecureNow app, use SecureNow's own blocklist/allowlist/IPDB data, and respect that app's SecureNow AI IPDB confidence threshold. Do not add custom IP reputation providers or custom auto-blocking.
17
+ - Do not confuse IP Allowlist with Trusted IPs. IP Allowlist is restrictive deny-by-default: when any allowlist entry exists for an app/environment, only listed IPs can reach it and all other IPs are blocked. Use Trusted IPs for known-safe monitors, office/VPN traffic, or false-positive suppression. Only use allowlist after explicit human approval to lock the app/environment to known IPs.
17
18
 
18
19
  Safety rules:
19
- - Do not print full API keys, JWTs, tokens, or .securenow/credentials.json. Mask secrets.
20
- - Do not commit secrets. Ignore only local SecureNow credential files (.securenow/credentials.json and .securenow/credentials.*.json); keep the .securenow/ directory itself trackable for repo-owned docs/templates.
20
+ - Do not print full API keys, JWTs, tokens, or local SecureNow credential files (.securenow/admin.json, .securenow/runtime.json, legacy .securenow/credentials.json, or .securenow/credentials.*.json). Mask secrets.
21
+ - Do not commit secrets. Ignore only local SecureNow credential files (.securenow/admin.json, .securenow/runtime.json, .securenow/credentials.json, and .securenow/credentials.*.json); keep the .securenow/ directory itself trackable for repo-owned docs/templates.
21
22
  - Do not manually browse to a SecureNow auth URL. Always start auth with npx securenow login so the CLI generates the required callback and state.
22
23
  - If the browser says "Missing callback parameter", you opened the wrong URL: rerun npx securenow login from the project root.
23
24
  - Do not skip login, app selection, firewall connection, or verification unless I explicitly say to.
@@ -31,37 +32,39 @@ Runbook:
31
32
  2. Install or upgrade SecureNow with the detected package manager, using securenow@latest. Verify the actual installed version with:
32
33
  node -p "require('./node_modules/securenow/package.json').version"
33
34
  npx securenow version
34
- Stop and fix the install if either is below 7.5.1 or npx still resolves an older local package.
35
+ Stop and fix the install if either is below 7.8.0 or npx still resolves an older local package.
35
36
  3. Read the installed package surface before editing files: node_modules/securenow/package.json, README/NPM_README, SKILL-API, SKILL-CLI, npx securenow help, and relevant subcommand help for login/init/firewall/doctor/env/test-span/log/mcp.
36
- 4. Mandatory auth gate:
37
- - Run npx securenow whoami from the project root.
38
- - If not logged in, run npx securenow login from the project root and wait for the browser flow.
39
- - After the CLI exits, rerun npx securenow whoami.
40
- - Do not proceed to app edits or verification until whoami succeeds.
37
+ 4. Mandatory auth/runtime gate:
38
+ - Run npx securenow whoami from the project root.
39
+ - If admin auth is missing, run npx securenow admin login from the project root and wait for the browser flow.
40
+ - If runtime app config is missing, run npx securenow app connect from the project root and wait for the browser flow.
41
+ - After the CLI exits, rerun npx securenow whoami.
42
+ - Do not proceed to app edits or verification until whoami shows the required lane(s). SDK setup needs runtime app config; admin/global MCP operations need admin auth.
41
43
  5. Validate project-local credentials without exposing secrets:
42
- - Confirm .securenow/credentials.json exists.
43
- - Confirm it has SecureNow's default config/explanations block.
44
- - Confirm it has an app key/name/instance and a firewall API key after login/app selection.
45
- - Confirm .securenow/credentials.json and any .securenow/credentials.*.json runtime files are ignored by git, without ignoring the entire .securenow/ directory.
46
- 6. Run npx securenow init. If it fails with ui.header is not a function or another CLI bug, upgrade to securenow@latest, verify >=7.5.1, and retry. Do not silently ignore init failures.
44
+ - Confirm .securenow/runtime.json exists for SDK runtime setup, or legacy .securenow/credentials.json exists for old installs.
45
+ - Confirm the runtime file has SecureNow's default config/explanations block.
46
+ - Confirm the runtime file has an app key/name/instance and a firewall API key after app selection.
47
+ - Confirm .securenow/admin.json exists only when admin CLI/MCP auth is needed.
48
+ - Confirm .securenow/admin.json, .securenow/runtime.json, legacy .securenow/credentials.json, and any .securenow/credentials.*.json runtime files are ignored by git, without ignoring the entire .securenow/ directory.
49
+ 6. Run npx securenow init. If it fails with ui.header is not a function or another CLI bug, upgrade to securenow@latest, verify >=7.8.0, and retry. Do not silently ignore init failures.
47
50
  7. Configure the least invasive framework-specific integration:
48
51
  - Next.js: preserve instrumentation.js/ts. Register securenow/nextjs only when NEXT_RUNTIME is nodejs. In ESM files, use createRequire before require("securenow/nextjs"). Include require("securenow/nextjs-auto-capture") for body capture. For Next 15+, add securenow to serverExternalPackages. For older Next.js, use experimental.serverComponentsExternalPackages. Preserve proxy.js/middleware.js.
49
52
  - Nuxt/Nitro: use the documented securenow/nuxt module or Nitro server plugin.
50
53
  - Express/Fastify/NestJS/Koa/Hapi/Hono/raw Node: preload securenow/register through existing scripts, NODE_OPTIONS, PM2 node_args, Docker CMD, or the process manager already used.
51
54
  - Firewall-only: preload securenow/firewall-only or use the documented securenow run --firewall-only command. Do not add OTel/tracing/logging in this mode.
52
55
  - Vite/browser-only: use only documented browser integration and state that server firewall protection requires a server runtime.
53
- 8. Do not create or require a .env file for local development or production. The SDK reads defaults from .securenow/credentials.json:
56
+ 8. Do not create or require a .env file for local development or production. The SDK reads defaults from .securenow/runtime.json, with legacy .securenow/credentials.json and generated runtime credential files still supported:
54
57
  - config.logging.enabled: true
55
58
  - config.capture.body: true
56
59
  - config.capture.multipart: true
57
60
  - config.firewall.enabled: true
58
61
  - config.firewall.failMode: "open"
59
62
  - config.capture.maxBodySize: 10240
60
- For production, run npx securenow credentials runtime --env production, store the resulting JSON as a deployment secret file, and mount/copy it to <app-root>/.securenow/credentials.json. Do not recommend env vars unless the user explicitly asks for legacy fallbacks.
63
+ For production, run npx securenow credentials runtime --env production, store the resulting JSON as a deployment secret file, and mount/copy it to <app-root>/.securenow/credentials.json or <app-root>/.securenow/credentials.production.json. Do not recommend env vars unless the user explicitly asks for legacy fallbacks.
61
64
  Local credentials should use config.runtime.deploymentEnvironment="local". Production runtime credentials should use "production". The app key stays the same; traces, logs, firewall status, forensics, and CLI/MCP queries are scoped by environment.
62
65
  9. Verify firewall and threshold:
63
66
  - Run npx securenow firewall apps and npx securenow firewall status.
64
- - Confirm the selected app is present, firewallEnabled is true, and the SecureNow IPDB confidence threshold is visible.
67
+ - Confirm the selected app is present, firewallEnabled is true, and the SecureNow AI IPDB confidence threshold is visible.
65
68
  - If firewallEnabled is false, run the documented per-app enable command, for example npx securenow firewall enable --app <appKey>, then verify again.
66
69
  10. End-to-end proof:
67
70
  - Run npx securenow doctor.
@@ -186,7 +189,7 @@ const TOOLS = [
186
189
  {
187
190
  name: 'securenow_auth_status',
188
191
  title: 'SecureNow Auth Status',
189
- description: 'Show local SecureNow credential source, selected app, and masked firewall key.',
192
+ description: 'Show local SecureNow credential source, selected app, and masked runtime API key.',
190
193
  scope: null,
191
194
  localOnly: true,
192
195
  readOnly: true,
@@ -235,7 +238,7 @@ const TOOLS = [
235
238
  {
236
239
  name: 'securenow_firewall_apps',
237
240
  title: 'List Firewall Apps',
238
- description: 'List applications with firewall toggle and SecureNow IPDB threshold state.',
241
+ description: 'List applications with firewall toggle and SecureNow AI IPDB threshold state.',
239
242
  scope: 'applications:read',
240
243
  readOnly: true,
241
244
  method: 'GET',
@@ -295,8 +298,8 @@ const TOOLS = [
295
298
  },
296
299
  {
297
300
  name: 'securenow_firewall_set_threshold',
298
- title: 'Set SecureNow IPDB Threshold',
299
- description: 'Set the per-app SecureNow IPDB confidence threshold. Write action; requires confirmation.',
301
+ title: 'Set SecureNow AI IPDB Threshold',
302
+ description: 'Set the per-app SecureNow AI IPDB confidence threshold. Write action; requires confirmation.',
300
303
  scope: 'applications:write',
301
304
  readOnly: false,
302
305
  confirm: true,
@@ -306,7 +309,7 @@ const TOOLS = [
306
309
  bodyFields: ['confidenceMinimum', 'environment'],
307
310
  inputSchema: objectSchema({
308
311
  appKey: string('Application key UUID.'),
309
- confidenceMinimum: number('Minimum SecureNow IPDB confidence score.', { minimum: 0, maximum: 100 }),
312
+ confidenceMinimum: number('Minimum SecureNow AI IPDB confidence score.', { minimum: 0, maximum: 100 }),
310
313
  ...environmentInput,
311
314
  ...confirmSchema,
312
315
  }, ['appKey', 'confidenceMinimum', 'confirm', 'reason']),
@@ -472,6 +475,190 @@ const TOOLS = [
472
475
  search: string('Optional search over IP, rule, path, or verdict.'),
473
476
  }),
474
477
  },
478
+ {
479
+ name: 'securenow_human_actions_grouped_list',
480
+ title: 'List Grouped Human Actions',
481
+ description: 'List Requires Human work as root-cause groups with batch-safety preview instead of one row per IP/action.',
482
+ scope: 'notifications:read',
483
+ readOnly: true,
484
+ method: 'GET',
485
+ endpoint: '/notifications/approval-task-groups',
486
+ queryFields: ['limit', 'page', 'search'],
487
+ inputSchema: objectSchema({
488
+ ...pagingInput,
489
+ search: string('Optional search over IP, rule, path, action, or verdict.'),
490
+ }),
491
+ },
492
+ {
493
+ name: 'securenow_alert_review_runs_list',
494
+ title: 'List Alert Review Root Causes',
495
+ description: 'List pending alert-review runs as root-cause groups with batch-action preview, rather than row-by-row noisy alert triage.',
496
+ scope: 'notifications:read',
497
+ readOnly: true,
498
+ method: 'GET',
499
+ endpoint: '/notifications/alert-review-runs',
500
+ queryFields: ['limit', 'page', 'search', 'rootCauseKey', 'groupKey', 'includeRuns', 'maxScan'],
501
+ inputSchema: objectSchema({
502
+ ...pagingInput,
503
+ search: string('Optional search over rule, route, IP, evidence, or proposed guard.'),
504
+ rootCauseKey: string('Optional root-cause group key to filter to one group.'),
505
+ groupKey: string('Compatibility alias for rootCauseKey.'),
506
+ includeRuns: boolean('Whether to include compact matching run rows in the response.'),
507
+ maxScan: number('Maximum pending-review rows to scan before grouping. Defaults to 500, capped at 1000.', { minimum: 1, maximum: 1000 }),
508
+ }),
509
+ },
510
+ {
511
+ name: 'securenow_alert_review_runs_count',
512
+ title: 'Count Alert Review Runs',
513
+ description: 'Count pending alert-review rows and the root-cause groups they collapse into.',
514
+ scope: 'notifications:read',
515
+ readOnly: true,
516
+ method: 'GET',
517
+ endpoint: '/notifications/alert-review-runs/count',
518
+ queryFields: ['search', 'maxScan'],
519
+ inputSchema: objectSchema({
520
+ search: string('Optional search over rule, route, IP, evidence, or proposed guard.'),
521
+ maxScan: number('Maximum pending-review rows to scan before grouping. Defaults to 500, capped at 1000.', { minimum: 1, maximum: 1000 }),
522
+ }),
523
+ },
524
+ {
525
+ name: 'securenow_alert_review_run_get',
526
+ title: 'Get Alert Review Run',
527
+ description: 'Fetch one pending-review alert run with its root-cause key, evidence-quality score, and similar open siblings.',
528
+ scope: 'notifications:read',
529
+ readOnly: true,
530
+ method: 'GET',
531
+ endpoint: '/notifications/alert-review-runs/:id',
532
+ pathParams: ['id'],
533
+ queryFields: ['maxScan'],
534
+ inputSchema: objectSchema({
535
+ id: string('Notification id for the alert review run.'),
536
+ maxScan: number('Maximum pending-review rows to scan for similar siblings. Defaults to 500, capped at 1000.', { minimum: 1, maximum: 1000 }),
537
+ }, ['id']),
538
+ },
539
+ {
540
+ name: 'securenow_alert_review_run_resolve',
541
+ title: 'Resolve Alert Review Run',
542
+ description: 'Close one pending alert-review row with an auditable no-write decision report.',
543
+ scope: 'notifications:write',
544
+ readOnly: false,
545
+ confirm: true,
546
+ method: 'POST',
547
+ endpoint: '/notifications/alert-review-runs/:id/resolve',
548
+ pathParams: ['id'],
549
+ fixedBody: { reportSource: 'mcp' },
550
+ bodyFields: ['confirm', 'reason', 'decisionReport', 'decisionSummary', 'outcome', 'evidence', 'reviewedHistory', 'traceIds', 'paths', 'methods', 'statusCodes', 'userAgents', 'missingProof', 'recommendations'],
551
+ inputSchema: objectSchema({
552
+ id: string('Notification id for the alert review run.'),
553
+ ...decisionReportInput,
554
+ ...confirmSchema,
555
+ }, ['id', 'confirm', 'reason']),
556
+ },
557
+ {
558
+ name: 'securenow_alert_review_run_dismiss',
559
+ title: 'Dismiss Alert Review Run',
560
+ description: 'Dismiss one pending alert-review row with an audit report and no enforcement write.',
561
+ scope: 'notifications:write',
562
+ readOnly: false,
563
+ confirm: true,
564
+ method: 'POST',
565
+ endpoint: '/notifications/alert-review-runs/:id/dismiss',
566
+ pathParams: ['id'],
567
+ fixedBody: { reportSource: 'mcp' },
568
+ bodyFields: ['confirm', 'reason', 'decisionReport', 'decisionSummary', 'outcome', 'evidence', 'reviewedHistory', 'traceIds', 'paths', 'methods', 'statusCodes', 'userAgents', 'missingProof', 'recommendations'],
569
+ inputSchema: objectSchema({
570
+ id: string('Notification id for the alert review run.'),
571
+ ...decisionReportInput,
572
+ ...confirmSchema,
573
+ }, ['id', 'confirm', 'reason']),
574
+ },
575
+ {
576
+ name: 'securenow_alert_review_run_keep_alive',
577
+ title: 'Keep Alert Rule Active',
578
+ description: 'Mark the noisy/paused alert rule as reviewed, keep it active, and close the pending alert-review row with an audit report.',
579
+ scope: 'notifications:write',
580
+ readOnly: false,
581
+ confirm: true,
582
+ method: 'POST',
583
+ endpoint: '/notifications/alert-review-runs/:id/keep-alive',
584
+ pathParams: ['id'],
585
+ fixedBody: { reportSource: 'mcp' },
586
+ bodyFields: ['confirm', 'reason', 'reviewNote', 'decisionReport', 'decisionSummary', 'evidence', 'reviewedHistory', 'traceIds', 'paths', 'methods', 'statusCodes', 'userAgents', 'missingProof', 'recommendations'],
587
+ inputSchema: objectSchema({
588
+ id: string('Notification id for the alert review run.'),
589
+ reviewNote: string('Optional rule-review note. Defaults to reason.'),
590
+ ...decisionReportInput,
591
+ ...confirmSchema,
592
+ }, ['id', 'confirm', 'reason']),
593
+ },
594
+ {
595
+ name: 'securenow_alert_review_run_apply_group_decision',
596
+ title: 'Apply Alert Review Group Decision',
597
+ description: 'Apply one verified no-write decision report to every currently matching alert-review row in a root-cause group, leaving non-matching rows open.',
598
+ scope: 'notifications:write',
599
+ readOnly: false,
600
+ confirm: true,
601
+ method: 'POST',
602
+ endpoint: '/notifications/alert-review-runs/apply-group-decision',
603
+ fixedBody: { reportSource: 'mcp' },
604
+ bodyFields: ['confirm', 'reason', 'rootCauseKey', 'groupKey', 'prototypeRunId', 'notificationIds', 'status', 'outcome', 'allowMixed', 'previewOnly', 'maxRows', 'maxScan', 'search', 'decisionReport', 'decisionSummary', 'evidence', 'reviewedHistory', 'traceIds', 'paths', 'methods', 'statusCodes', 'userAgents', 'missingProof', 'recommendations'],
605
+ inputSchema: objectSchema({
606
+ rootCauseKey: string('Root-cause group key from securenow_alert_review_runs_list.'),
607
+ groupKey: string('Compatibility alias for rootCauseKey.'),
608
+ prototypeRunId: string('Optional notification id whose root-cause key should be used.'),
609
+ notificationIds: arrayOfStrings('Optional subset of eligible notification ids to close. Leave empty to apply to every eligible row in the group.'),
610
+ status: string('Resolution status: resolved or dismissed. Defaults to resolved.'),
611
+ allowMixed: boolean('Allow a mixed decision/attribution group only after every row was manually reviewed. Defaults to false.'),
612
+ previewOnly: boolean('When true, returns the eligible rows without writing.'),
613
+ maxRows: number('Maximum rows to close in this batch. Defaults to 100, capped at 250.', { minimum: 1, maximum: 250 }),
614
+ maxScan: number('Maximum pending-review rows to scan before grouping. Defaults to 500, capped at 1000.', { minimum: 1, maximum: 1000 }),
615
+ search: string('Optional search filter applied before grouping.'),
616
+ ...decisionReportInput,
617
+ ...confirmSchema,
618
+ }, ['confirm', 'reason']),
619
+ },
620
+ {
621
+ name: 'securenow_alert_review_apply_group_decision',
622
+ title: 'Apply Alert Review Group Decision',
623
+ description: 'Compatibility alias for securenow_alert_review_run_apply_group_decision.',
624
+ scope: 'notifications:write',
625
+ readOnly: false,
626
+ confirm: true,
627
+ method: 'POST',
628
+ endpoint: '/notifications/alert-review-runs/apply-group-decision',
629
+ fixedBody: { reportSource: 'mcp' },
630
+ bodyFields: ['confirm', 'reason', 'rootCauseKey', 'groupKey', 'prototypeRunId', 'notificationIds', 'status', 'outcome', 'allowMixed', 'previewOnly', 'maxRows', 'maxScan', 'search', 'decisionReport', 'decisionSummary', 'evidence', 'reviewedHistory', 'traceIds', 'paths', 'methods', 'statusCodes', 'userAgents', 'missingProof', 'recommendations'],
631
+ inputSchema: objectSchema({
632
+ rootCauseKey: string('Root-cause group key from securenow_alert_review_runs_list.'),
633
+ groupKey: string('Compatibility alias for rootCauseKey.'),
634
+ prototypeRunId: string('Optional notification id whose root-cause key should be used.'),
635
+ notificationIds: arrayOfStrings('Optional subset of eligible notification ids to close. Leave empty to apply to every eligible row in the group.'),
636
+ status: string('Resolution status: resolved or dismissed. Defaults to resolved.'),
637
+ allowMixed: boolean('Allow a mixed decision/attribution group only after every row was manually reviewed. Defaults to false.'),
638
+ previewOnly: boolean('When true, returns the eligible rows without writing.'),
639
+ maxRows: number('Maximum rows to close in this batch. Defaults to 100, capped at 250.', { minimum: 1, maximum: 250 }),
640
+ maxScan: number('Maximum pending-review rows to scan before grouping. Defaults to 500, capped at 1000.', { minimum: 1, maximum: 1000 }),
641
+ search: string('Optional search filter applied before grouping.'),
642
+ ...decisionReportInput,
643
+ ...confirmSchema,
644
+ }, ['confirm', 'reason']),
645
+ },
646
+ {
647
+ name: 'securenow_alert_review_group_similar',
648
+ title: 'List Similar Alert Review Runs',
649
+ description: 'Fetch the current rows in one alert-review root-cause group for preview before a batch decision.',
650
+ scope: 'notifications:read',
651
+ readOnly: true,
652
+ method: 'GET',
653
+ endpoint: '/notifications/alert-review-runs',
654
+ queryFields: ['rootCauseKey', 'groupKey', 'includeRuns', 'maxScan'],
655
+ fixedQuery: { includeRuns: true },
656
+ inputSchema: objectSchema({
657
+ rootCauseKey: string('Root-cause group key from securenow_alert_review_runs_list.'),
658
+ groupKey: string('Compatibility alias for rootCauseKey.'),
659
+ maxScan: number('Maximum pending-review rows to scan before grouping. Defaults to 500, capped at 1000.', { minimum: 1, maximum: 1000 }),
660
+ }),
661
+ },
475
662
  {
476
663
  name: 'securenow_human_action_report',
477
664
  title: 'Get Human Action Report',
@@ -529,6 +716,25 @@ const TOOLS = [
529
716
  ...confirmSchema,
530
717
  }, ['notificationId', 'ip', 'confirm', 'reason']),
531
718
  },
719
+ {
720
+ name: 'securenow_human_action_close_no_write',
721
+ title: 'Close Human Action Without Enforcement',
722
+ description: 'Close one Requires Human IP row with an auditable no-write decision report. Use for ambiguous, skipped, rule-tuning-needed, new-rule-needed, rate-limited, or already-handled rows.',
723
+ scope: 'notifications:write',
724
+ readOnly: false,
725
+ confirm: true,
726
+ method: 'POST',
727
+ endpoint: '/notifications/:notificationId/ips/:ip/close-no-write',
728
+ pathParams: ['notificationId', 'ip'],
729
+ fixedBody: { reportSource: 'mcp' },
730
+ bodyFields: ['reason', 'decisionReport', 'decisionSummary', 'outcome', 'evidence', 'reviewedHistory', 'traceIds', 'paths', 'methods', 'statusCodes', 'userAgents', 'missingProof', 'recommendations'],
731
+ inputSchema: objectSchema({
732
+ notificationId: string('Notification id from the human action row.'),
733
+ ip: string('IP address for the row to close.'),
734
+ ...decisionReportInput,
735
+ ...confirmSchema,
736
+ }, ['notificationId', 'ip', 'confirm', 'reason']),
737
+ },
532
738
  {
533
739
  name: 'securenow_human_action_false_positive',
534
740
  title: 'Mark Human Action False Positive',
@@ -802,6 +1008,69 @@ const TOOLS = [
802
1008
  ...confirmSchema,
803
1009
  }, ['id', 'confirm', 'reason']),
804
1010
  },
1011
+ {
1012
+ name: 'securenow_rate_limit_list',
1013
+ title: 'List Rate Limits',
1014
+ description: 'List rate-limit remediation rules with app/environment scope.',
1015
+ scope: 'rate_limits:read',
1016
+ readOnly: true,
1017
+ method: 'GET',
1018
+ endpoint: '/rate-limits',
1019
+ queryFields: ['status', 'search', 'page', 'limit', 'appKey', 'environment'],
1020
+ inputSchema: objectSchema({
1021
+ status: string('Rule status: active, disabled, removed, or all. Defaults to active.'),
1022
+ search: string('Optional IP/path/name search.'),
1023
+ ...pagingInput,
1024
+ appKey: string('Optional application key scope.'),
1025
+ ...environmentInput,
1026
+ }),
1027
+ },
1028
+ {
1029
+ name: 'securenow_rate_limit_get',
1030
+ title: 'Get Rate Limit',
1031
+ description: 'Fetch one rate-limit remediation rule.',
1032
+ scope: 'rate_limits:read',
1033
+ readOnly: true,
1034
+ method: 'GET',
1035
+ endpoint: '/rate-limits/:id',
1036
+ pathParams: ['id'],
1037
+ inputSchema: objectSchema({
1038
+ id: string('Rate-limit rule id.'),
1039
+ }, ['id']),
1040
+ },
1041
+ {
1042
+ name: 'securenow_rate_limit_test',
1043
+ title: 'Test Rate Limit Match',
1044
+ description: 'Check whether an IP/path/method would match an active rate-limit rule.',
1045
+ scope: 'rate_limits:read',
1046
+ readOnly: true,
1047
+ method: 'GET',
1048
+ endpoint: '/rate-limits/check',
1049
+ queryFields: ['ip', 'path', 'method', 'appKey', 'environment'],
1050
+ inputSchema: objectSchema({
1051
+ ip: string('IPv4 address to test.'),
1052
+ path: string('Request path to test. Defaults to /.'),
1053
+ method: string('HTTP method to test. Defaults to GET.'),
1054
+ appKey: string('Optional application key scope.'),
1055
+ ...environmentInput,
1056
+ }, ['ip']),
1057
+ },
1058
+ {
1059
+ name: 'securenow_rate_limit_remove',
1060
+ title: 'Remove Rate Limit',
1061
+ description: 'Remove a rate-limit remediation rule. Write action; requires confirmation.',
1062
+ scope: 'rate_limits:write',
1063
+ readOnly: false,
1064
+ confirm: true,
1065
+ method: 'DELETE',
1066
+ endpoint: '/rate-limits/:id',
1067
+ pathParams: ['id'],
1068
+ bodyFields: ['reason'],
1069
+ inputSchema: objectSchema({
1070
+ id: string('Rate-limit rule id.'),
1071
+ ...confirmSchema,
1072
+ }, ['id', 'confirm', 'reason']),
1073
+ },
805
1074
  {
806
1075
  name: 'securenow_rate_limit_parse',
807
1076
  title: 'Parse Rate Limit Text',
@@ -905,6 +1174,256 @@ const TOOLS = [
905
1174
  ...confirmSchema,
906
1175
  }, ['id', 'sqlQuery', 'confirm', 'reason']),
907
1176
  },
1177
+ {
1178
+ name: 'securenow_alert_rule_instant_update',
1179
+ title: 'Update Instant Alert Rule',
1180
+ description: 'Patch instant rule conditions/config with version/hash guards and before/after seeded tests. System rules require applyGlobally:true and admin permission. Write action; requires confirmation.',
1181
+ scope: 'alerts:write',
1182
+ readOnly: false,
1183
+ confirm: true,
1184
+ method: 'PUT',
1185
+ endpoint: '/alert-rules/:id/instant',
1186
+ pathParams: ['id'],
1187
+ bodyFields: [
1188
+ 'operations',
1189
+ 'conditions',
1190
+ 'enabled',
1191
+ 'source',
1192
+ 'matchMode',
1193
+ 'executionMode',
1194
+ 'severity',
1195
+ 'throttle',
1196
+ 'expectedRuleVersion',
1197
+ 'expectedCurrentInstantHash',
1198
+ 'expectedCurrentRuleHash',
1199
+ 'applyGlobally',
1200
+ 'dryRun',
1201
+ 'reactivatePausedCopies',
1202
+ 'fixtures',
1203
+ 'reviewNotificationId',
1204
+ 'reviewNote',
1205
+ 'reason',
1206
+ ],
1207
+ fixedBody: { auditSource: 'mcp' },
1208
+ inputSchema: objectSchema({
1209
+ id: string('Alert rule id. Fetch with securenow_alert_rule_get first to get ruleVersion and instantHash.'),
1210
+ operations: {
1211
+ type: 'array',
1212
+ items: { type: 'object', additionalProperties: true },
1213
+ description: 'Condition patch operations: add_condition, remove_condition, update_condition. Use zero-based index/conditionIndex for remove/update.',
1214
+ },
1215
+ conditions: {
1216
+ type: 'array',
1217
+ items: { type: 'object', additionalProperties: true },
1218
+ description: 'Optional full replacement instant conditions array. Use operations for smaller patches.',
1219
+ },
1220
+ enabled: boolean('Whether instant detection is enabled.'),
1221
+ source: string('Instant event source. Currently trace.'),
1222
+ matchMode: string('Condition match mode: all or any.'),
1223
+ executionMode: string('Rule execution mode: instant or hybrid when instant detection is enabled.'),
1224
+ severity: string('Optional severity override: critical, high, medium, or low.'),
1225
+ throttle: { type: 'object', additionalProperties: true, description: 'Throttle patch, for example { enabled:true, minutes:15 }.' },
1226
+ expectedRuleVersion: number('Required current reviewState.reviewVersion from securenow_alert_rule_get.', { minimum: 1 }),
1227
+ expectedCurrentInstantHash: string('Required SHA-256 hash of the current instant config from securenow_alert_rule_get.'),
1228
+ expectedCurrentRuleHash: string('Optional SHA-256 hash of the current instant/throttle/severity surface from securenow_alert_rule_get.'),
1229
+ applyGlobally: boolean('Required true for system rules. Updates every customer copy of the system rule.'),
1230
+ dryRun: boolean('When true, validates and runs before/after seeded tests without saving.'),
1231
+ reactivatePausedCopies: boolean('Whether to reactivate paused noisy copies after the instant rule is tuned.'),
1232
+ fixtures: {
1233
+ type: 'array',
1234
+ items: { type: 'object', additionalProperties: true },
1235
+ description: 'Optional seeded fixtures. Each may include kind attack/benign, label, path, method, requestBody, requestHeaders, and expectMatch.',
1236
+ },
1237
+ reviewNotificationId: string('Optional notification id tied to this review/tuning action.'),
1238
+ reviewNote: string('Optional review note. Defaults to reason.'),
1239
+ ...confirmSchema,
1240
+ }, ['id', 'expectedRuleVersion', 'expectedCurrentInstantHash', 'confirm', 'reason']),
1241
+ },
1242
+ {
1243
+ name: 'securenow_alert_rule_conditions_get',
1244
+ title: 'Get Alert Rule Conditions',
1245
+ description: 'Fetch instant rule conditions with rule version and hash guards for safe condition-level tuning.',
1246
+ scope: 'alerts:read',
1247
+ readOnly: true,
1248
+ method: 'GET',
1249
+ endpoint: '/alert-rules/:id/conditions',
1250
+ pathParams: ['id'],
1251
+ inputSchema: objectSchema({
1252
+ id: string('Alert rule id.'),
1253
+ }, ['id']),
1254
+ },
1255
+ {
1256
+ name: 'securenow_alert_rule_condition_update',
1257
+ title: 'Update Alert Rule Condition',
1258
+ description: 'Patch instant alert-rule conditions with version/hash guards and seeded before/after tests. Compatibility alias for condition-level tuning.',
1259
+ scope: 'alerts:write',
1260
+ readOnly: false,
1261
+ confirm: true,
1262
+ method: 'PUT',
1263
+ endpoint: '/alert-rules/:id/instant',
1264
+ pathParams: ['id'],
1265
+ bodyFields: [
1266
+ 'confirm',
1267
+ 'operations',
1268
+ 'conditions',
1269
+ 'enabled',
1270
+ 'source',
1271
+ 'matchMode',
1272
+ 'executionMode',
1273
+ 'expectedRuleVersion',
1274
+ 'expectedCurrentInstantHash',
1275
+ 'expectedCurrentRuleHash',
1276
+ 'applyGlobally',
1277
+ 'reactivatePausedCopies',
1278
+ 'fixtures',
1279
+ 'reviewNotificationId',
1280
+ 'reviewNote',
1281
+ 'reason',
1282
+ ],
1283
+ fixedBody: { auditSource: 'mcp' },
1284
+ inputSchema: objectSchema({
1285
+ id: string('Alert rule id. Fetch with securenow_alert_rule_conditions_get first to get ruleVersion and instantHash.'),
1286
+ operations: {
1287
+ type: 'array',
1288
+ items: { type: 'object', additionalProperties: true },
1289
+ description: 'Condition patch operations: add_condition, remove_condition, update_condition. Use zero-based index/conditionIndex for remove/update.',
1290
+ },
1291
+ conditions: {
1292
+ type: 'array',
1293
+ items: { type: 'object', additionalProperties: true },
1294
+ description: 'Optional full replacement instant conditions array. Use operations for smaller patches.',
1295
+ },
1296
+ enabled: boolean('Whether instant detection is enabled.'),
1297
+ source: string('Instant event source. Currently trace.'),
1298
+ matchMode: string('Condition match mode: all or any.'),
1299
+ executionMode: string('Rule execution mode: instant or hybrid when instant detection is enabled.'),
1300
+ expectedRuleVersion: number('Required current reviewState.reviewVersion from securenow_alert_rule_conditions_get.', { minimum: 1 }),
1301
+ expectedCurrentInstantHash: string('Required SHA-256 hash of the current instant config from securenow_alert_rule_conditions_get.'),
1302
+ expectedCurrentRuleHash: string('Optional SHA-256 hash of the current instant/throttle/severity surface from securenow_alert_rule_conditions_get.'),
1303
+ applyGlobally: boolean('Required true for system rules. Updates every customer copy of the system rule.'),
1304
+ reactivatePausedCopies: boolean('Whether to reactivate paused noisy copies after the condition is tuned.'),
1305
+ fixtures: {
1306
+ type: 'array',
1307
+ items: { type: 'object', additionalProperties: true },
1308
+ description: 'Optional seeded fixtures. Each may include kind attack/benign, label, path, method, requestBody, requestHeaders, and expectMatch.',
1309
+ },
1310
+ reviewNotificationId: string('Optional notification id tied to this review/tuning action.'),
1311
+ reviewNote: string('Optional review note. Defaults to reason.'),
1312
+ ...confirmSchema,
1313
+ }, ['id', 'expectedRuleVersion', 'expectedCurrentInstantHash', 'confirm', 'reason']),
1314
+ },
1315
+ {
1316
+ name: 'securenow_alert_rule_condition_candidate_test',
1317
+ title: 'Test Alert Rule Condition Candidate',
1318
+ description: 'Dry-run a condition-level instant-rule patch without saving it, including seeded benign/attack fixture checks.',
1319
+ scope: 'alerts:write',
1320
+ readOnly: false,
1321
+ confirm: true,
1322
+ method: 'PUT',
1323
+ endpoint: '/alert-rules/:id/instant',
1324
+ pathParams: ['id'],
1325
+ bodyFields: [
1326
+ 'confirm',
1327
+ 'operations',
1328
+ 'conditions',
1329
+ 'enabled',
1330
+ 'source',
1331
+ 'matchMode',
1332
+ 'executionMode',
1333
+ 'expectedRuleVersion',
1334
+ 'expectedCurrentInstantHash',
1335
+ 'expectedCurrentRuleHash',
1336
+ 'applyGlobally',
1337
+ 'fixtures',
1338
+ 'reviewNotificationId',
1339
+ 'reviewNote',
1340
+ 'reason',
1341
+ ],
1342
+ fixedBody: { auditSource: 'mcp', dryRun: true },
1343
+ inputSchema: objectSchema({
1344
+ id: string('Alert rule id. Fetch with securenow_alert_rule_conditions_get first to get ruleVersion and instantHash.'),
1345
+ operations: {
1346
+ type: 'array',
1347
+ items: { type: 'object', additionalProperties: true },
1348
+ description: 'Candidate condition operations to test.',
1349
+ },
1350
+ conditions: {
1351
+ type: 'array',
1352
+ items: { type: 'object', additionalProperties: true },
1353
+ description: 'Optional full replacement instant conditions array to test.',
1354
+ },
1355
+ enabled: boolean('Whether instant detection would be enabled.'),
1356
+ source: string('Instant event source. Currently trace.'),
1357
+ matchMode: string('Condition match mode: all or any.'),
1358
+ executionMode: string('Rule execution mode: instant or hybrid when instant detection is enabled.'),
1359
+ expectedRuleVersion: number('Required current reviewState.reviewVersion from securenow_alert_rule_conditions_get.', { minimum: 1 }),
1360
+ expectedCurrentInstantHash: string('Required SHA-256 hash of the current instant config from securenow_alert_rule_conditions_get.'),
1361
+ expectedCurrentRuleHash: string('Optional SHA-256 hash of the current instant/throttle/severity surface from securenow_alert_rule_conditions_get.'),
1362
+ applyGlobally: boolean('Required true for system rules, even for admin dry-runs.'),
1363
+ fixtures: {
1364
+ type: 'array',
1365
+ items: { type: 'object', additionalProperties: true },
1366
+ description: 'Optional seeded fixtures. Each may include kind attack/benign, label, path, method, requestBody, requestHeaders, and expectMatch.',
1367
+ },
1368
+ reviewNotificationId: string('Optional notification id tied to this review/tuning action.'),
1369
+ reviewNote: string('Optional review note. Defaults to reason.'),
1370
+ ...confirmSchema,
1371
+ }, ['id', 'expectedRuleVersion', 'expectedCurrentInstantHash', 'confirm', 'reason']),
1372
+ },
1373
+ {
1374
+ name: 'securenow_alert_rule_condition_diff',
1375
+ title: 'Diff Alert Rule Condition Candidate',
1376
+ description: 'Return the current instant conditions, candidate conditions, hashes, and before/after fixture behavior without saving.',
1377
+ scope: 'alerts:write',
1378
+ readOnly: false,
1379
+ confirm: true,
1380
+ method: 'PUT',
1381
+ endpoint: '/alert-rules/:id/instant',
1382
+ pathParams: ['id'],
1383
+ bodyFields: [
1384
+ 'confirm',
1385
+ 'operations',
1386
+ 'conditions',
1387
+ 'enabled',
1388
+ 'source',
1389
+ 'matchMode',
1390
+ 'executionMode',
1391
+ 'expectedRuleVersion',
1392
+ 'expectedCurrentInstantHash',
1393
+ 'expectedCurrentRuleHash',
1394
+ 'applyGlobally',
1395
+ 'fixtures',
1396
+ 'reason',
1397
+ ],
1398
+ fixedBody: { auditSource: 'mcp', dryRun: true },
1399
+ inputSchema: objectSchema({
1400
+ id: string('Alert rule id. Fetch with securenow_alert_rule_conditions_get first to get ruleVersion and instantHash.'),
1401
+ operations: {
1402
+ type: 'array',
1403
+ items: { type: 'object', additionalProperties: true },
1404
+ description: 'Candidate condition operations to diff.',
1405
+ },
1406
+ conditions: {
1407
+ type: 'array',
1408
+ items: { type: 'object', additionalProperties: true },
1409
+ description: 'Optional full replacement instant conditions array to diff.',
1410
+ },
1411
+ enabled: boolean('Whether instant detection would be enabled.'),
1412
+ source: string('Instant event source. Currently trace.'),
1413
+ matchMode: string('Condition match mode: all or any.'),
1414
+ executionMode: string('Rule execution mode: instant or hybrid when instant detection is enabled.'),
1415
+ expectedRuleVersion: number('Required current reviewState.reviewVersion from securenow_alert_rule_conditions_get.', { minimum: 1 }),
1416
+ expectedCurrentInstantHash: string('Required SHA-256 hash of the current instant config from securenow_alert_rule_conditions_get.'),
1417
+ expectedCurrentRuleHash: string('Optional SHA-256 hash of the current instant/throttle/severity surface from securenow_alert_rule_conditions_get.'),
1418
+ applyGlobally: boolean('Required true for system rules, even for admin dry-runs.'),
1419
+ fixtures: {
1420
+ type: 'array',
1421
+ items: { type: 'object', additionalProperties: true },
1422
+ description: 'Optional seeded fixtures used to explain benign removals and attack coverage.',
1423
+ },
1424
+ ...confirmSchema,
1425
+ }, ['id', 'expectedRuleVersion', 'expectedCurrentInstantHash', 'confirm', 'reason']),
1426
+ },
908
1427
  {
909
1428
  name: 'securenow_alert_rule_test',
910
1429
  title: 'Test Alert Rule',
@@ -1108,19 +1627,22 @@ const TOOLS = [
1108
1627
  {
1109
1628
  name: 'securenow_blocklist_add',
1110
1629
  title: 'Add Blocked IP',
1111
- description: 'Add an IP/CIDR to the blocklist. Write action; requires confirmation.',
1630
+ description: 'Add an IP/CIDR to the blocklist, optionally scoped to a route/method. Write action; requires confirmation.',
1112
1631
  scope: 'blocklist:write',
1113
1632
  readOnly: false,
1114
1633
  confirm: true,
1115
1634
  method: 'POST',
1116
1635
  endpoint: '/blocklist',
1117
- bodyFields: ['ip', 'reason', 'expiresAt', 'metadata', 'appKey', 'environment'],
1636
+ bodyFields: ['ip', 'reason', 'expiresAt', 'metadata', 'appKey', 'environment', 'pathPattern', 'pathMatchMode', 'method'],
1118
1637
  inputSchema: objectSchema({
1119
1638
  ip: string('IPv4 address or CIDR.'),
1120
1639
  reason: string('Reason for blocking.'),
1121
1640
  expiresAt: string('Optional expiry time as ISO 8601.'),
1122
1641
  metadata: { type: 'object', additionalProperties: true, description: 'Optional metadata.' },
1123
1642
  appKey: string('Optional application key to scope this block. Omit for all apps.'),
1643
+ pathPattern: string('Optional route/path pattern, for example /admin*. Omit to block all routes.'),
1644
+ pathMatchMode: { type: 'string', enum: ['exact', 'prefix', 'regex'], description: 'Path matching mode. Defaults to prefix.' },
1645
+ method: { type: 'string', enum: ['ALL', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], description: 'HTTP method scope. Defaults to ALL.' },
1124
1646
  ...environmentInput,
1125
1647
  ...confirmSchema,
1126
1648
  }, ['ip', 'confirm', 'reason']),
@@ -1169,8 +1691,8 @@ const TOOLS = [
1169
1691
  },
1170
1692
  {
1171
1693
  name: 'securenow_allowlist_list',
1172
- title: 'List Allowlist',
1173
- description: 'List allowed IPs.',
1694
+ title: 'List Restrictive Allowlist',
1695
+ description: 'List restrictive allowlist entries. If any active entry exists for an app/environment, only listed IPs can reach it and all other IPs are blocked. This is not the Trusted IP list.',
1174
1696
  scope: 'allowlist:read',
1175
1697
  readOnly: true,
1176
1698
  method: 'GET',
@@ -1184,24 +1706,26 @@ const TOOLS = [
1184
1706
  },
1185
1707
  {
1186
1708
  name: 'securenow_allowlist_add',
1187
- title: 'Add Allowed IP',
1188
- description: 'Add an IP/CIDR to the allowlist. Write action; requires confirmation.',
1709
+ title: 'Add Restrictive Allowlist IP',
1710
+ description: 'Dangerous production action: add an IP/CIDR to the restrictive allowlist. Once active, allowlist mode blocks every IP except listed entries. Do not use this to mark an IP trusted or suppress false positives; use securenow_trusted_add instead. Requires explicit human approval confirming deny-all behavior.',
1189
1711
  scope: 'allowlist:write',
1190
1712
  readOnly: false,
1191
1713
  confirm: true,
1192
1714
  method: 'POST',
1193
1715
  endpoint: '/allowlist',
1194
- bodyFields: ['ip', 'label', 'reason', 'expiresAt', 'applicationsAll', 'applicationKeys', 'environment'],
1716
+ fixedBody: { initiatedBy: 'mcp' },
1717
+ bodyFields: ['ip', 'label', 'reason', 'expiresAt', 'applicationsAll', 'applicationKeys', 'environment', 'allowlistDenyAllApproved'],
1195
1718
  inputSchema: objectSchema({
1196
1719
  ip: string('IPv4 address or CIDR.'),
1197
1720
  label: string('Human-readable label.'),
1198
- reason: string('Reason for allowing.'),
1721
+ reason: string('Required human-approved reason. Must acknowledge that allowlist blocks all non-listed IPs and why Trusted IPs is not the correct action.'),
1199
1722
  expiresAt: string('Optional expiry time as ISO 8601.'),
1200
1723
  applicationsAll: boolean('Apply to all applications.'),
1201
1724
  applicationKeys: arrayOfStrings('Application keys to scope this allowlist entry to.'),
1725
+ allowlistDenyAllApproved: boolean('Must be true only after the user explicitly approves deny-by-default allowlist behavior. Do not set this for trusted IPs, monitors, false positives, or investigation cleanup.'),
1202
1726
  ...environmentInput,
1203
1727
  ...confirmSchema,
1204
- }, ['ip', 'confirm', 'reason']),
1728
+ }, ['ip', 'confirm', 'reason', 'allowlistDenyAllApproved']),
1205
1729
  },
1206
1730
  {
1207
1731
  name: 'securenow_allowlist_remove',
@@ -1221,7 +1745,7 @@ const TOOLS = [
1221
1745
  {
1222
1746
  name: 'securenow_trusted_list',
1223
1747
  title: 'List Trusted IPs',
1224
- description: 'List trusted IPs.',
1748
+ description: 'List trusted IPs. Trusted IPs are for known-safe traffic and do not turn on deny-by-default allowlist mode.',
1225
1749
  scope: 'trusted_ips:read',
1226
1750
  readOnly: true,
1227
1751
  method: 'GET',
@@ -1235,7 +1759,7 @@ const TOOLS = [
1235
1759
  {
1236
1760
  name: 'securenow_trusted_add',
1237
1761
  title: 'Add Trusted IP',
1238
- description: 'Add a trusted IP/CIDR. Write action; requires confirmation.',
1762
+ description: 'Add a trusted IP/CIDR for known-safe infrastructure, monitors, office/VPN traffic, or scoped false-positive suppression. This does not enable deny-by-default allowlist mode and is the correct tool when an IP should be trusted without blocking other visitors. Write action; requires confirmation.',
1239
1763
  scope: 'trusted_ips:write',
1240
1764
  readOnly: false,
1241
1765
  confirm: true,
@@ -1347,7 +1871,7 @@ const PROMPTS = [
1347
1871
  {
1348
1872
  name: 'investigate_human_action_row',
1349
1873
  title: 'Investigate Human Action Row',
1350
- description: 'Use MCP tools to deeply review one Requires Human row and either block the IP or mark a scoped false positive.',
1874
+ description: 'Use MCP tools to deeply review one Requires Human row and reach a safe audited outcome.',
1351
1875
  arguments: [
1352
1876
  { name: 'rowNumber', description: '1-based row number from the Requires Human queue.', required: true },
1353
1877
  { name: 'page', description: 'Queue page number. Defaults to 1.', required: false },
@@ -1417,7 +1941,7 @@ function promptMessages(name, args = {}) {
1417
1941
  'Verify SecureNow default-on protection for this project.',
1418
1942
  args.appKey ? `App key: ${args.appKey}` : null,
1419
1943
  'Check npx securenow whoami, npx securenow api-key show, npx securenow firewall apps, and npx securenow firewall status.',
1420
- 'Confirm traces, logs, capture.body, capture.multipart, and firewall.enabled are enabled by .securenow/credentials.json defaults unless explicitly set false.',
1944
+ 'Confirm traces, logs, capture.body, capture.multipart, and firewall.enabled are enabled by .securenow/runtime.json defaults unless explicitly set false. Legacy .securenow/credentials.json is still accepted.',
1421
1945
  'Do not print full tokens or API keys.',
1422
1946
  ].filter(Boolean).join('\n'),
1423
1947
  },
@@ -1436,7 +1960,7 @@ function promptMessages(name, args = {}) {
1436
1960
  args.appKeys ? `Scope to app keys: ${args.appKeys}` : null,
1437
1961
  `Environment scope: ${args.environment || 'production'}.`,
1438
1962
  'Use IP intelligence first, then related traces/logs, then recommend remediation.',
1439
- 'Only block, allow, or trust the IP after explicit user confirmation.',
1963
+ 'Only block or trust the IP after explicit user confirmation. Never use IP Allowlist for false positives or trusted traffic; allowlist is deny-by-default and blocks all non-listed IPs.',
1440
1964
  ].filter(Boolean).join('\n'),
1441
1965
  },
1442
1966
  },
@@ -1458,11 +1982,16 @@ function promptMessages(name, args = {}) {
1458
1982
  `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.`,
1459
1983
  'Read the AI report, finalDecision, investigation steps, findings, proofs, metadata paths/user agents/status codes, and trace IDs.',
1460
1984
  'Open trace evidence with securenow_traces_show and correlated logs with securenow_logs_for_trace when trace IDs are available.',
1461
- 'Return one clear outcome: Block IP, Rate Limit, False Positive, Rule Tuning Needed, or Ambiguous. If evidence is ambiguous, stop and explain what is missing.',
1985
+ 'Return one clear outcome: Block IP, Rate Limit, False Positive, Rule Tuning Applied/Needed, New Alert Rule Needed, Case Action Approved/Rejected, or Ambiguous/Skipped with exact missing proof.',
1986
+ 'A row can be finished without an enforcement write when no write is safe. In that case record or prepare a structured no-write decision report explaining the missing proof, missing permission, or vague guard.',
1987
+ 'If evidence is mixed, split the decision by IP/cluster. Block only proven malicious IPs, false-positive only exact benign shapes, rate-limit only route-specific volume abuse, and leave ambiguous members with missing proof.',
1988
+ 'If the right fix is global/system rule tuning and the MCP tool returns access denied, do not create customer-specific false positives. Prepare the exact attack-preserving guard/SQL, dry-run it if possible, and record outcome=rule_tuning with missingProof naming the missing permission.',
1989
+ 'If an AI proposed action says to use a shared benign shape but does not provide exact path/method/status/user-agent/body/header conditions, do not execute it. Record the missing guard fields instead.',
1462
1990
  'Use rate limiting only as temporary soft remediation for repeated route-specific abuse such as login brute force, credential stuffing, scraping/API bursts, enumeration, recon/probing, or repeated noisy payloads where risk/impact evidence is below the block threshold.',
1463
1991
  'Do not rate-limit instead of blocking when there is confirmed exploit success, token/data exposure, SSRF reachability, file read, RCE, persistence, malware/C2, or riskScore >= 85 with high-confidence malicious evidence. Do not rate-limit benign false positives, trusted monitors, app-server/proxy attribution problems, isolated one-off requests, or broad noisy rules that need alert tuning.',
1992
+ 'Do not create or recommend IP Allowlist entries during investigations unless the user explicitly asks to lock the app/environment to a known set of IPs. For safe monitors, offices, VPNs, or false positives, use Trusted IPs or scoped false-positive exclusions instead.',
1464
1993
  confirmWrites
1465
- ? 'The user requested execution. If evidence supports the decision, call securenow_human_action_block, securenow_rate_limit_create_from_text plus securenow_human_action_decision_report_add(outcome=rate_limited), or securenow_human_action_false_positive with confirm:true, a precise reason, and a decisionReport containing summary, evidence, reviewedHistory, traceIds, and missingProof when relevant. If you skip/mark ambiguous but still need to record the audit trail, call securenow_human_action_decision_report_add.'
1994
+ ? 'The user requested execution. If evidence supports the decision, call securenow_human_action_block, securenow_rate_limit_create_from_text plus securenow_human_action_decision_report_add(outcome=rate_limited), or securenow_human_action_false_positive with confirm:true, a precise reason, and a decisionReport containing summary, evidence, reviewedHistory, traceIds, and missingProof when relevant. If no enforcement write is safe, call securenow_human_action_decision_report_add with outcome=rule_tuning, new_alert_rule, ambiguous, skipped, or other so the audit trail is complete without changing IP status.'
1466
1995
  : 'Do not execute write tools yet. Prepare the recommended decision and exact tool call the user can approve.',
1467
1996
  '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.',
1468
1997
  'False positives must be narrow: app + alert rule + path + method/status/user-agent/body evidence where possible. Never globally trust an IP by default.',
@@ -1484,14 +2013,19 @@ function promptMessages(name, args = {}) {
1484
2013
  'Work my SecureNow Requires Human queue like a senior security analyst using the MCP tools.',
1485
2014
  `Review up to ${limit} row(s), most urgent first.${args.search ? ` Search filter: ${args.search}.` : ''}`,
1486
2015
  'Start with securenow_human_actions_list. For each row, call securenow_notifications_get and securenow_human_action_report, inspect the AI report/investigation steps/proofs/trace IDs, and fetch trace/log evidence where useful.',
1487
- 'For each row choose exactly one outcome: Block IP, Rate Limit, False Positive, Rule Tuning Needed, or Skip because evidence is insufficient. Explain skipped rows.',
2016
+ 'For each row choose exactly one outcome: Block IP, Rate Limit, False Positive, Rule Tuning Applied/Needed, New Alert Rule Needed, Case Action Approved/Rejected, or Ambiguous/Skipped with exact missing proof.',
2017
+ 'Finished does not always mean enforcement changed. A row is also finished when a structured no-write decision report records the exact proof gap, missing permission, or vague guard and no weaker substitute write is used.',
2018
+ 'If a global/system rule update is the right fix but access is denied, do not mark customer-specific false positives just to clear the row. Prepare the exact attack-preserving guard/SQL, dry-run it when possible, and record outcome=rule_tuning with missingProof.',
2019
+ 'If evidence is mixed, do partial resolution: block proven malicious IPs, false-positive exact benign shapes, rate-limit route-specific volume abuse, and leave ambiguous IPs with their missing proof.',
2020
+ 'Reject vague AI proposed actions. If the prompt/action lacks concrete rule id, path, method, status, user-agent/body/header guard, benign samples, and attack examples that still match, record it as ambiguous or rule_tuning needed.',
1488
2021
  'Use Rate Limit only for repeated route-specific abuse where temporary friction is safer than blocking: login brute force/credential stuffing, password reset/account enumeration, scraping/API bursts, path or ID enumeration, recon/probing, or repeated noisy payloads without confirmed exploit success.',
1489
2022
  'Do not rate-limit confirmed high-risk attacks that should be blocked, benign traffic that should be false-positive scoped, broad noisy rules that need tuning, app-server/proxy attribution problems, or isolated one-off requests.',
2023
+ 'Do not create or recommend IP Allowlist entries while working this queue unless the user explicitly approves deny-by-default allowlist mode for the whole app/environment. Use Trusted IPs for trusted/bypass cases.',
1490
2024
  confirmWrites
1491
- ? 'The user requested execution. For supported decisions, call the correct write tool with confirm:true, a precise reason, and a decisionReport containing summary, evidence, reviewedHistory, traceIds, and missingProof when relevant, then continue. For skipped/ambiguous/rule-tuning-needed rows that should be auditable without changing IP status, use securenow_human_action_decision_report_add.'
2025
+ ? 'The user requested execution. For supported decisions, call the correct write tool with confirm:true, a precise reason, and a decisionReport containing summary, evidence, reviewedHistory, traceIds, and missingProof when relevant, then continue. For skipped/ambiguous/rule-tuning-needed/new-rule-needed rows that should be auditable without changing enforcement status, use securenow_human_action_decision_report_add or securenow_human_case_decision_report_add.'
1492
2026
  : 'Do not execute write tools yet. Produce a row-by-row action plan and exact MCP write calls for user approval.',
1493
2027
  'For block decisions, use securenow_human_action_block. For rate-limit decisions, use securenow_rate_limit_create_from_text and then securenow_human_action_decision_report_add with outcome=rate_limited. 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.',
1494
- 'End with counts: handled, proposed block, rate limits created/proposed, proposed false positive, rule tuning needed, skipped, still waiting.',
2028
+ 'End with counts: handled, blocked/proposed block, rate limits created/proposed, false positives, rule tuning/admin permission blockers with exact guard, partial resolutions, skipped with missing proof, and still waiting.',
1495
2029
  ].join('\n'),
1496
2030
  },
1497
2031
  },
@@ -1513,7 +2047,7 @@ function promptMessages(name, args = {}) {
1513
2047
  'Start with securenow_blocklist_stats, then securenow_blocklist_pending_list({ page: 1, limit, environment }).',
1514
2048
  'For each pending block, inspect id, IP, source, reason, metadata.riskScore, metadata.aiRiskScore, metadata.abuseConfidenceScore, automation rule, linked notification, age, app, and environment.',
1515
2049
  'When investigationNotificationId exists, fetch securenow_notifications_get for that case and use the linked IP report/history as evidence.',
1516
- 'Approve only when the row has clear malicious evidence, riskScore >= 90, or SecureNow IPDB evidence score >= 80 with no false-positive/test signal.',
2050
+ 'Approve only when the row has clear malicious evidence, riskScore >= 90, or SecureNow AI IPDB evidence score >= 80 with no false-positive/test signal.',
1517
2051
  'Reject stale, ambiguous, synthetic/test, self-traffic, or false-positive rows. Prefer narrow rule/exclusion tuning when the same benign pattern repeats.',
1518
2052
  confirmWrites
1519
2053
  ? 'The user requested execution. Use securenow_blocklist_pending_approve or securenow_blocklist_pending_reject with confirm:true and a precise reason. Use bulk tools only after every selected row satisfies the same reviewed policy.'
@@ -1537,7 +2071,7 @@ function promptMessages(name, args = {}) {
1537
2071
  text: [
1538
2072
  'Configure SecureNow default automation using the MCP tools.',
1539
2073
  `Review environment context: ${environment}. Built-in defaults apply to all apps and all environments unless the customer narrows them later.`,
1540
- 'Use one canonical product score for automation decisions: riskScore. SecureNow IPDB / AbuseIPDB score and AI confidence remain supporting evidence.',
2074
+ 'Use one canonical product score for automation decisions: riskScore. SecureNow AI IPDB score and AI confidence remain supporting evidence.',
1541
2075
  'Built-in defaults are active by default:',
1542
2076
  '- Critical Risk Auto-Block: riskScore>=95, TTL 168h.',
1543
2077
  '- High Risk Auto-Block: riskScore>=90 AND riskScore<95, TTL 72h.',
@@ -1595,6 +2129,9 @@ function assertConfirmed(tool, args = {}) {
1595
2129
  if (!args.reason || !String(args.reason).trim()) {
1596
2130
  throw new Error(`${tool.name} requires a non-empty reason.`);
1597
2131
  }
2132
+ if (tool.name === 'securenow_allowlist_add' && args.allowlistDenyAllApproved !== true) {
2133
+ throw new Error('securenow_allowlist_add is deny-by-default: it blocks every non-listed IP for the scoped app/environment. Pass allowlistDenyAllApproved:true only after explicit human approval. Use securenow_trusted_add for trusted IPs or false positives.');
2134
+ }
1598
2135
  }
1599
2136
 
1600
2137
  function buildApiRequest(tool, rawArgs = {}) {
@@ -1606,7 +2143,7 @@ function buildApiRequest(tool, rawArgs = {}) {
1606
2143
  endpoint = endpoint.replace(`:${key}`, encodeURIComponent(String(args[key])));
1607
2144
  }
1608
2145
 
1609
- const query = {};
2146
+ const query = { ...(tool.fixedQuery || {}) };
1610
2147
  for (const key of tool.queryFields || []) {
1611
2148
  let value = args[key];
1612
2149
  if (tool.normalize && typeof tool.normalize[key] === 'function') {