secator 0.12.0__tar.gz → 0.14.0__tar.gz

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 (206) hide show
  1. {secator-0.12.0 → secator-0.14.0}/.docker/Dockerfile.alpine +2 -0
  2. {secator-0.12.0 → secator-0.14.0}/.docker/Dockerfile.arch +2 -2
  3. {secator-0.12.0 → secator-0.14.0}/.docker/Dockerfile.debian +2 -2
  4. {secator-0.12.0 → secator-0.14.0}/.docker/Dockerfile.kali +2 -2
  5. {secator-0.12.0 → secator-0.14.0}/.docker/Dockerfile.osx +2 -2
  6. {secator-0.12.0 → secator-0.14.0}/.docker/Dockerfile.ubuntu +2 -2
  7. {secator-0.12.0 → secator-0.14.0}/CHANGELOG.md +24 -0
  8. {secator-0.12.0 → secator-0.14.0}/PKG-INFO +1 -1
  9. {secator-0.12.0 → secator-0.14.0}/pyproject.toml +1 -1
  10. {secator-0.12.0 → secator-0.14.0}/secator/cli.py +199 -66
  11. secator-0.14.0/secator/configs/profiles/aggressive.yaml +8 -0
  12. secator-0.14.0/secator/configs/profiles/default.yaml +8 -0
  13. secator-0.14.0/secator/configs/profiles/insane.yaml +8 -0
  14. secator-0.14.0/secator/configs/profiles/paranoid.yaml +8 -0
  15. secator-0.14.0/secator/configs/profiles/polite.yaml +8 -0
  16. secator-0.14.0/secator/configs/profiles/sneaky.yaml +8 -0
  17. secator-0.14.0/secator/configs/profiles/tor.yaml +5 -0
  18. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/host_recon.yaml +11 -2
  19. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/url_dirsearch.yaml +5 -0
  20. {secator-0.12.0 → secator-0.14.0}/secator/decorators.py +1 -0
  21. {secator-0.12.0 → secator-0.14.0}/secator/definitions.py +0 -4
  22. {secator-0.12.0 → secator-0.14.0}/secator/installer.py +29 -15
  23. {secator-0.12.0 → secator-0.14.0}/secator/report.py +2 -2
  24. {secator-0.12.0 → secator-0.14.0}/secator/runners/_base.py +32 -1
  25. {secator-0.12.0 → secator-0.14.0}/secator/runners/_helpers.py +13 -2
  26. {secator-0.12.0 → secator-0.14.0}/secator/runners/command.py +2 -1
  27. {secator-0.12.0 → secator-0.14.0}/secator/runners/scan.py +1 -0
  28. {secator-0.12.0 → secator-0.14.0}/secator/runners/task.py +1 -0
  29. {secator-0.12.0 → secator-0.14.0}/secator/tasks/_categories.py +2 -2
  30. {secator-0.12.0 → secator-0.14.0}/secator/tasks/arjun.py +2 -1
  31. {secator-0.12.0 → secator-0.14.0}/secator/tasks/bbot.py +30 -4
  32. {secator-0.12.0 → secator-0.14.0}/secator/tasks/bup.py +2 -1
  33. {secator-0.12.0 → secator-0.14.0}/secator/tasks/cariddi.py +15 -3
  34. {secator-0.12.0 → secator-0.14.0}/secator/tasks/dalfox.py +2 -1
  35. {secator-0.12.0 → secator-0.14.0}/secator/tasks/dirsearch.py +1 -1
  36. {secator-0.12.0 → secator-0.14.0}/secator/tasks/dnsx.py +2 -1
  37. {secator-0.12.0 → secator-0.14.0}/secator/tasks/dnsxbrute.py +2 -1
  38. {secator-0.12.0 → secator-0.14.0}/secator/tasks/feroxbuster.py +3 -2
  39. {secator-0.12.0 → secator-0.14.0}/secator/tasks/ffuf.py +2 -1
  40. {secator-0.12.0 → secator-0.14.0}/secator/tasks/gau.py +2 -1
  41. {secator-0.12.0 → secator-0.14.0}/secator/tasks/gitleaks.py +4 -3
  42. {secator-0.12.0 → secator-0.14.0}/secator/tasks/gospider.py +3 -2
  43. {secator-0.12.0 → secator-0.14.0}/secator/tasks/grype.py +1 -0
  44. {secator-0.12.0 → secator-0.14.0}/secator/tasks/h8mail.py +2 -1
  45. {secator-0.12.0 → secator-0.14.0}/secator/tasks/httpx.py +3 -2
  46. {secator-0.12.0 → secator-0.14.0}/secator/tasks/katana.py +4 -3
  47. {secator-0.12.0 → secator-0.14.0}/secator/tasks/maigret.py +1 -1
  48. {secator-0.12.0 → secator-0.14.0}/secator/tasks/mapcidr.py +2 -1
  49. {secator-0.12.0 → secator-0.14.0}/secator/tasks/msfconsole.py +4 -3
  50. {secator-0.12.0 → secator-0.14.0}/secator/tasks/naabu.py +4 -2
  51. {secator-0.12.0 → secator-0.14.0}/secator/tasks/nuclei.py +15 -9
  52. {secator-0.12.0 → secator-0.14.0}/secator/tasks/searchsploit.py +3 -2
  53. {secator-0.12.0 → secator-0.14.0}/secator/tasks/subfinder.py +2 -1
  54. {secator-0.12.0 → secator-0.14.0}/secator/tasks/testssl.py +4 -3
  55. {secator-0.12.0 → secator-0.14.0}/secator/tasks/trivy.py +2 -2
  56. {secator-0.12.0 → secator-0.14.0}/secator/tasks/wafw00f.py +2 -1
  57. {secator-0.12.0 → secator-0.14.0}/secator/tasks/wpprobe.py +2 -1
  58. {secator-0.12.0 → secator-0.14.0}/secator/tasks/wpscan.py +6 -3
  59. {secator-0.12.0 → secator-0.14.0}/secator/template.py +1 -1
  60. {secator-0.12.0 → secator-0.14.0}/tests/integration/test_tasks.py +8 -1
  61. secator-0.12.0/secator/configs/profiles/aggressive.yaml +0 -7
  62. secator-0.12.0/secator/configs/profiles/default.yaml +0 -9
  63. secator-0.12.0/secator/configs/profiles/stealth.yaml +0 -7
  64. {secator-0.12.0 → secator-0.14.0}/.docker/build_all.sh +0 -0
  65. {secator-0.12.0 → secator-0.14.0}/.dockerignore +0 -0
  66. {secator-0.12.0 → secator-0.14.0}/.flake8 +0 -0
  67. {secator-0.12.0 → secator-0.14.0}/.gitignore +0 -0
  68. {secator-0.12.0 → secator-0.14.0}/CONTRIBUTING.md +0 -0
  69. {secator-0.12.0 → secator-0.14.0}/Dockerfile +0 -0
  70. {secator-0.12.0 → secator-0.14.0}/LICENSE +0 -0
  71. {secator-0.12.0 → secator-0.14.0}/README.md +0 -0
  72. {secator-0.12.0 → secator-0.14.0}/SECURITY.md +0 -0
  73. {secator-0.12.0 → secator-0.14.0}/cloudbuild.yaml +0 -0
  74. {secator-0.12.0 → secator-0.14.0}/helm/.helmignore +0 -0
  75. {secator-0.12.0 → secator-0.14.0}/helm/Chart.yaml +0 -0
  76. {secator-0.12.0 → secator-0.14.0}/helm/templates/redis-service.yaml +0 -0
  77. {secator-0.12.0 → secator-0.14.0}/helm/templates/redis.yaml +0 -0
  78. {secator-0.12.0 → secator-0.14.0}/helm/templates/secator-manager.yaml +0 -0
  79. {secator-0.12.0 → secator-0.14.0}/helm/templates/secator-worker.yaml +0 -0
  80. {secator-0.12.0 → secator-0.14.0}/helm/values.yaml +0 -0
  81. {secator-0.12.0 → secator-0.14.0}/scripts/download_cves.sh +0 -0
  82. {secator-0.12.0 → secator-0.14.0}/scripts/install.sh +0 -0
  83. {secator-0.12.0 → secator-0.14.0}/scripts/install_asciinema.sh +0 -0
  84. {secator-0.12.0 → secator-0.14.0}/scripts/install_go.sh +0 -0
  85. {secator-0.12.0 → secator-0.14.0}/scripts/install_ruby.sh +0 -0
  86. {secator-0.12.0 → secator-0.14.0}/scripts/msf/exploit_cve.rc +0 -0
  87. {secator-0.12.0 → secator-0.14.0}/scripts/msf/ftp_anonymous.rc +0 -0
  88. {secator-0.12.0 → secator-0.14.0}/scripts/msf/ftp_version.rc +0 -0
  89. {secator-0.12.0 → secator-0.14.0}/scripts/msf/ftp_vsftpd_234_backdoor.rc +0 -0
  90. {secator-0.12.0 → secator-0.14.0}/scripts/msf/redis.rc +0 -0
  91. {secator-0.12.0 → secator-0.14.0}/scripts/stories/STORY.md +0 -0
  92. {secator-0.12.0 → secator-0.14.0}/scripts/stories/aliases.sh +0 -0
  93. {secator-0.12.0 → secator-0.14.0}/scripts/stories/demo.sh +0 -0
  94. {secator-0.12.0 → secator-0.14.0}/scripts/stories/fmt.sh +0 -0
  95. {secator-0.12.0 → secator-0.14.0}/scripts/stories/input.sh +0 -0
  96. {secator-0.12.0 → secator-0.14.0}/scripts/stories/pipe.sh +0 -0
  97. {secator-0.12.0 → secator-0.14.0}/scripts/stories/short_demo.sh +0 -0
  98. {secator-0.12.0 → secator-0.14.0}/secator/.gitignore +0 -0
  99. {secator-0.12.0 → secator-0.14.0}/secator/__init__.py +0 -0
  100. {secator-0.12.0 → secator-0.14.0}/secator/celery.py +0 -0
  101. {secator-0.12.0 → secator-0.14.0}/secator/celery_signals.py +0 -0
  102. {secator-0.12.0 → secator-0.14.0}/secator/celery_utils.py +0 -0
  103. {secator-0.12.0 → secator-0.14.0}/secator/config.py +0 -0
  104. {secator-0.12.0 → secator-0.14.0}/secator/configs/__init__.py +0 -0
  105. {secator-0.12.0 → secator-0.14.0}/secator/configs/profiles/__init__.py +0 -0
  106. {secator-0.12.0 → secator-0.14.0}/secator/configs/scans/__init__.py +0 -0
  107. {secator-0.12.0 → secator-0.14.0}/secator/configs/scans/domain.yaml +0 -0
  108. {secator-0.12.0 → secator-0.14.0}/secator/configs/scans/host.yaml +0 -0
  109. {secator-0.12.0 → secator-0.14.0}/secator/configs/scans/network.yaml +0 -0
  110. {secator-0.12.0 → secator-0.14.0}/secator/configs/scans/subdomain.yaml +0 -0
  111. {secator-0.12.0 → secator-0.14.0}/secator/configs/scans/url.yaml +0 -0
  112. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/__init__.py +0 -0
  113. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/cidr_recon.yaml +0 -0
  114. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/code_scan.yaml +0 -0
  115. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/subdomain_recon.yaml +0 -0
  116. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/url_bypass.yaml +0 -0
  117. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/url_crawl.yaml +0 -0
  118. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/url_fuzz.yaml +0 -0
  119. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/url_nuclei.yaml +0 -0
  120. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/url_params_fuzz.yaml +0 -0
  121. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/url_vuln.yaml +0 -0
  122. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/user_hunt.yaml +0 -0
  123. {secator-0.12.0 → secator-0.14.0}/secator/configs/workflows/wordpress.yaml +0 -0
  124. {secator-0.12.0 → secator-0.14.0}/secator/exporters/__init__.py +0 -0
  125. {secator-0.12.0 → secator-0.14.0}/secator/exporters/_base.py +0 -0
  126. {secator-0.12.0 → secator-0.14.0}/secator/exporters/console.py +0 -0
  127. {secator-0.12.0 → secator-0.14.0}/secator/exporters/csv.py +0 -0
  128. {secator-0.12.0 → secator-0.14.0}/secator/exporters/gdrive.py +0 -0
  129. {secator-0.12.0 → secator-0.14.0}/secator/exporters/json.py +0 -0
  130. {secator-0.12.0 → secator-0.14.0}/secator/exporters/table.py +0 -0
  131. {secator-0.12.0 → secator-0.14.0}/secator/exporters/txt.py +0 -0
  132. {secator-0.12.0 → secator-0.14.0}/secator/hooks/__init__.py +0 -0
  133. {secator-0.12.0 → secator-0.14.0}/secator/hooks/gcs.py +0 -0
  134. {secator-0.12.0 → secator-0.14.0}/secator/hooks/mongodb.py +0 -0
  135. {secator-0.12.0 → secator-0.14.0}/secator/output_types/__init__.py +0 -0
  136. {secator-0.12.0 → secator-0.14.0}/secator/output_types/_base.py +0 -0
  137. {secator-0.12.0 → secator-0.14.0}/secator/output_types/certificate.py +0 -0
  138. {secator-0.12.0 → secator-0.14.0}/secator/output_types/error.py +0 -0
  139. {secator-0.12.0 → secator-0.14.0}/secator/output_types/exploit.py +0 -0
  140. {secator-0.12.0 → secator-0.14.0}/secator/output_types/info.py +0 -0
  141. {secator-0.12.0 → secator-0.14.0}/secator/output_types/ip.py +0 -0
  142. {secator-0.12.0 → secator-0.14.0}/secator/output_types/port.py +0 -0
  143. {secator-0.12.0 → secator-0.14.0}/secator/output_types/progress.py +0 -0
  144. {secator-0.12.0 → secator-0.14.0}/secator/output_types/record.py +0 -0
  145. {secator-0.12.0 → secator-0.14.0}/secator/output_types/stat.py +0 -0
  146. {secator-0.12.0 → secator-0.14.0}/secator/output_types/state.py +0 -0
  147. {secator-0.12.0 → secator-0.14.0}/secator/output_types/subdomain.py +0 -0
  148. {secator-0.12.0 → secator-0.14.0}/secator/output_types/tag.py +0 -0
  149. {secator-0.12.0 → secator-0.14.0}/secator/output_types/target.py +0 -0
  150. {secator-0.12.0 → secator-0.14.0}/secator/output_types/url.py +0 -0
  151. {secator-0.12.0 → secator-0.14.0}/secator/output_types/user_account.py +0 -0
  152. {secator-0.12.0 → secator-0.14.0}/secator/output_types/vulnerability.py +0 -0
  153. {secator-0.12.0 → secator-0.14.0}/secator/output_types/warning.py +0 -0
  154. {secator-0.12.0 → secator-0.14.0}/secator/rich.py +0 -0
  155. {secator-0.12.0 → secator-0.14.0}/secator/runners/__init__.py +0 -0
  156. {secator-0.12.0 → secator-0.14.0}/secator/runners/celery.py +0 -0
  157. {secator-0.12.0 → secator-0.14.0}/secator/runners/workflow.py +0 -0
  158. {secator-0.12.0 → secator-0.14.0}/secator/scans/__init__.py +0 -0
  159. {secator-0.12.0 → secator-0.14.0}/secator/serializers/__init__.py +0 -0
  160. {secator-0.12.0 → secator-0.14.0}/secator/serializers/dataclass.py +0 -0
  161. {secator-0.12.0 → secator-0.14.0}/secator/serializers/json.py +0 -0
  162. {secator-0.12.0 → secator-0.14.0}/secator/serializers/regex.py +0 -0
  163. {secator-0.12.0 → secator-0.14.0}/secator/tasks/__init__.py +0 -0
  164. {secator-0.12.0 → secator-0.14.0}/secator/tasks/fping.py +0 -0
  165. {secator-0.12.0 → secator-0.14.0}/secator/tasks/gf.py +0 -0
  166. {secator-0.12.0 → secator-0.14.0}/secator/tasks/nmap.py +0 -0
  167. {secator-0.12.0 → secator-0.14.0}/secator/thread.py +0 -0
  168. {secator-0.12.0 → secator-0.14.0}/secator/utils.py +0 -0
  169. {secator-0.12.0 → secator-0.14.0}/secator/utils_test.py +0 -0
  170. {secator-0.12.0 → secator-0.14.0}/secator/workflows/__init__.py +0 -0
  171. {secator-0.12.0 → secator-0.14.0}/tests/__init__.py +0 -0
  172. {secator-0.12.0 → secator-0.14.0}/tests/fixtures/h8mail_breach.txt +0 -0
  173. {secator-0.12.0 → secator-0.14.0}/tests/fixtures/ls.py +0 -0
  174. {secator-0.12.0 → secator-0.14.0}/tests/fixtures/msfconsole_input.rc +0 -0
  175. {secator-0.12.0 → secator-0.14.0}/tests/fixtures/nmap_output.xml +0 -0
  176. {secator-0.12.0 → secator-0.14.0}/tests/integration/__init__.py +0 -0
  177. {secator-0.12.0 → secator-0.14.0}/tests/integration/inputs.py +0 -0
  178. {secator-0.12.0 → secator-0.14.0}/tests/integration/outputs.py +0 -0
  179. {secator-0.12.0 → secator-0.14.0}/tests/integration/setup.sh +0 -0
  180. {secator-0.12.0 → secator-0.14.0}/tests/integration/teardown.sh +0 -0
  181. {secator-0.12.0 → secator-0.14.0}/tests/integration/test_addons.py +0 -0
  182. {secator-0.12.0 → secator-0.14.0}/tests/integration/test_celery.py +0 -0
  183. {secator-0.12.0 → secator-0.14.0}/tests/integration/test_helpers.py +0 -0
  184. {secator-0.12.0 → secator-0.14.0}/tests/integration/test_scans.py +0 -0
  185. {secator-0.12.0 → secator-0.14.0}/tests/integration/test_worker.py +0 -0
  186. {secator-0.12.0 → secator-0.14.0}/tests/integration/test_workflows.py +0 -0
  187. {secator-0.12.0 → secator-0.14.0}/tests/integration/wordlist.txt +0 -0
  188. {secator-0.12.0 → secator-0.14.0}/tests/integration/wordlist_dns.txt +0 -0
  189. {secator-0.12.0 → secator-0.14.0}/tests/integration/wordpress_toolbox/Dockerfile +0 -0
  190. {secator-0.12.0 → secator-0.14.0}/tests/integration/wordpress_toolbox/Makefile +0 -0
  191. {secator-0.12.0 → secator-0.14.0}/tests/performance/__init__.py +0 -0
  192. {secator-0.12.0 → secator-0.14.0}/tests/performance/loadtester.py +0 -0
  193. {secator-0.12.0 → secator-0.14.0}/tests/performance/test_worker.py +0 -0
  194. {secator-0.12.0 → secator-0.14.0}/tests/unit/__init__.py +0 -0
  195. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_celery.py +0 -0
  196. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_cli.py +0 -0
  197. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_command.py +0 -0
  198. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_config.py +0 -0
  199. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_offline.py +0 -0
  200. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_runners.py +0 -0
  201. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_scans.py +0 -0
  202. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_serializers.py +0 -0
  203. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_tasks.py +0 -0
  204. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_tasks_categories.py +0 -0
  205. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_template.py +0 -0
  206. {secator-0.12.0 → secator-0.14.0}/tests/unit/test_utils.py +0 -0
