secator 0.0.1__py3-none-any.whl → 0.3.5__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 (68) hide show
  1. secator/.gitignore +162 -0
  2. secator/celery.py +8 -68
  3. secator/cli.py +631 -274
  4. secator/decorators.py +42 -6
  5. secator/definitions.py +104 -33
  6. secator/exporters/csv.py +1 -2
  7. secator/exporters/gdrive.py +1 -1
  8. secator/exporters/json.py +1 -2
  9. secator/exporters/txt.py +1 -2
  10. secator/hooks/mongodb.py +12 -12
  11. secator/installer.py +335 -0
  12. secator/report.py +2 -14
  13. secator/rich.py +3 -10
  14. secator/runners/_base.py +106 -34
  15. secator/runners/_helpers.py +18 -17
  16. secator/runners/command.py +91 -55
  17. secator/runners/scan.py +3 -1
  18. secator/runners/task.py +6 -4
  19. secator/runners/workflow.py +13 -11
  20. secator/tasks/_categories.py +14 -19
  21. secator/tasks/cariddi.py +2 -1
  22. secator/tasks/dalfox.py +2 -0
  23. secator/tasks/dirsearch.py +5 -7
  24. secator/tasks/dnsx.py +1 -0
  25. secator/tasks/dnsxbrute.py +1 -0
  26. secator/tasks/feroxbuster.py +6 -7
  27. secator/tasks/ffuf.py +4 -7
  28. secator/tasks/gau.py +1 -4
  29. secator/tasks/gf.py +2 -1
  30. secator/tasks/gospider.py +1 -0
  31. secator/tasks/grype.py +47 -47
  32. secator/tasks/h8mail.py +5 -6
  33. secator/tasks/httpx.py +24 -18
  34. secator/tasks/katana.py +11 -15
  35. secator/tasks/maigret.py +3 -3
  36. secator/tasks/mapcidr.py +1 -0
  37. secator/tasks/msfconsole.py +3 -1
  38. secator/tasks/naabu.py +2 -1
  39. secator/tasks/nmap.py +14 -17
  40. secator/tasks/nuclei.py +4 -3
  41. secator/tasks/searchsploit.py +4 -2
  42. secator/tasks/subfinder.py +1 -0
  43. secator/tasks/wpscan.py +11 -13
  44. secator/utils.py +64 -82
  45. secator/utils_test.py +3 -2
  46. secator-0.3.5.dist-info/METADATA +411 -0
  47. secator-0.3.5.dist-info/RECORD +100 -0
  48. {secator-0.0.1.dist-info → secator-0.3.5.dist-info}/WHEEL +1 -2
  49. secator-0.0.1.dist-info/METADATA +0 -199
  50. secator-0.0.1.dist-info/RECORD +0 -114
  51. secator-0.0.1.dist-info/top_level.txt +0 -2
  52. tests/__init__.py +0 -0
  53. tests/integration/__init__.py +0 -0
  54. tests/integration/inputs.py +0 -42
  55. tests/integration/outputs.py +0 -392
  56. tests/integration/test_scans.py +0 -82
  57. tests/integration/test_tasks.py +0 -103
  58. tests/integration/test_workflows.py +0 -163
  59. tests/performance/__init__.py +0 -0
  60. tests/performance/loadtester.py +0 -56
  61. tests/unit/__init__.py +0 -0
  62. tests/unit/test_celery.py +0 -39
  63. tests/unit/test_scans.py +0 -0
  64. tests/unit/test_serializers.py +0 -51
  65. tests/unit/test_tasks.py +0 -348
  66. tests/unit/test_workflows.py +0 -96
  67. {secator-0.0.1.dist-info → secator-0.3.5.dist-info}/entry_points.txt +0 -0
  68. {secator-0.0.1.dist-info → secator-0.3.5.dist-info/licenses}/LICENSE +0 -0
secator/decorators.py CHANGED
@@ -1,11 +1,11 @@
1
+ import sys
1
2
  from collections import OrderedDict
