secator 0.11.1__py3-none-any.whl → 0.13.0__py3-none-any.whl

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.

Potentially problematic release.


This version of secator might be problematic. Click here for more details.

Files changed (48) hide show
  1. secator/cli.py +175 -66
  2. secator/config.py +1 -0
  3. secator/configs/workflows/host_recon.yaml +11 -2
  4. secator/configs/workflows/port_scan.yaml +39 -0
  5. secator/configs/workflows/url_dirsearch.yaml +5 -0
  6. secator/configs/workflows/url_params_fuzz.yaml +2 -0
  7. secator/decorators.py +39 -21
  8. secator/definitions.py +0 -4
  9. secator/installer.py +29 -15
  10. secator/runners/_base.py +2 -1
  11. secator/runners/_helpers.py +13 -2
  12. secator/runners/command.py +3 -1
  13. secator/tasks/_categories.py +2 -2
  14. secator/tasks/arjun.py +11 -2
  15. secator/tasks/bbot.py +30 -4
  16. secator/tasks/bup.py +2 -1
  17. secator/tasks/cariddi.py +15 -3
  18. secator/tasks/dalfox.py +2 -1
  19. secator/tasks/dirsearch.py +1 -1
  20. secator/tasks/dnsx.py +2 -1
  21. secator/tasks/dnsxbrute.py +2 -1
  22. secator/tasks/feroxbuster.py +3 -2
  23. secator/tasks/ffuf.py +2 -1
  24. secator/tasks/gau.py +2 -1
  25. secator/tasks/gitleaks.py +4 -3
  26. secator/tasks/gospider.py +3 -2
  27. secator/tasks/grype.py +1 -0
  28. secator/tasks/h8mail.py +2 -1
  29. secator/tasks/httpx.py +3 -2
  30. secator/tasks/katana.py +4 -3
  31. secator/tasks/maigret.py +1 -1
  32. secator/tasks/mapcidr.py +2 -1
  33. secator/tasks/msfconsole.py +4 -3
  34. secator/tasks/naabu.py +4 -2
  35. secator/tasks/nuclei.py +15 -9
  36. secator/tasks/searchsploit.py +3 -2
  37. secator/tasks/subfinder.py +2 -1
  38. secator/tasks/testssl.py +4 -3
  39. secator/tasks/trivy.py +2 -2
  40. secator/tasks/wafw00f.py +2 -1
  41. secator/tasks/wpprobe.py +2 -1
  42. secator/tasks/wpscan.py +6 -3
  43. secator/template.py +12 -0
  44. {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/METADATA +1 -1
  45. {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/RECORD +48 -47
  46. {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/WHEEL +0 -0
  47. {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/entry_points.txt +0 -0
  48. {secator-0.11.1.dist-info → secator-0.13.0.dist-info}/licenses/LICENSE +0 -0
secator/cli.py CHANGED
@@ -37,7 +37,7 @@ ALL_TASKS = discover_tasks()
37
37
  ALL_WORKFLOWS = [t for t in TEMPLATES if t.type == 'workflow']
38
38
  ALL_SCANS = [t for t in TEMPLATES if t.type == 'scan']
39
39
  FINDING_TYPES_LOWER = [c.__name__.lower() for c in FINDING_TYPES]
40
- CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
40
+ CONTEXT_SETTINGS = dict(help_option_names=['-h', '-help', '--help'])
41
41
 
42
42
 
43
43
  #-----#
@@ -46,15 +46,16 @@ CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
46
46
 
47
47
 
48
48
  @click.group(cls=OrderedGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
49
- @click.option('--version', '-version', is_flag=True, default=False)
49
+ @click.option('--version', '-version', '-v', is_flag=True, default=False)
50
+ @click.option('--quiet', '-quiet', '-q', is_flag=True, default=False)
50
51
  @click.pass_context
51
- def cli(ctx, version):
52
+ def cli(ctx, version, quiet):
52
53
  """Secator CLI."""
53
54
  ctx.obj = {
54
55
  'piped_input': S_ISFIFO(os.fstat(0).st_mode),
55
56
  'piped_output': not sys.stdout.isatty()
56
57
  }
57
- if not ctx.obj['piped_output']:
58
+ if not ctx.obj['piped_output'] and not quiet:
58
59
  console.print(ASCII, highlight=False)
59
60
  if ctx.invoked_subcommand is None:
60
61
  if version:
@@ -67,11 +68,16 @@ def cli(ctx, version):
67
68
  # TASK #
68
69
  #------#
69
70
 
70
- @cli.group(aliases=['x', 't'])
71
+ @cli.group(aliases=['x', 't'], invoke_without_command=True)
72
+ @click.option('--list', '-list', is_flag=True, default=False)
71
73
  @click.pass_context
72
- def task(ctx):
74
+ def task(ctx, list=False):
73
75
  """Run a task."""
74
- pass
76
+ if list:
77
+ print("\n".join(sorted([t.__name__ for t in ALL_TASKS])))
78
+ return
79
+ if ctx.invoked_subcommand is None:
80
+ ctx.get_help()
75
81
 
76
82
 
77
83
  for cls in ALL_TASKS:
@@ -83,11 +89,16 @@ for cls in ALL_TASKS:
83
89
  #----------#
84
90
 
85
91
 
86
- @cli.group(cls=OrderedGroup, aliases=['w'])
92
+ @cli.group(cls=OrderedGroup, aliases=['w'], invoke_without_command=True)
93
+ @click.option('--list', '-list', is_flag=True, default=False)
87
94
  @click.pass_context
88
- def workflow(ctx):
95
+ def workflow(ctx, list=False):
89
96
  """Run a workflow."""
90
- pass
97
+ if list:
98
+ print("\n".join(sorted([t.name for t in ALL_WORKFLOWS])))
99
+ return
100
+ if ctx.invoked_subcommand is None:
101
+ ctx.get_help()
91
102
 
92
103
 
93
104
  for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
@@ -98,11 +109,16 @@ for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
98
109
  # SCAN #
99
110
  #------#
100
111
 
101
- @cli.group(cls=OrderedGroup, aliases=['s'])
112
+ @cli.group(cls=OrderedGroup, aliases=['s'], invoke_without_command=True)
113
+ @click.option('--list', '-list', is_flag=True, default=False)
102
114
  @click.pass_context
103
- def scan(ctx):
115
+ def scan(ctx, list=False):
104
116
  """Run a scan."""
105
- pass
117
+ if list:
118
+ print("\n".join(sorted([t.name for t in ALL_SCANS])))
119
+ return
120
+ if ctx.invoked_subcommand is None:
121
+ ctx.get_help()
106
122
 
107
123
 
108
124
  for config in sorted(ALL_SCANS, key=lambda x: x['name']):
@@ -119,7 +135,7 @@ for config in sorted(ALL_SCANS, key=lambda x: x['name']):
119
135
  @click.option('-r', '--reload', is_flag=True, help='Autoreload Celery on code changes.')
120
136
  @click.option('-Q', '--queue', type=str, default='', help='Listen to a specific queue.')
121
137
  @click.option('-P', '--pool', type=str, default='eventlet', help='Pool implementation.')
122
- @click.option('--quiet', is_flag=True, help='Quiet mode.')
138
+ @click.option('--quiet', is_flag=True, default=False, help='Quiet mode.')
123
139
  @click.option('--loglevel', type=str, default='INFO', help='Log level.')
124
140
  @click.option('--check', is_flag=True, help='Check if Celery worker is alive.')
125
141
  @click.option('--dev', is_flag=True, help='Start a worker in dev mode (celery multi).')
@@ -860,13 +876,21 @@ def health(json, debug, strict):
860
876
  import json as _json
861
877
  print(_json.dumps(status))
862
878
 
879
+ # Print errors and warnings
880
+ error = False
881
+ for tool, info in status['tools'].items():
882
+ if not info['installed']:
883
+ console.print(Warning(message=f'{tool} is not installed.'))
884
+ error = True
885
+ elif info['outdated']:
886
+ message = (
887
+ f'{tool} is outdated (current:{info["version"]}, latest:{info["latest_version"]}).'
888
+ f' Run `secator install tools {tool}` to update it.'
889
+ )
890
+ console.print(Warning(message=message))
891
+
863
892
  # Strict mode
864
893
  if strict:
865
- error = False
866
- for tool, info in status['tools'].items():
867
- if not info['installed']:
868
- console.print(Error(message=f'{tool} is not installed.'))
869
- error = True
870
894
  if error:
871
895
  sys.exit(1)
872
896
  console.print(Info(message='Strict healthcheck passed !'))
@@ -1061,16 +1085,27 @@ def install_tools(cmds, cleanup, fail_fast):
1061
1085
  if CONFIG.offline_mode:
1062
1086
  console.print(Error(message='Cannot run this command in offline mode.'))
1063
1087
  return
1088
+ tools = []
1064
1089
  if cmds is not None:
1065
1090
  cmds = cmds.split(',')
1066
- tools = [cls for cls in ALL_TASKS if cls.__name__ in cmds]
1091
+ for cmd in cmds:
1092
+ if '==' in cmd:
1093
+ cmd, version = tuple(cmd.split('=='))
1094
+ else:
1095
+ cmd, version = cmd, None
1096
+ cls = next((cls for cls in ALL_TASKS if cls.__name__ == cmd), None)
1097
+ if cls:
1098
+ if version:
1099
+ cls.install_version = version
1100
+ tools.append(cls)
1101
+ else:
1102
+ console.print(Warning(message=f'Tool {cmd} is not supported or inexistent.'))
1067
1103
  else:
1068
1104
  tools = ALL_TASKS
1069
1105
  tools.sort(key=lambda x: x.__name__)
1070
1106
  return_code = 0
1071
1107
  if not tools:
1072
- cmd_str = ' '.join(cmds)
1073
- console.print(Error(message=f'No tools found for {cmd_str}.'))
1108
+ console.print(Error(message='No tools found for installing.'))
1074
1109
  return
1075
1110
  for ix, cls in enumerate(tools):
1076
1111
  # with console.status(f'[bold yellow][{ix + 1}/{len(tools)}] Installing {cls.__name__} ...'):
@@ -1140,7 +1175,7 @@ def update(all):
1140
1175
  cmd = cls.cmd.split(' ')[0]
1141
1176
  version_flag = cls.get_version_flag()
1142
1177
  info = get_version_info(cmd, version_flag, cls.install_github_handle)
1143
- if not info['installed'] or info['status'] == 'outdated' or not info['latest_version']:
1178
+ if not info['installed'] or info['outdated'] or not info['latest_version']:
1144
1179
  # with console.status(f'[bold yellow]Installing {cls.__name__} ...'):
1145
1180
  status = ToolInstaller.install(cls)
1146
1181
  if not status.is_ok():
@@ -1375,73 +1410,147 @@ def performance(tasks, workflows, scans, test):
1375
1410
  @test.command()
1376
1411
  @click.argument('name', type=str)
1377
1412
  @click.option('--verbose', '-v', is_flag=True, default=False, help='Print verbose output')
1378
- def task(name, verbose):
1379
- """Test task."""
1413
+ @click.option('--check', '-c', is_flag=True, default=False, help='Check task semantics only (no unit + integration tests)') # noqa: E501
1414
+ def task(name, verbose, check):
1415
+ """Test a single task for semantics errors, and run unit + integration tests."""
1416
+ console.print(f'[bold gold3]:wrench: Testing task {name} ...[/]')
1380
1417
  task = [task for task in ALL_TASKS if task.__name__ == name]
1381
1418
  warnings = []
1419
+ errors = []
1382
1420
  exit_code = 0
1383
1421
 
1384
1422
  # Check if task is correctly registered
1385
- check_error(task, 'Check task is registered', 'Task is not registered. Make sure there is no syntax errors in the task class definition.', warnings) # noqa: E501
1386
1423
  task = task[0]
1387
1424
  task_name = task.__name__
1388
1425
 
1389
1426
  # Run install
1390
- console.print(f'\n[bold gold3]:wrench: Running install tests for task {name} ...[/]') if verbose else None
1391
1427
  cmd = f'secator install tools {task_name}'
1392
1428
  ret_code = Command.execute(cmd, name='install', quiet=not verbose, cwd=ROOT_FOLDER)
1393
1429
  version_info = task.get_version_info()
1394
- check_error(version_info['installed'], 'Check task is installed', 'Failed to install command. Fix your installation command.', warnings) # noqa: E501
1395
- check_error(any(cmd for cmd in [task.install_cmd, task.install_github_handle]), 'Check task installation command is defined', 'Task has no installation command. Please define a `install_cmd` or `install_github_handle` class attribute.', warnings) # noqa: E501
1396
- check_error(version_info['version'], 'Check task version can be fetched', 'Failed to detect version info. Fix your `version_flag` class attribute.', warnings) # noqa: E501
1430
+ if verbose:
1431
+ console.print(f'Version info:\n{version_info}')
1432
+ status = version_info['status']
1433
+ check_test(
1434
+ version_info['installed'],
1435
+ 'Check task is installed',
1436
+ 'Failed to install command. Fix your installation command.',
1437
+ errors
1438
+ )
1439
+ check_test(
1440
+ any(cmd for cmd in [task.install_cmd, task.install_github_handle]),
1441
+ 'Check task installation command is defined',
1442
+ 'Task has no installation command. Please define one or more of the following class attributes: `install_pre`, `install_cmd`, `install_post`, `install_github_handle`.', # noqa: E501
1443
+ errors
1444
+ )
1445
+ check_test(
1446
+ version_info['version'],
1447
+ 'Check task version can be fetched',
1448
+ 'Failed to detect current version. Consider updating your `version_flag` class attribute.',
1449
+ warnings,
1450
+ warn=True
1451
+ )
1452
+ check_test(
1453
+ status != 'latest unknown',
1454
+ 'Check latest version',
1455
+ 'Failed to detect latest version.',
1456
+ warnings,
1457
+ warn=True
1458
+ )
1459
+ check_test(
1460
+ not version_info['outdated'],
1461
+ 'Check task version is up to date',
1462
+ f'Task is not up to date (current version: {version_info["version"]}, latest: {version_info["latest_version"]}). Consider updating your `install_version` class attribute.', # noqa: E501
1463
+ warnings,
1464
+ warn=True
1465
+ )
1397
1466
 
1398
1467
  # Run task-specific tests
1399
- console.print(f'\n[bold gold3]:wrench: Running task-specific tests for {name} ...[/]') if verbose else None
1400
- check_error(task.__doc__, 'Check task description is set (cls.__doc__)', 'Task has no description (class docstring).', warnings) # noqa: E501
1401
- check_error(task.cmd, 'Check task command is set (cls.cmd)', 'Task has no cmd attribute.', warnings)
1402
- check_error(task.input_type, 'Check task input type is set (cls.input_type)', 'Task has no input_type attribute.', warnings) # noqa: E501
1403
- check_error(task.output_types, 'Check task output types is set (cls.output_types)', 'Task has no output_types attribute.', warnings) # noqa: E501
1404
-
1405
- # Print all warnings
1406
- exit_code = 1 if len(warnings) > 0 else 0
1407
- if exit_code == 1:
1408
- console.print()
1409
- console.print("[bold red]Issues:[/]")
1410
- for warning in warnings:
1411
- console.print(warning)
1412
- console.print()
1413
- console.print(Info(message=f'Skipping unit and integration tests for {name} due to previous errors.'))
1414
- console.print(Error(message=f'Task {name} tests failed. Please fix the issues above before making a PR.'))
1415
- sys.exit(exit_code)
1416
-
1417
- # Run unit tests
1418
- console.print(f'\n[bold gold3]:wrench: Running unit tests for {name} ...[/]') if verbose else None
1419
- cmd = f'secator test unit --tasks {name}'
1420
- ret_code = run_test(cmd, exit=False, verbose=verbose)
1421
- check_error(ret_code == 0, 'Check unit tests pass', 'Unit tests failed.', warnings)
1422
-
1423
- # Run integration tests
1424
- console.print(f'\n[bold gold3]:wrench: Running integration tests for {name} ...[/]') if verbose else None
1425
- cmd = f'secator test integration --tasks {name}'
1426
- ret_code = run_test(cmd, exit=False, verbose=verbose)
1427
- check_error(ret_code == 0, 'Check integration tests pass', 'Integration tests failed.', warnings)
1468
+ check_test(
1469
+ task.__doc__,
1470
+ 'Check task description is set (cls.__doc__)',
1471
+ 'Task has no description (class docstring).',
1472
+ errors
1473
+ )
1474
+ check_test(
1475
+ task.cmd,
1476
+ 'Check task command is set (cls.cmd)',
1477
+ 'Task has no cmd attribute.',
1478
+ errors
1479
+ )
1480
+ check_test(
1481
+ task.input_type,
1482
+ 'Check task input type is set (cls.input_type)',
1483
+ 'Task has no input_type attribute.',
1484
+ warnings,
1485
+ warn=True
1486
+ )
1487
+ check_test(
1488
+ task.output_types,
1489
+ 'Check task output types is set (cls.output_types)',
1490
+ 'Task has no output_types attribute. Consider setting some so that secator can load your task outputs.',
1491
+ warnings,
1492
+ warn=True
1493
+ )
1494
+ check_test(
1495
+ task.install_version,
1496
+ 'Check task install_version is set (cls.install_version)',
1497
+ 'Task has no install_version attribute. Consider setting it to pin the tool version and ensure it does not break in the future.', # noqa: E501
1498
+ warnings,
1499
+ warn=True
1500
+ )
1501
+
1502
+ if not check:
1503
+
1504
+ # Run unit tests
1505
+ cmd = f'secator test unit --tasks {name}'
1506
+ ret_code = run_test(cmd, exit=False, verbose=verbose)
1507
+ check_test(
1508
+ ret_code == 0,
1509
+ 'Check unit tests pass',
1510
+ 'Unit tests failed.',
1511
+ errors
1512
+ )
1513
+
1514
+ # Run integration tests
1515
+ cmd = f'secator test integration --tasks {name}'
1516
+ ret_code = run_test(cmd, exit=False, verbose=verbose)
1517
+ check_test(
1518
+ ret_code == 0,
1519
+ 'Check integration tests pass',
1520
+ 'Integration tests failed.',
1521
+ errors
1522
+ )
1428
1523
 
1429
1524
  # Exit with exit code
1430
- exit_code = 1 if len(warnings) > 0 else 0
1525
+ exit_code = 1 if len(errors) > 0 else 0
1431
1526
  if exit_code == 0:
1432
- console.print(f':tada: Task {name} tests passed ! You are free to make a PR.', style='bold green')
1527
+ console.print(f':tada: Task {name} tests passed !', style='bold green')
1433
1528
  else:
1434
- console.print(Error(message=f'Task {name} tests failed. Please fix the issues above before making a PR.'))
1529
+ console.print('\n[bold gold3]Errors:[/]')
1530
+ for error in errors:
1531
+ console.print(error)
1532
+ console.print(Error(message=f'Task {name} tests failed. Please fix the issues above.'))
1435
1533
 
1534
+ if warnings:
1535
+ console.print('\n[bold gold3]Warnings:[/]')
1536
+ for warning in warnings:
1537
+ console.print(warning)
1538
+
1539
+ console.print("\n")
1436
1540
  sys.exit(exit_code)
1437
1541
 
1438
1542
 
1439
- def check_error(condition, message, error, warnings=[]):
1543
+ def check_test(condition, message, fail_message, results=[], warn=False):
1440
1544
  console.print(f'[bold magenta]:zap: {message} ...[/]', end='')
1441
1545
  if not condition:
1442
- warning = Warning(message=error)
1443
- warnings.append(warning)
1444
- console.print(' [bold red]FAILED[/]', style='dim')
1546
+ if not warn:
1547
+ error = Error(message=fail_message)
1548
+ console.print(' [bold red]FAILED[/]', style='dim')
1549
+ results.append(error)
1550
+ else:
1551
+ warning = Warning(message=fail_message)
1552
+ console.print(' [bold yellow]WARNING[/]', style='dim')
1553
+ results.append(warning)
1445
1554
  else:
1446
1555
  console.print(' [bold green]OK[/]', style='dim')
1447
1556
  return True
secator/config.py CHANGED
@@ -94,6 +94,7 @@ class Runners(StrictModel):
94
94
  skip_cve_low_confidence: bool = False
95
95
  remove_duplicates: bool = False
96
96
  show_chunk_progress: bool = False
97
+ show_command_output: bool = False
97
98
 
98
99
 
99
100
  class Security(StrictModel):
@@ -7,10 +7,19 @@ input_types:
7
7
  - host
8
8
  - cidr_range
9
9
  tasks:
10
+ naabu:
11
+ description: Find open ports
12
+ ports: "-" # scan all ports
10
13
  nmap:
11
14
  description: Search for vulnerabilities on open ports
12
- skip_host_discovery: True
13
- ports: "-" # scan all ports
15
+ version_detection: True
16
+ script: vulners
17
+ targets_:
18
+ - port.host
19
+ - target.name
20
+ ports_:
21
+ - port.port
22
+ ports: "-" # default if no port found by naabu
14
23
  _group/1:
15
24
  httpx:
16
25
  description: Probe HTTP services on open ports
@@ -0,0 +1,39 @@
1
+ type: workflow
2
+ name: port_scan
3
+ alias: pscan
4
+ description: Port scan
5
+ tags: [recon, network, http, vuln]
6
+ input_types:
7
+ - host
8
+ - cidr_range
9
+ tasks:
10
+ naabu:
11
+ description: Find open ports
12
+ skip_host_discovery: True
13
+ ports: "-" # scan all ports
14
+ nmap:
15
+ description: Search for vulnerabilities on open ports
16
+ skip_host_discovery: True
17
+ version_detection: True
18
+ targets_: port.host
19
+ ports_: port.port
20
+ _group:
21
+ searchsploit:
22
+ description: Search for related exploits
23
+ targets_:
24
+ - type: port
25
+ field: '{host}~{service_name}'
26
+ condition: item._source.startswith('nmap') and len(item.service_name.split('/')) > 1
27
+ httpx:
28
+ description: Probe HTTP services on open ports
29
+ targets_:
30
+ - type: port
31
+ field: '{host}:{port}'
32
+ condition: item._source.startswith('nmap')
33
+ results:
34
+ - type: port
35
+
36
+ - type: url
37
+ condition: item.status_code != 0
38
+
39
+ - type: vulnerability
@@ -14,6 +14,11 @@ tasks:
14
14
  field: '{name}/FUZZ'
15
15
  cariddi:
16
16
  description: Crawl HTTP directories for content
17
+ info: True
18
+ secrets: True
19
+ errors: True
20
+ juicy_extensions: 1
21
+ juicy_endpoints: True
17
22
  targets_:
18
23
  - target.name
19
24
  - url.url
@@ -11,6 +11,8 @@ tasks:
11
11
  ffuf:
12
12
  description: Fuzz URL params
13
13
  wordlist: https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/Web-Content/burp-parameter-names.txt
14
+ auto_calibration: true
15
+ follow_redirect: true
14
16
  targets_:
15
17
  - type: url
16
18
  field: url
secator/decorators.py CHANGED
@@ -19,11 +19,11 @@ RUNNER_OPTS = {
19
19
  'print_stat': {'is_flag': True, 'short': 'stat', 'default': False, 'help': 'Print runtime statistics'},
20
20
  'print_format': {'default': '', 'short': 'fmt', 'help': 'Output formatting string'},
21
21
  'enable_profiler': {'is_flag': True, 'short': 'prof', 'default': False, 'help': 'Enable runner profiling'},
22
- 'show': {'is_flag': True, 'short': 'sh', 'default': False, 'help': 'Show command that will be run (tasks only)'},
23
22
  'no_process': {'is_flag': True, 'short': 'nps', 'default': False, 'help': 'Disable secator processing'},
24
23
  # 'filter': {'default': '', 'short': 'f', 'help': 'Results filter', 'short': 'of'}, # TODO add this
25
- 'quiet': {'is_flag': True, 'short': 'q', 'default': False, 'help': 'Enable quiet mode'},
24
+ 'quiet': {'is_flag': True, 'short': 'q', 'default': not CONFIG.runners.show_command_output, 'opposite': 'verbose', 'help': 'Enable quiet mode'}, # noqa: E501
26
25
  'dry_run': {'is_flag': True, 'short': 'dr', 'default': False, 'help': 'Enable dry run'},
26
+ 'show': {'is_flag': True, 'short': 'yml', 'default': False, 'help': 'Show runner yaml'},
27
27
  }
28
28
 
29
29
  RUNNER_GLOBAL_OPTS = {
@@ -163,35 +163,39 @@ def get_command_options(config):
163
163
  elif opt in RUNNER_GLOBAL_OPTS:
164
164
  prefix = 'Execution'
165
165
 
166
+ # Get opt value from YAML config
167
+ opt_conf_value = task_config_opts.get(opt)
168
+
166
169
  # Get opt conf
167
170
  conf = opt_conf.copy()
171
+ opt_is_flag = conf.get('is_flag', False)
172
+ opt_default = conf.get('default', False if opt_is_flag else None)
173
+ opt_is_required = conf.get('required', False)
168
174
  conf['show_default'] = True
169
175
  conf['prefix'] = prefix
170
- opt_default = conf.get('default', None)
171
- opt_is_flag = conf.get('is_flag', False)
172
- opt_value_in_config = task_config_opts.get(opt)
176
+ conf['default'] = opt_default
177
+ conf['reverse'] = False
173
178
 
174
- # Check if opt already defined in config
175
- if opt_value_in_config:
176
- if conf.get('required', False):
179
+ # Change CLI opt defaults if opt was overriden in YAML config
180
+ if opt_conf_value:
181
+ if opt_is_required:
177
182
  debug('OPT (skipped: opt is required and defined in config)', obj={'opt': opt}, sub=f'cli.{config.name}', verbose=True) # noqa: E501
178
183
  continue
179
184
  mapped_value = cls.opt_value_map.get(opt)
180
185
  if callable(mapped_value):
181
- opt_value_in_config = mapped_value(opt_value_in_config)
186
+ opt_conf_value = mapped_value(opt_conf_value)
182
187
  elif mapped_value:
183
- opt_value_in_config = mapped_value
184
- if opt_value_in_config != opt_default:
188
+ opt_conf_value = mapped_value
189
+
190
+ # Handle option defaults
191
+ if opt_conf_value != opt_default:
185
192
  if opt in opt_cache:
186
193
  continue
187
194
  if opt_is_flag:
188
- conf['reverse'] = True
189
- conf['default'] = not conf['default']
190
- # print(f'{opt}: change default to {opt_value_in_config}')
191
- conf['default'] = opt_value_in_config
195
+ conf['default'] = opt_default = opt_conf_value
192
196
 
193
- # If opt is a flag but the default is True, add opposite flag
194
- if opt_is_flag and opt_default is True:
197
+ # Add reverse flag
198
+ if opt_default is True:
195
199
  conf['reverse'] = True
196
200
 
197
201
  # Check if opt already processed before
@@ -205,7 +209,7 @@ def get_command_options(config):
205
209
  all_opts[opt] = conf
206
210
 
207
211
  # Debug
208
- debug_conf = OrderedDict({'opt': opt, 'config_val': opt_value_in_config or 'N/A', **conf.copy()})
212
+ debug_conf = OrderedDict({'opt': opt, 'config_val': opt_conf_value or 'N/A', **conf.copy()})
209
213
  debug('OPT', obj=debug_conf, sub=f'cli.{config.name}', verbose=True)
210
214
 
211
215
  return all_opts
@@ -236,11 +240,17 @@ def decorate_command_options(opts):
236
240
  conf.pop('process', None)
237
241
  conf.pop('requires_sudo', None)
238
242
  reverse = conf.pop('reverse', False)
243
+ opposite = conf.pop('opposite', None)
239
244
  long = f'--{opt_name}'
240
245
  short = f'-{short_opt}' if short_opt else f'-{opt_name}'
241
246
  if reverse:
242
- long += f'/--no-{opt_name}'
243
- short += f'/-n{short_opt}' if short else f'/-n{opt_name}'
247
+ if opposite:
248
+ long += f'/--{opposite}'
249
+ short += f'/-{opposite[0]}'
250
+ conf['help'] = conf['help'].replace(opt_name, f'{opt_name} / {opposite}')
251
+ else:
252
+ long += f'/--no-{opt_name}'
253
+ short += f'/-n{short_opt}' if short else f'/-n{opt_name}'
244
254
  f = click.option(long, short, **conf)(f)
245
255
  return f
246
256
  return decorator
@@ -321,9 +331,16 @@ def register_runner(cli_endpoint, config):
321
331
  worker = opts.pop('worker')
322
332
  ws = opts.pop('workspace')
323
333
  driver = opts.pop('driver', '')
334
+ quiet = opts['quiet']
335
+ dry_run = opts['dry_run']
324
336
  show = opts['show']
325
337
  context = {'workspace_name': ws}
326
338
 
339
+ # Show runner yaml
340
+ if show:
341
+ config.print()
342
+ sys.exit(0)
343
+
327
344
  # Remove options whose values are default values
328
345
  for k, v in options.items():
329
346
  opt_name = k.replace('-', '_')
@@ -364,7 +381,7 @@ def register_runner(cli_endpoint, config):
364
381
  hooks = deep_merge_dicts(*hooks)
365
382
 
366
383
  # Enable sync or not
367
- if sync or show:
384
+ if sync or dry_run:
368
385
  sync = True
369
386
  else:
370
387
  from secator.celery import is_celery_worker_alive
@@ -394,6 +411,7 @@ def register_runner(cli_endpoint, config):
394
411
  'piped_output': ctx.obj['piped_output'],
395
412
  'caller': 'cli',
396
413
  'sync': sync,
414
+ 'quiet': quiet
397
415
  })
398
416
 
399
417
  # Start runner
secator/definitions.py CHANGED
@@ -23,10 +23,6 @@ ASCII = rf"""
23
23
  DEBUG = CONFIG.debug.level
24
24
  DEBUG_COMPONENT = CONFIG.debug.component.split(',')
25
25
 
26
- # Default tasks settings
27
- DEFAULT_NUCLEI_FLAGS = os.environ.get('DEFAULT_NUCLEI_FLAGS', '-stats -sj -si 20 -hm -or')
28
- DEFAULT_FEROXBUSTER_FLAGS = os.environ.get('DEFAULT_FEROXBUSTER_FLAGS', '--auto-bail --no-state')
29
-
30
26
  # Constants
31
27
  OPT_NOT_SUPPORTED = -1
32
28
  OPT_PIPE_INPUT = -1