@@ -21,6 +21,7 @@ RUN pipx install --pip-args="--no-cache-dir" . && \
21
21
 
22
22
  FROM python:3.12-alpine
23
23
  ARG flavor=full
24
+ ARG build_from_source=false
24
25
  ENV TERM="xterm-256color"
25
26
  ENV PATH="${PATH}:/root/.local/bin"
26
27
  ENV GOBIN="/root/.local/bin"
@@ -29,5 +30,6 @@ RUN apk add --no-cache \
29
30
  flock \
30
31
  pipx \
31
32
  sudo
33
+ RUN if [ "$build_from_source" = "true" ]; then secator config set security.force_source_install 1; fi
32
34
  RUN if [ "$flavor" != "lite" ]; then secator install tools --cleanup --fail-fast; fi
33
35
  ENTRYPOINT ["secator"]
@@ -31,6 +31,6 @@ RUN pipx install . && \
31
31
  secator install addons mongodb && \
32
32
  secator install addons redis && \
33
33
  secator install addons dev
34
- RUN secator config set security.force_source_install true
35
- RUN secator install tools --cleanup --fail-fast
34
+ RUN if [ "$build_from_source" = "true" ]; then secator config set security.force_source_install 1; fi
35
+ RUN if [ "$flavor" != "lite" ]; then secator install tools --cleanup --fail-fast; fi
36
36
  ENTRYPOINT ["secator"]