2
3
 
3
4
  import rich_click as click
4
5
  from rich_click.rich_click import _get_rich_console
5
6
  from rich_click.rich_group import RichGroup
6
7
 
7
- from secator.celery import is_celery_worker_alive
8
- from secator.definitions import OPT_NOT_SUPPORTED
8
+ from secator.definitions import ADDONS_ENABLED, OPT_NOT_SUPPORTED
9
9
  from secator.runners import Scan, Task, Workflow
10
10
  from secator.utils import (deduplicate, expand_input, get_command_category,
11
11
  get_command_cls)
@@ -38,18 +38,47 @@ class OrderedGroup(RichGroup):
38
38
  super(OrderedGroup, self).__init__(name, commands, **attrs)
39
39
  self.commands = commands or OrderedDict()
40
40
 
41
+ def command(self, *args, **kwargs):
42
+ """Behaves the same as `click.Group.command()` but supports aliases.
43
+ """
44
+ def decorator(f):
45
+ aliases = kwargs.pop("aliases", None)
46
+ if aliases:
47
+ max_width = _get_rich_console().width
48
+ aliases_str = ', '.join(f'[bold cyan]{alias}[/]' for alias in aliases)
49
+ padding = max_width // 4
50
+
51
+ name = kwargs.pop("name", None)
52
+ if not name:
53
+ raise click.UsageError("`name` command argument is required when using aliases.")
54
+
55
+ f.__doc__ = f.__doc__ or '\0'.ljust(padding+1)
56
+ f.__doc__ = f'{f.__doc__:<{padding}}[dim](aliases)[/] {aliases_str}'
57
+ base_command = super(OrderedGroup, self).command(
58
+ name, *args, **kwargs
59
+ )(f)
60
+ for alias in aliases:
61
+ cmd = super(OrderedGroup, self).command(alias, *args, hidden=True, **kwargs)(f)
62
+ cmd.help = f"Alias for '{name}'.\n\n{cmd.help}"
63
+ cmd.params = base_command.params
64
+
65
+ else:
66
+ cmd = super(OrderedGroup, self).command(*args, **kwargs)(f)
67
+
68
+ return cmd
69
+ return decorator
70
+
41
71
  def group(self, *args, **kwargs):
42
- """Behaves the same as `click.Group.group()` except if passed
43
- a list of names, all after the first will be aliases for the first.
72
+ """Behaves the same as `click.Group.group()` but supports aliases.
44
73
  """
45
74
  def decorator(f):
46
75
  aliases = kwargs.pop('aliases', [])
47
76
  aliased_group = []
48
77
  if aliases:
49
78
  max_width = _get_rich_console().width
50
- # we have a list so create group aliases
51
79
  aliases_str = ', '.join(f'[bold cyan]{alias}[/]' for alias in aliases)
52
80
  padding = max_width // 4
81
+ f.__doc__ = f.__doc__ or '\0'.ljust(padding+1)
53
82
  f.__doc__ = f'{f.__doc__:<{padding}}[dim](aliases)[/] {aliases_str}'
54
83
  for alias in aliases:
55
84
  grp = super(OrderedGroup, self).group(
@@ -143,6 +172,7 @@ def decorate_command_options(opts):
143
172
  for opt_name, opt_conf in reversed_opts.items():
144
173
  conf = opt_conf.copy()
145
174
  short = conf.pop('short', None)
175
+ conf.pop('internal', False)
146
176
  conf.pop('prefix', None)
147
177
  long = f'--{opt_name}'
148
178
  short = f'-{short}' if short else f'-{opt_name}'
@@ -185,6 +215,7 @@ def register_runner(cli_endpoint, config):
185
215
  short_help += f' [dim]alias: {config.alias}'
186
216
  fmt_opts['print_start'] = True
187
217
  fmt_opts['print_run_summary'] = True
218
+ fmt_opts['print_progress'] = False
188
219
  runner_cls = Scan
189
220
 
190
221
  elif cli_endpoint.name == 'workflow':
@@ -199,6 +230,7 @@ def register_runner(cli_endpoint, config):
199
230
  short_help = f'{short_help:<55} [dim](alias)[/][bold cyan] {config.alias}'
200
231
  fmt_opts['print_start'] = True
201
232
  fmt_opts['print_run_summary'] = True
233
+ fmt_opts['print_progress'] = False
202
234
  runner_cls = Workflow
203
235
 
204
236
  elif cli_endpoint.name == 'task':
@@ -243,11 +275,12 @@ def register_runner(cli_endpoint, config):
243
275
  # opts.update(unknown_opts)
244
276
  targets = opts.pop(input_type)
245
277
  targets = expand_input(targets)
246
- if sync or show:
278
+ if sync or show or not ADDONS_ENABLED['worker']:
247
279
  sync = True
248
280
  elif worker:
249
281
  sync = False
250
282
  else: # automatically run in worker if it's alive
283
+ from secator.celery import is_celery_worker_alive
251
284
  sync = not is_celery_worker_alive()
252
285
  opts['sync'] = sync
253
286
  opts.update({
@@ -260,6 +293,9 @@ def register_runner(cli_endpoint, config):
260
293
  # Build hooks from driver name
261
294
  hooks = {}
262
295
  if driver == 'mongodb':
296
+ if not ADDONS_ENABLED['mongo']:
297
+ _get_rich_console().print('[bold red]Missing MongoDB dependencies: please run `secator install addons mongodb`[/].')
298
+ sys.exit(1)
263
299
  from secator.hooks.mongodb import MONGODB_HOOKS
264
300
  hooks = MONGODB_HOOKS
265
301
 
secator/definitions.py CHANGED
@@ -1,49 +1,45 @@
1
1
  #!/usr/bin/python
2
2
 
3
3
  import os
4
+ import requests
5
+
6
+ from dotenv import find_dotenv, load_dotenv
7
+ from importlib.metadata import version
8
+
9
+ from secator.rich import console
4
10
 
5
- from pkg_resources import get_distribution
6
- from dotenv import load_dotenv, find_dotenv
7
11
  load_dotenv(find_dotenv(usecwd=True), override=False)
8
12
 
9
13
  # Globals
10
- VERSION = get_distribution('secator').version
14
+ VERSION = version('secator')
11
15
  ASCII = f"""
12
- __
16
+ __
13
17
  ________ _________ _/ /_____ _____