@@ -33,6 +33,6 @@ RUN pipx install . && \
33
33
  secator install addons redis && \
34
34
  secator install addons dev
35
35
  RUN secator install langs go
36
- RUN secator config set security.force_source_install true
37
- RUN secator install tools --cleanup --fail-fast
36
+ RUN if [ "$build_from_source" = "true" ]; then secator config set security.force_source_install 1; fi
37
+ RUN if [ "$flavor" != "lite" ]; then secator install tools --cleanup --fail-fast; fi
38
38
  ENTRYPOINT ["secator"]
@@ -32,6 +32,6 @@ RUN pipx install . && \
32
32
  secator install addons mongodb && \
33
33
  secator install addons redis && \
34
34
  secator install addons dev
35
- RUN secator config set security.force_source_install true
36
- RUN secator install tools --cleanup --fail-fast
35
+ RUN if [ "$build_from_source" = "true" ]; then secator config set security.force_source_install 1; fi
36
+ RUN if [ "$flavor" != "lite" ]; then secator install tools --cleanup --fail-fast; fi
37
37
  ENTRYPOINT ["secator"]
@@ -35,6 +35,6 @@ RUN pipx install . && \
35
35
  secator install addons mongodb && \
36
36
  secator install addons redis && \
37
37
  secator install addons dev