14
18
  / ___/ _ \/ ___/ __ `/ __/ __ \/ ___/
15
19
  (__ / __/ /__/ /_/ / /_/ /_/ / /
16
20
  /____/\___/\___/\__,_/\__/\____/_/ v{VERSION}
17
21
 
18
- freelabz.com
22
+ freelabz.com
19
23
  """ # noqa: W605,W291
20
24
 
21
25
  # Secator folders
22
26
  ROOT_FOLDER = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
23
- CONFIGS_FOLDER = ROOT_FOLDER + '/secator/configs'
27
+ LIB_FOLDER = ROOT_FOLDER + '/secator'
28
+ CONFIGS_FOLDER = LIB_FOLDER + '/configs'
24
29
  EXTRA_CONFIGS_FOLDER = os.environ.get('SECATOR_EXTRA_CONFIGS_FOLDER')
30
+ BIN_FOLDER = os.environ.get('SECATOR_BIN_FOLDER', f'{os.path.expanduser("~")}/.local/bin')
25
31
  DATA_FOLDER = os.environ.get('SECATOR_DATA_FOLDER', f'{os.path.expanduser("~")}/.secator')
26
- TASKS_FOLDER = os.environ.get('SECATOR_TASKS_FOLDER', f'{DATA_FOLDER}/tasks')
27
32
  REPORTS_FOLDER = os.environ.get('SECATOR_REPORTS_FOLDER', f'{DATA_FOLDER}/reports')
28
- WORDLISTS_FOLDER = os.environ.get('SECATOR_WORDLISTS_FOLDER', '/usr/share/seclists')
33
+ WORDLISTS_FOLDER = os.environ.get('SECATOR_WORDLISTS_FOLDER', f'{DATA_FOLDER}/wordlists')
29
34
  SCRIPTS_FOLDER = f'{ROOT_FOLDER}/scripts'
30
35
  CVES_FOLDER = f'{DATA_FOLDER}/cves'
31
36
  PAYLOADS_FOLDER = f'{DATA_FOLDER}/payloads'
32
37
  REVSHELLS_FOLDER = f'{DATA_FOLDER}/revshells'
33
- os.makedirs(DATA_FOLDER, exist_ok=True)
34
- os.makedirs(TASKS_FOLDER, exist_ok=True)
35
- os.makedirs(REPORTS_FOLDER, exist_ok=True)
36
- os.makedirs(WORDLISTS_FOLDER, exist_ok=True)
37
- os.makedirs(SCRIPTS_FOLDER, exist_ok=True)
38
- os.makedirs(CVES_FOLDER, exist_ok=True)
39
- os.makedirs(PAYLOADS_FOLDER, exist_ok=True)
40
- os.makedirs(REVSHELLS_FOLDER, exist_ok=True)
38
+ TESTS_FOLDER = f'{ROOT_FOLDER}/tests'
41
39
 
42
40
  # Celery local fs folders
43
41
  CELERY_DATA_FOLDER = f'{DATA_FOLDER}/celery/data'
44
42
  CELERY_RESULTS_FOLDER = f'{DATA_FOLDER}/celery/results'
45
- os.makedirs(CELERY_DATA_FOLDER, exist_ok=True)
46
- os.makedirs(CELERY_RESULTS_FOLDER, exist_ok=True)
47
43
 
48
44
  # Environment variables
49
45
  DEBUG = int(os.environ.get('DEBUG', '0'))
@@ -57,11 +53,12 @@ CELERY_BROKER_VISIBILITY_TIMEOUT = int(os.environ.get('CELERY_BROKER_VISIBILITY_
57
53
  CELERY_OVERRIDE_DEFAULT_LOGGING = bool(int(os.environ.get('CELERY_OVERRIDE_DEFAULT_LOGGING', 1)))
58
54
  GOOGLE_DRIVE_PARENT_FOLDER_ID = os.environ.get('GOOGLE_DRIVE_PARENT_FOLDER_ID')
59
55
  GOOGLE_CREDENTIALS_PATH = os.environ.get('GOOGLE_CREDENTIALS_PATH')
56
+ GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN')
60
57
 
61
58
  # Defaults HTTP and Proxy settings
62
59
  DEFAULT_SOCKS5_PROXY = os.environ.get('SOCKS5_PROXY', "socks5://127.0.0.1:9050")
63
60
  DEFAULT_HTTP_PROXY = os.environ.get('HTTP_PROXY', "https://127.0.0.1:9080")
64
- DEFAULT_STORE_HTTP_RESPONSES = bool(int(os.environ.get('STORE_HTTP_RESPONSES', 1)))
61
+ DEFAULT_STORE_HTTP_RESPONSES = bool(int(os.environ.get('DEFAULT_STORE_HTTP_RESPONSES', 1)))
65
62
  DEFAULT_PROXYCHAINS_COMMAND = "proxychains"
66
63
  DEFAULT_FREEPROXY_TIMEOUT = 1 # seconds
67
64
 
@@ -74,11 +71,14 @@ DEFAULT_HTTPX_FLAGS = os.environ.get('DEFAULT_HTTPX_FLAGS', '-td')
74
71
  DEFAULT_KATANA_FLAGS = os.environ.get('DEFAULT_KATANA_FLAGS', '-jc -js-crawl -known-files all -or -ob')
75
72
  DEFAULT_NUCLEI_FLAGS = os.environ.get('DEFAULT_NUCLEI_FLAGS', '-stats -sj -si 20 -hm -or')
76
73
  DEFAULT_FEROXBUSTER_FLAGS = os.environ.get('DEFAULT_FEROXBUSTER_FLAGS', '--auto-bail --no-state')
77
- DEFAULT_PROGRESS_UPDATE_FREQUENCY = 10
74
+ DEFAULT_PROGRESS_UPDATE_FREQUENCY = int(os.environ.get('DEFAULT_PROGRESS_UPDATE_FREQUENCY', 60))
75
+ DEFAULT_SKIP_CVE_SEARCH = bool(int(os.environ.get('DEFAULT_SKIP_CVE_SEARCH', 0)))
78
76
 
79
77
  # Default wordlists
80
- DEFAULT_HTTP_WORDLIST = os.environ.get('DEFAULT_HTTP_WORDLIST', f'{WORDLISTS_FOLDER}/Fuzzing/fuzz-Bo0oM.txt')
81
- DEFAULT_DNS_WORDLIST = os.environ.get('DEFAULT_DNS_WORDLIST', f'{WORDLISTS_FOLDER}/Discovery/DNS/combined_subdomains.txt') # noqa:E501
78
+ DEFAULT_HTTP_WORDLIST = os.environ.get('DEFAULT_HTTP_WORDLIST', f'{WORDLISTS_FOLDER}/fuzz-Bo0oM.txt')
79
+ DEFAULT_HTTP_WORDLIST_URL = 'https://raw.githubusercontent.com/Bo0oM/fuzz.txt/master/fuzz.txt'
80
+ DEFAULT_DNS_WORDLIST = os.environ.get('DEFAULT_DNS_WORDLIST', f'{WORDLISTS_FOLDER}/combined_subdomains.txt')
81
+ DEFAULT_DNS_WORDLIST_URL = 'https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/combined_subdomains.txt' # noqa: E501
82
82
 
83
83
  # Constants
84
84
  OPT_NOT_SUPPORTED = -1
@@ -87,7 +87,6 @@ OPT_PIPE_INPUT = -1
87
87
  # Vocab
88
88
  ALIVE = 'alive'
89
89
  AUTO_CALIBRATION = 'auto_calibration'
90
- COOKIES = 'cookies'
91
90
  CONTENT_TYPE = 'content_type'
92
91
  CONTENT_LENGTH = 'content_length'
93
92
  CIDR_RANGE = 'cidr_range'
@@ -98,7 +97,6 @@ DOMAIN = 'domain'
98
97
  DEPTH = 'depth'
99
98
  EXTRA_DATA = 'extra_data'
100
99
  EMAIL = 'email'
101
- FAILED_HTTP_STATUS = -1
102
100
  FILTER_CODES = 'filter_codes'
103
101
  FILTER_WORDS = 'filter_words'
104
102
  FOLLOW_REDIRECT = 'follow_redirect'
@@ -106,9 +104,7 @@ FILTER_REGEX = 'filter_regex'
106
104
  FILTER_SIZE = 'filter_size'
107
105
  HEADER = 'header'
108
106
  HOST = 'host'
109
- INPUT = 'input'
110
107
  IP = 'ip'
111
- JSON = 'json'
112
108
  LINES = 'lines'
113
109
  METHOD = 'method'
114
110
  MATCH_CODES = 'match_codes'
@@ -117,13 +113,10 @@ MATCH_SIZE = 'match_size'
117
113
  MATCH_WORDS = 'match_words'
118
114
  OUTPUT_PATH = 'output_path'
119
115
  PATH = 'path'
120
- PAYLOAD = 'payload'
121
116
  PERCENT = 'percent'
122
- PROBE = 'probe'
123
117
  PORTS = 'ports'
124
118
  PORT = 'port'
125
119
  PROXY = 'proxy'
126
- QUIET = 'quiet'
127
120
  RATE_LIMIT = 'rate_limit'
128
121
  RETRIES = 'retries'
129
122
  TAGS = 'tags'
@@ -135,19 +128,16 @@ TYPE = 'type'
135
128
  URL = 'url'
136
129
  USER_AGENT = 'user_agent'
137
130
  USERNAME = 'username'
138
- SCREENSHOT_PATH = 'screenshot_path'
139
131
  STORED_RESPONSE_PATH = 'stored_response_path'
140
132
  SCRIPT = 'script'
141
133
  SERVICE_NAME = 'service_name'
142
134
  SOURCES = 'sources'
143
135
  STATE = 'state'
144
136
  STATUS_CODE = 'status_code'
145
- SUBDOMAIN = 'subdomain'
146
137
  TECH = 'tech'
147
138
  TITLE = 'title'
148
139
  SITE_NAME = 'site_name'
149
140
  SERVICE_NAME = 'service_name'
150
- VULN = 'vulnerability'
151
141
  CONFIDENCE = 'confidence'
152
142
  CVSS_SCORE = 'cvss_score'
153
143
  DESCRIPTION = 'description'
@@ -159,7 +149,88 @@ REFERENCE = 'reference'
159
149
  REFERENCES = 'references'
160
150
  SEVERITY = 'severity'
161
151
  TAGS = 'tags'
162
- VULN_TYPE = 'type'
163
152
  WEBSERVER = 'webserver'
164
153
  WORDLIST = 'wordlist'
165
154
  WORDS = 'words'
155
+
156
+
157
+ # Create all folders
158
+ for folder in [BIN_FOLDER, DATA_FOLDER, REPORTS_FOLDER, WORDLISTS_FOLDER, CVES_FOLDER, PAYLOADS_FOLDER,
159
+ REVSHELLS_FOLDER, CELERY_DATA_FOLDER, CELERY_RESULTS_FOLDER]:
160
+ if not os.path.exists(folder):
161
+ console.print(f'[bold turquoise4]Creating folder {folder} ...[/] ', end='')
162
+ os.makedirs(folder)
163
+ console.print('[bold green]ok.[/]')
164
+
165
+
166
+ # Download default wordlists
167
+ for wordlist in ['HTTP', 'DNS']:
168
+ wordlist_path = globals()[f'DEFAULT_{wordlist}_WORDLIST']
169
+ wordlist_url = globals()[f'DEFAULT_{wordlist}_WORDLIST_URL']
170
+ if not os.path.exists(wordlist_path):
171
+ try:
172
+ console.print(f'[bold turquoise4]Downloading default {wordlist} wordlist {wordlist_path} ...[/] ', end='')
173
+ resp = requests.get(wordlist_url)
174
+ with open(wordlist_path, 'w') as f:
175
+ f.write(resp.text)
176
+ console.print('[bold green]ok.[/]')
177
+ except requests.exceptions.RequestException as e:
178
+ console.print(f'[bold green]failed ({type(e).__name__}).[/]')
179
+ pass
180
+
181
+ ADDONS_ENABLED = {}
182
+
183
+ # Check worker addon
184
+ try:
185
+ import eventlet # noqa: F401
186
+ ADDONS_ENABLED['worker'] = True
187
+ except ModuleNotFoundError:
188
+ ADDONS_ENABLED['worker'] = False
189
+
190
+ # Check google addon
191
+ try:
192
+ import gspread # noqa: F401
193
+ ADDONS_ENABLED['google'] = True
194
+ except ModuleNotFoundError:
195
+ ADDONS_ENABLED['google'] = False
196
+
197
+ # Check mongodb addon
198
+ try:
199
+ import pymongo # noqa: F401
200
+ ADDONS_ENABLED['mongodb'] = True
201
+ except ModuleNotFoundError:
202
+ ADDONS_ENABLED['mongodb'] = False
203
+
204
+ # Check redis addon
205
+ try:
206
+ import redis # noqa: F401
207
+ ADDONS_ENABLED['redis'] = True
208
+ except ModuleNotFoundError:
209
+ ADDONS_ENABLED['redis'] = False
210
+
211
+ # Check dev addon
212
+ try:
213
+ import flake8 # noqa: F401
214
+ ADDONS_ENABLED['dev'] = True
215
+ except ModuleNotFoundError:
216
+ ADDONS_ENABLED['dev'] = False
217
+
218
+ # Check build addon
219
+ try:
220
+ import hatch # noqa: F401
221
+ ADDONS_ENABLED['build'] = True
222
+ except ModuleNotFoundError:
223
+ ADDONS_ENABLED['build'] = False
224
+
225
+ # Check trace addon
226
+ try:
227
+ import memray # noqa: F401
228
+ ADDONS_ENABLED['trace'] = True
229
+ except ModuleNotFoundError:
230
+ ADDONS_ENABLED['trace'] = False
231
+
232
+ # Check dev package
233
+ if os.path.exists(f'{ROOT_FOLDER}/pyproject.toml'):
234
+ DEV_PACKAGE = True
235
+ else:
236
+ DEV_PACKAGE = False
secator/exporters/csv.py CHANGED
@@ -6,7 +6,6 @@ from secator.rich import console
6
6
 
7
7
  class CsvExporter(Exporter):
8
8
  def send(self):
9
- title = self.report.data['info']['title']
10
9
  results = self.report.data['results']
11
10
  csv_paths = []
12
11
 
@@ -15,7 +14,7 @@ class CsvExporter(Exporter):
15
14
  if not items:
16
15
  continue
17
16
  keys = list(items[0].keys())
18
- csv_path = f'{self.report.output_folder}/{title}_{output_type}_{self.report.timestamp}.csv'
17
+ csv_path = f'{self.report.output_folder}/report_{output_type}.csv'
19
18
  csv_paths.append(csv_path)
20
19
  with open(csv_path, 'w', newline='') as output_file:
21
20
  dict_writer = _csv.DictWriter(output_file, keys)
@@ -55,7 +55,7 @@ class GdriveExporter(Exporter):
55
55
  k.replace('_', ' ').upper()
56
56
  for k in list(items[0].keys())
57
57
  ]
58
- csv_path = f'{self.report.output_folder}/{title}_{output_type}_{self.report.timestamp}.csv'
58
+ csv_path = f'{self.report.output_folder}/report_{output_type}.csv'
59
59
  if not os.path.exists(csv_path):
60
60
  console.print(
61
61
  f'Unable to find CSV at {csv_path}. For Google sheets reports, please enable CSV reports as well.')
secator/exporters/json.py CHANGED
@@ -5,8 +5,7 @@ from secator.serializers.dataclass import dumps_dataclass
5
5
 
6
6
  class JsonExporter(Exporter):
7
7
  def send(self):
8
- title = self.report.data['info']['title']
9
- json_path = f'{self.report.output_folder}/{title}_{self.report.timestamp}.json'
8
+ json_path = f'{self.report.output_folder}/report.json'
10
9
 
11
10
  # Save JSON report to file
12
11
  with open(json_path, 'w') as f:
secator/exporters/txt.py CHANGED
@@ -4,7 +4,6 @@ from secator.rich import console
4
4
 
5
5
  class TxtExporter(Exporter):
6
6
  def send(self):
7
- title = self.report.data['info']['title']
8
7
  results = self.report.data['results']
9
8
  txt_paths = []
10
9
 
@@ -12,7 +11,7 @@ class TxtExporter(Exporter):
12
11
  items = [str(i) for i in items]
13
12
  if not items:
14
13
  continue
15
- txt_path = f'{self.report.output_folder}/{title}_{output_type}_{self.report.timestamp}.txt'
14
+ txt_path = f'{self.report.output_folder}/report_{output_type}.txt'
16
15
  with open(txt_path, 'w') as f:
17
16
  f.write('\n'.join(items))
18
17
  txt_paths.append(txt_path)
secator/hooks/mongodb.py CHANGED
@@ -1,40 +1,43 @@
1
- from bson.objectid import ObjectId
2
- import os
3
1
  import logging
2
+ import os
4
3
  import time
5
4
 
5
+ import pymongo
6
+ from bson.objectid import ObjectId
6
7
  from celery import shared_task
7
8
 
8
9
  from secator.definitions import DEFAULT_PROGRESS_UPDATE_FREQUENCY
9
- from secator.runners import Task, Workflow, Scan
10
10
  from secator.output_types import OUTPUT_TYPES
11
- from secator.utils import debug
11
+ from secator.runners import Scan, Task, Workflow
12
+ from secator.utils import debug, escape_mongodb_url
12
13
 
13
- import pymongo
14
14
  # import gevent.monkey
15
15
  # gevent.monkey.patch_all()
16
16
 
17
17
  MONGODB_URL = os.environ.get('MONGODB_URL', 'mongodb://localhost')
18
18
  MONGODB_UPDATE_FREQUENCY = int(os.environ.get('MONGODB_UPDATE_FREQUENCY', DEFAULT_PROGRESS_UPDATE_FREQUENCY))
19
19
  MAX_POOL_SIZE = 100
20
- client = pymongo.MongoClient(MONGODB_URL, maxPoolSize=MAX_POOL_SIZE)
21
20
 
22
21
  logger = logging.getLogger(__name__)
23
22
 
23
+ client = pymongo.MongoClient(escape_mongodb_url(MONGODB_URL), maxPoolSize=MAX_POOL_SIZE)
24
+
24
25
 
25
26
  def update_runner(self):
26
27
  db = client.main
27
28
  type = self.config.type
28
29
  collection = f'{type}s'
29
30
  update = self.toDict()
31
+ debug_obj = {'type': 'runner', 'name': self.name, 'status': self.status}
30
32
  chunk = update.get('chunk')
31
33
  _id = self.context.get(f'{type}_chunk_id') if chunk else self.context.get(f'{type}_id')
34
+ debug('update', sub='hooks.mongodb', id=_id, obj=update, obj_after=True, level=4)
32
35
  start_time = time.time()
33
36
  if _id:
34
37
  delta = start_time - self.last_updated if self.last_updated else MONGODB_UPDATE_FREQUENCY
35
38
  if self.last_updated and delta < MONGODB_UPDATE_FREQUENCY and self.status == 'RUNNING':
36
39
  debug(f'skipped ({delta:>.2f}s < {MONGODB_UPDATE_FREQUENCY}s)',
37
- sub='hooks.mongodb', id=_id, obj={self.name: self.status}, obj_after=False, level=3)
40
+ sub='hooks.mongodb', id=_id, obj=debug_obj, obj_after=False, level=3)
38
41
  return
39
42
  db = client.main
40
43
  start_time = time.time()
@@ -42,8 +45,7 @@ def update_runner(self):
42
45
  end_time = time.time()
43
46
  elapsed = end_time - start_time
44
47
  debug(
45
- f'[dim gold4]updated in {elapsed:.4f}s[/]',
46
- sub='hooks.mongodb', id=_id, obj={self.name: self.status}, obj_after=False, level=2)
48
+ f'[dim gold4]updated in {elapsed:.4f}s[/]', sub='hooks.mongodb', id=_id, obj=debug_obj, obj_after=False, level=2)
47
49
  self.last_updated = start_time
48
50
  else: # sync update and save result to runner object
49
51
  runner = db[collection].insert_one(update)
@@ -54,9 +56,7 @@ def update_runner(self):
54
56
  self.context[f'{type}_id'] = _id
55
57
  end_time = time.time()
56
58
  elapsed = end_time - start_time
57
- debug(
58
- f'created in {elapsed:.4f}s',
59
- sub='hooks.mongodb', id=_id, obj={self.name: self.status}, obj_after=False, level=2)
59
+ debug(f'created in {elapsed:.4f}s', sub='hooks.mongodb', id=_id, obj=debug_obj, obj_after=False, level=2)
60
60
 
61
61
 
62
62
  def update_finding(self, item):