38
- RUN secator config set security.force_source_install true
39
- RUN secator install tools --cleanup --fail-fast
38
+ RUN if [ "$build_from_source" = "true" ]; then secator config set security.force_source_install 1; fi
39
+ RUN if [ "$flavor" != "lite" ]; then secator install tools --cleanup --fail-fast; fi
40
40
  ENTRYPOINT ["secator"]
@@ -32,6 +32,6 @@ RUN pipx install . && \
32
32
  secator install addons mongodb && \
33
33
  secator install addons redis && \
34
34
  secator install addons dev
35
- RUN secator config set security.force_source_install true
36
- RUN secator install tools --cleanup --fail-fast
35
+ RUN if [ "$build_from_source" = "true" ]; then secator config set security.force_source_install 1; fi
36
+ RUN if [ "$flavor" != "lite" ]; then secator install tools --cleanup --fail-fast; fi
37
37
  ENTRYPOINT ["secator"]
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.0](https://github.com/freelabz/secator/compare/v0.13.0...v0.14.0) (2025-04-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * add support for profiles ([#532](https://github.com/freelabz/secator/issues/532)) ([6f499b7](https://github.com/freelabz/secator/commit/6f499b72ec18dddde717e00318be4e2b53cc0478))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **cli:** restore --unified option, delete port_scan workflow ([#606](https://github.com/freelabz/secator/issues/606)) ([6dc647d](https://github.com/freelabz/secator/commit/6dc647d7205d15faa1c675485af441549d798118))
14
+
15
+ ## [0.13.0](https://github.com/freelabz/secator/compare/v0.12.0...v0.13.0) (2025-04-29)
16
+
17
+
18
+ ### Features
19
+
20
+ * **core:** restore default task opts, improve workflows ([#560](https://github.com/freelabz/secator/issues/560)) ([4849d44](https://github.com/freelabz/secator/commit/4849d44448f52529380a6d9163a1b4db54114958))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * use distro.like() instead of distro.id() ([#604](https://github.com/freelabz/secator/issues/604)) ([15157d4](https://github.com/freelabz/secator/commit/15157d433a0c60292ed91bb26f513a10f15924c2))
26
+
3
27
  ## [0.12.0](https://github.com/freelabz/secator/compare/v0.11.1...v0.12.0) (2025-04-24)
4
28
 
5
29
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secator
3
- Version: 0.12.0
3
+ Version: 0.14.0
4
4
  Summary: The pentester's swiss knife.
5
5
  Project-URL: Homepage, https://github.com/freelabz/secator
6
6
  Project-URL: Issues, https://github.com/freelabz/secator/issues
@@ -4,7 +4,7 @@ build-backend = 'hatchling.build'
4
4
 
5
5
  [project]
6
6
  name = 'secator'
7
- version = "0.12.0"
7
+ version = "0.14.0"
8
8
  authors = [{ name = 'FreeLabz', email = 'sales@freelabz.com' }]
9
9
  readme = 'README.md'
10
10
  description = "The pentester's swiss knife."
@@ -36,8 +36,9 @@ click.rich_click.USE_RICH_MARKUP = True
36
36
  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
+ ALL_PROFILES = [t for t in TEMPLATES if t.type == 'profile']
39
40
  FINDING_TYPES_LOWER = [c.__name__.lower() for c in FINDING_TYPES]
40
- CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
41
+ CONTEXT_SETTINGS = dict(help_option_names=['-h', '-help', '--help'])
41
42
 
42
43
 
43
44
  #-----#
@@ -46,15 +47,16 @@ CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
46
47
 
47
48
 
48
49
  @click.group(cls=OrderedGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
49
- @click.option('--version', '-version', is_flag=True, default=False)
50
+ @click.option('--version', '-version', '-v', is_flag=True, default=False)
51
+ @click.option('--quiet', '-quiet', '-q', is_flag=True, default=False)
50
52
  @click.pass_context
51
- def cli(ctx, version):
53
+ def cli(ctx, version, quiet):
52
54
  """Secator CLI."""
53
55
  ctx.obj = {
54
56
  'piped_input': S_ISFIFO(os.fstat(0).st_mode),
55
57
  'piped_output': not sys.stdout.isatty()
56
58
  }
57
- if not ctx.obj['piped_output']:
59
+ if not ctx.obj['piped_output'] and not quiet:
58
60
  console.print(ASCII, highlight=False)
59
61
  if ctx.invoked_subcommand is None:
60
62
  if version:
@@ -67,11 +69,16 @@ def cli(ctx, version):
67
69
  # TASK #
68
70
  #------#
69
71
 
70
- @cli.group(aliases=['x', 't'])
72
+ @cli.group(aliases=['x', 't'], invoke_without_command=True)
73
+ @click.option('--list', '-list', is_flag=True, default=False)
71
74
  @click.pass_context
72
- def task(ctx):
75
+ def task(ctx, list=False):
73
76
  """Run a task."""
74
- pass
77
+ if list:
78
+ print("\n".join(sorted([t.__name__ for t in ALL_TASKS])))
79
+ return
80
+ if ctx.invoked_subcommand is None:
81
+ ctx.get_help()
75
82
 
76
83
 
77
84
  for cls in ALL_TASKS:
@@ -83,11 +90,16 @@ for cls in ALL_TASKS:
83
90
  #----------#
84
91
 
85
92
 
86
- @cli.group(cls=OrderedGroup, aliases=['w'])
93
+ @cli.group(cls=OrderedGroup, aliases=['w'], invoke_without_command=True)
94
+ @click.option('--list', '-list', is_flag=True, default=False)
87
95
  @click.pass_context
88
- def workflow(ctx):
96
+ def workflow(ctx, list=False):
89
97
  """Run a workflow."""
90
- pass
98
+ if list:
99
+ print("\n".join(sorted([t.name for t in ALL_WORKFLOWS])))
100
+ return
101
+ if ctx.invoked_subcommand is None:
102
+ ctx.get_help()
91
103
 
92
104
 
93
105
  for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
@@ -98,17 +110,41 @@ for config in sorted(ALL_WORKFLOWS, key=lambda x: x['name']):
98
110
  # SCAN #
99
111
  #------#
100
112
 
101
- @cli.group(cls=OrderedGroup, aliases=['s'])
113
+ @cli.group(cls=OrderedGroup, aliases=['s'], invoke_without_command=True)
114
+ @click.option('--list', '-list', is_flag=True, default=False)
102
115
  @click.pass_context
103
- def scan(ctx):
116
+ def scan(ctx, list=False):
104
117
  """Run a scan."""
105
- pass
118
+ if list:
119
+ print("\n".join(sorted([t.name for t in ALL_SCANS])))
120
+ return
121
+ if ctx.invoked_subcommand is None:
122
+ ctx.get_help()
106
123
 
107
124
 
108
125
  for config in sorted(ALL_SCANS, key=lambda x: x['name']):
109
126
  register_runner(scan, config)
110
127
 
111
128
 
129
+ @cli.group(aliases=['p'])
130
+ @click.pass_context
131
+ def profile(ctx):
132
+ """Show profiles"""
133
+ pass
134
+
135
+
136
+ @profile.command('list')
137
+ def profile_list():
138
+ table = Table()
139
+ table.add_column("Profile name", style="bold gold3")
140
+ table.add_column("Description", overflow='fold')
141
+ table.add_column("Options", overflow='fold')
142
+ for profile in ALL_PROFILES:
143
+ opts_str = ','.join(f'{k}={v}' for k, v in profile.opts.items())
144
+ table.add_row(profile.name, profile.description, opts_str)
145
+ console.print(table)
146
+
147
+
112
148
  #--------#
113
149
  # WORKER #
114
150
  #--------#
@@ -669,7 +705,7 @@ def report_show(report_query, output, runner_type, time_delta, type, query, work
669
705
  all_results.extend(runner.results)
670
706
  continue
671
707
  report = Report(runner, title=f"Consolidated report - {current}", exporters=exporters)
672
- report.build(extractors=extractors if not unified else [])
708
+ report.build(extractors=extractors if not unified else [], dedupe=unified)
673
709
  file_date = get_file_date(path)
674
710
  runner_name = data['info']['name']
675
711
  console.print(
@@ -860,13 +896,21 @@ def health(json, debug, strict):
860
896
  import json as _json
861
897
  print(_json.dumps(status))
862
898
 
899
+ # Print errors and warnings
900
+ error = False
901
+ for tool, info in status['tools'].items():
902
+ if not info['installed']:
903
+ console.print(Warning(message=f'{tool} is not installed.'))
904
+ error = True
905
+ elif info['outdated']:
906
+ message = (
907
+ f'{tool} is outdated (current:{info["version"]}, latest:{info["latest_version"]}).'
908
+ f' Run `secator install tools {tool}` to update it.'
909
+ )
910
+ console.print(Warning(message=message))
911
+
863
912
  # Strict mode
864
913
  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
914
  if error:
871
915
  sys.exit(1)
872
916
  console.print(Info(message='Strict healthcheck passed !'))
@@ -1061,16 +1105,27 @@ def install_tools(cmds, cleanup, fail_fast):
1061
1105
  if CONFIG.offline_mode:
1062
1106
  console.print(Error(message='Cannot run this command in offline mode.'))
1063
1107
  return
1108
+ tools = []
1064
1109
  if cmds is not None:
1065
1110
  cmds = cmds.split(',')
1066
- tools = [cls for cls in ALL_TASKS if cls.__name__ in cmds]
1111
+ for cmd in cmds:
1112
+ if '==' in cmd:
1113
+ cmd, version = tuple(cmd.split('=='))
1114
+ else:
1115
+ cmd, version = cmd, None
1116
+ cls = next((cls for cls in ALL_TASKS if cls.__name__ == cmd), None)
1117
+ if cls:
1118
+ if version:
1119
+ cls.install_version = version
1120
+ tools.append(cls)
1121
+ else:
1122
+ console.print(Warning(message=f'Tool {cmd} is not supported or inexistent.'))
1067
1123
  else:
1068
1124
  tools = ALL_TASKS
1069
1125
  tools.sort(key=lambda x: x.__name__)
1070
1126
  return_code = 0
1071
1127
  if not tools:
1072
- cmd_str = ' '.join(cmds)
1073
- console.print(Error(message=f'No tools found for {cmd_str}.'))
1128
+ console.print(Error(message='No tools found for installing.'))
1074
1129
  return
1075
1130
  for ix, cls in enumerate(tools):
1076
1131
  # with console.status(f'[bold yellow][{ix + 1}/{len(tools)}] Installing {cls.__name__} ...'):
@@ -1140,7 +1195,7 @@ def update(all):
1140
1195
  cmd = cls.cmd.split(' ')[0]
1141
1196
  version_flag = cls.get_version_flag()
1142
1197
  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']:
1198
+ if not info['installed'] or info['outdated'] or not info['latest_version']:
1144
1199
  # with console.status(f'[bold yellow]Installing {cls.__name__} ...'):
1145
1200
  status = ToolInstaller.install(cls)
1146
1201
  if not status.is_ok():
@@ -1375,73 +1430,151 @@ def performance(tasks, workflows, scans, test):
1375
1430
  @test.command()
1376
1431
  @click.argument('name', type=str)
1377
1432
  @click.option('--verbose', '-v', is_flag=True, default=False, help='Print verbose output')
1378
- def task(name, verbose):
1379
- """Test task."""
1433
+ @click.option('--check', '-c', is_flag=True, default=False, help='Check task semantics only (no unit + integration tests)') # noqa: E501
1434
+ def task(name, verbose, check):
1435
+ """Test a single task for semantics errors, and run unit + integration tests."""
1436
+ console.print(f'[bold gold3]:wrench: Testing task {name} ...[/]')
1380
1437
  task = [task for task in ALL_TASKS if task.__name__ == name]
1381
1438
  warnings = []
1439
+ errors = []
1382
1440
  exit_code = 0
1383
1441
 
1384
1442
  # 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
1443
  task = task[0]
1387
1444
  task_name = task.__name__
1388
1445
 
1446
+ # Check task command is set
1447
+ check_test(
1448
+ task.cmd,
1449
+ 'Check task command is set (cls.cmd)',
1450
+ 'Task has no cmd attribute.',
1451
+ errors
1452
+ )
1453
+ if errors:
1454
+ sys.exit(0)
1455
+
1389
1456
  # Run install
1390
- console.print(f'\n[bold gold3]:wrench: Running install tests for task {name} ...[/]') if verbose else None
1391
1457
  cmd = f'secator install tools {task_name}'
1392
1458
  ret_code = Command.execute(cmd, name='install', quiet=not verbose, cwd=ROOT_FOLDER)
1393
1459
  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
1460
+ if verbose:
1461
+ console.print(f'Version info:\n{version_info}')
1462
+ status = version_info['status']
1463
+ check_test(
1464
+ version_info['installed'],
1465
+ 'Check task is installed',
1466
+ 'Failed to install command. Fix your installation command.',
1467
+ errors
1468
+ )
1469
+ check_test(
1470
+ any(cmd for cmd in [task.install_pre, task.install_cmd, task.install_github_handle]),
1471
+ 'Check task installation command is defined',
1472
+ '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
1473
+ errors
1474
+ )
1475
+ check_test(
1476
+ version_info['version'],
1477
+ 'Check task version can be fetched',
1478
+ 'Failed to detect current version. Consider updating your `version_flag` class attribute.',
1479
+ warnings,
1480
+ warn=True
1481
+ )
1482
+ check_test(
1483
+ status != 'latest unknown',
1484
+ 'Check latest version',
1485
+ 'Failed to detect latest version.',
1486
+ warnings,
1487
+ warn=True
1488
+ )
1489
+ check_test(
1490
+ not version_info['outdated'],
1491
+ 'Check task version is up to date',
1492
+ 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
1493
+ warnings,
1494
+ warn=True
1495
+ )
1397
1496
 
1398
1497
  # 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)
1498
+ check_test(
1499
+ task.__doc__,
1500
+ 'Check task description is set (cls.__doc__)',
1501
+ 'Task has no description (class docstring).',
1502
+ errors
1503
+ )
1504
+ check_test(
1505
+ task.input_type,
1506
+ 'Check task input type is set (cls.input_type)',
1507
+ 'Task has no input_type attribute.',
1508
+ warnings,
1509
+ warn=True
1510
+ )
1511
+ check_test(
1512
+ task.output_types,
1513
+ 'Check task output types is set (cls.output_types)',
1514
+ 'Task has no output_types attribute. Consider setting some so that secator can load your task outputs.',
1515
+ warnings,
1516
+ warn=True
1517
+ )
1518
+ check_test(
1519
+ task.install_version,
1520
+ 'Check task install_version is set (cls.install_version)',
1521
+ '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
1522
+ warnings,
1523
+ warn=True
1524
+ )
1525
+
1526
+ if not check:
1527
+
1528
+ # Run unit tests
1529
+ cmd = f'secator test unit --tasks {name}'
1530
+ ret_code = run_test(cmd, exit=False, verbose=verbose)
1531
+ check_test(
1532
+ ret_code == 0,
1533
+ 'Check unit tests pass',
1534
+ 'Unit tests failed.',
1535
+ errors
1536
+ )
1537
+
1538
+ # Run integration tests
1539
+ cmd = f'secator test integration --tasks {name}'
1540
+ ret_code = run_test(cmd, exit=False, verbose=verbose)
1541
+ check_test(
1542
+ ret_code == 0,
1543
+ 'Check integration tests pass',
1544
+ 'Integration tests failed.',
1545
+ errors
1546
+ )
1428
1547
 
1429
1548
  # Exit with exit code
1430
- exit_code = 1 if len(warnings) > 0 else 0
1549
+ exit_code = 1 if len(errors) > 0 else 0
1431
1550
  if exit_code == 0:
1432
- console.print(f':tada: Task {name} tests passed ! You are free to make a PR.', style='bold green')
1551
+ console.print(f':tada: Task {name} tests passed !', style='bold green')
1433
1552
  else:
1434
- console.print(Error(message=f'Task {name} tests failed. Please fix the issues above before making a PR.'))
1553
+ console.print('\n[bold gold3]Errors:[/]')
1554
+ for error in errors:
1555
+ console.print(error)
1556
+ console.print(Error(message=f'Task {name} tests failed. Please fix the issues above.'))
1435
1557
 
1558
+ if warnings:
1559
+ console.print('\n[bold gold3]Warnings:[/]')
1560
+ for warning in warnings:
1561
+ console.print(warning)
1562
+
1563
+ console.print("\n")
1436
1564
  sys.exit(exit_code)
1437
1565
 
1438
1566
 
1439
- def check_error(condition, message, error, warnings=[]):
1567
+ def check_test(condition, message, fail_message, results=[], warn=False):
1440
1568
  console.print(f'[bold magenta]:zap: {message} ...[/]', end='')
1441
1569
  if not condition:
1442
- warning = Warning(message=error)
1443
- warnings.append(warning)
1444
- console.print(' [bold red]FAILED[/]', style='dim')
1570
+ if not warn:
1571
+ error = Error(message=fail_message)
1572
+ console.print(' [bold red]FAILED[/]', style='dim')
1573
+ results.append(error)
1574
+ else:
1575
+ warning = Warning(message=fail_message)
1576
+ console.print(' [bold yellow]WARNING[/]', style='dim')
1577
+ results.append(warning)
1445
1578
  else:
1446
1579
  console.print(' [bold green]OK[/]', style='dim')
1447
1580
  return True
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: aggressive
3
+ description: "Internal networks or time-sensitive scans"
4
+ opts:
5
+ rate_limit: 10000
6
+ delay: 0
7
+ timeout: 1
8
+ retries: 1
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: default
3
+ description: "General scanning"
4
+ opts:
5
+ rate_limit: 1000
6
+ delay: 0
7
+ timeout: 5
8
+ retries: 3
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: insane
3
+ description: "Local LAN scanning or stress scanning"
4
+ opts:
5
+ rate_limit: 100000
6
+ delay: 0
7
+ timeout: 1
8
+ retries: 0
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: paranoid
3
+ description: "Maximum stealth"
4
+ opts:
5
+ rate_limit: 5
6
+ delay: 5
7
+ timeout: 15
8
+ retries: 5
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: polite
3
+ description: "Avoid overloading network"
4
+ opts:
5
+ rate_limit: 100
6
+ delay: 0
7
+ timeout: 10
8
+ retries: 5
@@ -0,0 +1,8 @@
1
+ type: profile
2
+ name: sneaky
3
+ description: "IDS/IPS evasion, sensitive networks"
4
+ opts:
5
+ rate_limit: 10
6
+ delay: 2
7
+ timeout: 15
8
+ retries: 5
@@ -0,0 +1,5 @@
1
+ type: profile
2
+ name: tor
3
+ description: "Anonymous scan using Tor network"
4
+ opts:
5
+ proxy: tor
@@ -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
@@ -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
@@ -13,6 +13,7 @@ from secator.utils import (deduplicate, expand_input, get_command_category)
13
13
 
14
14
  RUNNER_OPTS = {
15
15
  'output': {'type': str, 'default': None, 'help': 'Output options (-o table,json,csv,gdrive)', 'short': 'o'},
16
+ 'profiles': {'type': str, 'default': 'default', 'help': 'Profiles', 'short': 'pf'},
16
17
  'workspace': {'type': str, 'default': 'default', 'help': 'Workspace', 'short': 'ws'},
17
18
  'print_json': {'is_flag': True, 'short': 'json', 'default': False, 'help': 'Print items as JSON lines'},
18
19
  'print_raw': {'is_flag': True, 'short': 'raw', 'default': False, 'help': 'Print items in raw format'},
@@ -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