curlcommander 0.3.1__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.
Files changed (75) hide show
  1. curlcommander/__init__.py +1 -0
  2. curlcommander/__main__.py +6 -0
  3. curlcommander/cli/__init__.py +0 -0
  4. curlcommander/cli/arg_parser.py +342 -0
  5. curlcommander/cli/onboarding.py +479 -0
  6. curlcommander/cli/runner.py +1088 -0
  7. curlcommander/cli/wizard.py +107 -0
  8. curlcommander/config.py +73 -0
  9. curlcommander/core/__init__.py +0 -0
  10. curlcommander/core/api_styles.py +88 -0
  11. curlcommander/core/assertions.py +172 -0
  12. curlcommander/core/auth_handler.py +20 -0
  13. curlcommander/core/browser.py +151 -0
  14. curlcommander/core/clipboard.py +69 -0
  15. curlcommander/core/cookies.py +44 -0
  16. curlcommander/core/curl_builder.py +97 -0
  17. curlcommander/core/curl_parser.py +226 -0
  18. curlcommander/core/discovery.py +140 -0
  19. curlcommander/core/encoders.py +69 -0
  20. curlcommander/core/evidence.py +61 -0
  21. curlcommander/core/features.py +137 -0
  22. curlcommander/core/fuzzer.py +162 -0
  23. curlcommander/core/headers.py +167 -0
  24. curlcommander/core/http_client.py +160 -0
  25. curlcommander/core/intruder.py +86 -0
  26. curlcommander/core/logging_setup.py +55 -0
  27. curlcommander/core/multipart.py +57 -0
  28. curlcommander/core/parsing.py +59 -0
  29. curlcommander/core/passive.py +186 -0
  30. curlcommander/core/payload_catalog.py +126 -0
  31. curlcommander/core/payload_sources.py +232 -0
  32. curlcommander/core/payloads.py +34 -0
  33. curlcommander/core/proxy.py +199 -0
  34. curlcommander/core/raw_http.py +100 -0
  35. curlcommander/core/raw_transport.py +207 -0
  36. curlcommander/core/redaction.py +170 -0
  37. curlcommander/core/request_model.py +103 -0
  38. curlcommander/core/response_formatter.py +49 -0
  39. curlcommander/core/scope.py +60 -0
  40. curlcommander/core/validators/__init__.py +0 -0
  41. curlcommander/core/validators/base.py +27 -0
  42. curlcommander/core/validators/clickjacking.py +65 -0
  43. curlcommander/core/validators/cors.py +36 -0
  44. curlcommander/core/validators/csrf.py +65 -0
  45. curlcommander/core/validators/redirect.py +58 -0
  46. curlcommander/core/validators/xss.py +144 -0
  47. curlcommander/data/__init__.py +0 -0
  48. curlcommander/data/payload_map.yaml +29 -0
  49. curlcommander/data/payloads/__init__.py +0 -0
  50. curlcommander/data/payloads/cmdi.txt +6 -0
  51. curlcommander/data/payloads/sqli.txt +10 -0
  52. curlcommander/data/payloads/ssti.txt +6 -0
  53. curlcommander/data/payloads/traversal.txt +6 -0
  54. curlcommander/data/payloads/xss.txt +8 -0
  55. curlcommander/data/sources.yaml +20 -0
  56. curlcommander/gui/__init__.py +0 -0
  57. curlcommander/gui/app.py +248 -0
  58. curlcommander/gui/curl_panel.py +34 -0
  59. curlcommander/gui/history_panel.py +127 -0
  60. curlcommander/gui/intruder_panel.py +238 -0
  61. curlcommander/gui/proxy_panel.py +194 -0
  62. curlcommander/gui/rawreq.py +30 -0
  63. curlcommander/gui/repeater_panel.py +121 -0
  64. curlcommander/gui/request_panel.py +283 -0
  65. curlcommander/gui/response_panel.py +133 -0
  66. curlcommander/gui/response_view.py +221 -0
  67. curlcommander/main.py +33 -0
  68. curlcommander/storage/__init__.py +0 -0
  69. curlcommander/storage/db.py +76 -0
  70. curlcommander/storage/history_repo.py +125 -0
  71. curlcommander-0.3.1.dist-info/METADATA +533 -0
  72. curlcommander-0.3.1.dist-info/RECORD +75 -0
  73. curlcommander-0.3.1.dist-info/WHEEL +4 -0
  74. curlcommander-0.3.1.dist-info/entry_points.txt +2 -0
  75. curlcommander-0.3.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -0,0 +1,6 @@
1
+ """Enable `python -m curlcommander` and serve as the frozen-binary entry point."""
2
+
3
+ from curlcommander.main import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
File without changes
@@ -0,0 +1,342 @@
1
+ import argparse
2
+ from typing import NoReturn
3
+
4
+
5
+ def _version() -> str:
6
+ from curlcommander import __version__
7
+
8
+ return __version__
9
+
10
+
11
+ def _version_line() -> str:
12
+ """Version plus how curlcmd was installed (PT-BR), for ``--version``."""
13
+ from curlcommander.core import features
14
+
15
+ return f"curlcmd {_version()} (instalação: {features.install_method()})"
16
+
17
+
18
+ class ArgParser(argparse.ArgumentParser):
19
+ """ArgumentParser that exits with code 1 (usage error) instead of 2."""
20
+
21
+ def error(self, message: str) -> NoReturn:
22
+ self.print_usage()
23
+ self.exit(1, f"{self.prog}: error: {message}\n")
24
+
25
+
26
+ def build_request_parser() -> argparse.ArgumentParser:
27
+ """Parser used when no subcommand is detected (request / wizard mode)."""
28
+ parser = ArgParser(
29
+ prog="curlcmd",
30
+ description="CurlCommander — construtor de requisições HTTP e gerador de curl",
31
+ )
32
+ parser.add_argument("url", nargs="?", help="URL de destino")
33
+ imp = parser.add_argument_group("importação")
34
+ imp.add_argument("--import", dest="import_curl", metavar="CURL", help="Importa um comando curl (string)")
35
+ imp.add_argument("--import-file", metavar="CAMINHO", help="Importa um comando curl de um arquivo")
36
+ imp.add_argument("--import-clipboard", action="store_true", help="Importa um comando curl da área de transferência")
37
+ imp.add_argument("--import-raw", metavar="CAMINHO", help="Importa um bloco HTTP cru (Burp/.http)")
38
+ imp.add_argument("--host", metavar="URL", help="Host base para --import-raw / --raw-request (ex.: https://alvo)")
39
+
40
+ api = parser.add_argument_group("estilos de API")
41
+ api.add_argument("--graphql", metavar="QUERY", help="Envia uma query GraphQL (POST JSON)")
42
+ api.add_argument("--graphql-vars", metavar="JSON", help="Variáveis GraphQL em JSON")
43
+ api.add_argument(
44
+ "--graphql-introspection", action="store_true", help="Envia query de introspection e reporta se habilitada"
45
+ )
46
+ api.add_argument("--xml", metavar="CORPO|@ARQ", help="Envia um corpo XML (application/xml)")
47
+ api.add_argument("--soap", metavar="CORPO|@ARQ", help="Envia um corpo SOAP/XML (text/xml)")
48
+ api.add_argument("--soap-action", metavar="URI", help="Valor do cabeçalho SOAPAction")
49
+ api.add_argument("--soap-envelope", action="store_true", help="Embrulha o corpo --soap num envelope SOAP")
50
+ api.add_argument("--grpc-web", action="store_true", help="Define content-type gRPC-web (use --body-file p/ bytes)")
51
+ api.add_argument("--stream", action="store_true", help="Faz streaming da resposta linha a linha (NDJSON/SSE)")
52
+
53
+ raw = parser.add_argument_group("controle cru / pentest")
54
+ raw.add_argument("--raw-request", metavar="CAMINHO", help="Envia um bloco HTTP cru byte a byte por um socket")
55
+ raw.add_argument("--no-fix-length", action="store_true", help="Nunca recalcula Content-Length p/ --raw-request")
56
+ raw.add_argument("--raw-path", action="store_true", help="Envia o caminho da URL byte a byte (sem normalização)")
57
+ raw.add_argument(
58
+ "--no-default-headers", action="store_true", help="Não envia os cabeçalhos padrão do httpx (UA/Accept/...)"
59
+ )
60
+
61
+ opsec = parser.add_argument_group("segurança operacional")
62
+ opsec.add_argument("--scope", metavar="CAMINHO", help="Allowlist de hosts/CIDRs em escopo; recusa fora do escopo")
63
+ opsec.add_argument("--dry-run", action="store_true", help="Mostra o que seria enviado na rede, sem enviar")
64
+ opsec.add_argument("--evidence", metavar="DIR", help="Salva requisição+resposta+metadados crus em DIR")
65
+ opsec.add_argument("--engagement", metavar="LABEL", help="Rótulo de autorização/engajamento p/ evidência")
66
+
67
+ fuzz = parser.add_argument_group("fuzzing")
68
+ fuzz.add_argument(
69
+ "-w",
70
+ "--wordlist",
71
+ action="append",
72
+ dest="wordlists",
73
+ default=[],
74
+ metavar="CAMINHO",
75
+ help="Wordlist para marcadores FUZZ (repetível)",
76
+ )
77
+ fuzz.add_argument(
78
+ "--payloads",
79
+ action="append",
80
+ dest="payloads",
81
+ default=[],
82
+ metavar="CAT",
83
+ help="Categoria de payload via catálogo (xss/sqli/ssti/lfi/ssrf/...)",
84
+ )
85
+ fuzz.add_argument(
86
+ "--payloads-all",
87
+ action="append",
88
+ dest="payloads_all",
89
+ default=[],
90
+ metavar="CAT",
91
+ help="Categoria de payload de TODAS as fontes sincronizadas, deduplicado",
92
+ )
93
+ fuzz.add_argument(
94
+ "--fuzz-mode", choices=["clusterbomb", "pitchfork"], default="clusterbomb", help="Modo multi-wordlist"
95
+ )
96
+ fuzz.add_argument("--encode", metavar="LISTA", help="Cadeia de encoders aplicada aos payloads (ex.: url,base64)")
97
+ fuzz.add_argument("--concurrency", type=int, default=10, metavar="N", help="Requisições de fuzz concorrentes")
98
+ fuzz.add_argument(
99
+ "--rate", type=float, default=0.0, metavar="R", help="Máx. requisições por segundo (0 = ilimitado)"
100
+ )
101
+ fuzz.add_argument("--mc", metavar="CODES", help="Casa códigos de status (separados por vírgula)")
102
+ fuzz.add_argument("--fc", metavar="CODES", help="Filtra códigos de status (separados por vírgula)")
103
+ fuzz.add_argument("--ms", type=int, metavar="N", help="Casa resposta de N bytes")
104
+ fuzz.add_argument("--fs", type=int, metavar="N", help="Filtra resposta de N bytes")
105
+ fuzz.add_argument("--mr", metavar="REGEX", help="Casa regex no corpo")
106
+ parser.add_argument("-X", "--method", default="GET", metavar="MÉTODO", help="Método HTTP (padrão: GET)")
107
+ parser.add_argument(
108
+ "-H",
109
+ "--header",
110
+ action="append",
111
+ dest="headers",
112
+ default=[],
113
+ metavar="Chave: Valor",
114
+ help="Cabeçalho (repetível)",
115
+ )
116
+ parser.add_argument(
117
+ "-p",
118
+ "--param",
119
+ action="append",
120
+ dest="params",
121
+ default=[],
122
+ metavar="chave=valor",
123
+ help="Parâmetro de query (repetível)",
124
+ )
125
+ parser.add_argument("-b", "--body", default="", help="Corpo da requisição (string)")
126
+ parser.add_argument("--body-file", metavar="CAMINHO", help="Lê o corpo da requisição de um arquivo")
127
+ parser.add_argument("--json", dest="json_body", metavar="JSON", help="Corpo JSON (define o Content-Type sozinho)")
128
+ parser.add_argument("--form", dest="form_body", metavar="DADOS", help="Corpo form-urlencoded")
129
+ parser.add_argument("--auth-bearer", metavar="TOKEN", help="Token Bearer")
130
+ parser.add_argument("--auth-basic", metavar="USUÁRIO:SENHA", help="Credenciais Basic auth")
131
+ parser.add_argument("--auth-apikey", metavar="'Cabeçalho: Valor'", help="Auth por API key")
132
+ parser.add_argument(
133
+ "--cookie", action="append", dest="cookies", default=[], metavar="k=v", help="Cookie (repetível)"
134
+ )
135
+ parser.add_argument("--cookie-jar", metavar="CAMINHO", help="Persiste/carrega cookies num arquivo jar")
136
+ parser.add_argument("--session", metavar="NOME", help="Sessão nomeada (cookie jar persistente)")
137
+ parser.add_argument(
138
+ "-F",
139
+ "--form-file",
140
+ action="append",
141
+ dest="form",
142
+ default=[],
143
+ metavar="nome=@arquivo",
144
+ help="Campo/arquivo multipart (repetível)",
145
+ )
146
+ parser.add_argument("--proxy", metavar="URL", help="URL de proxy para rotear a requisição")
147
+ parser.add_argument("--burp", action="store_true", help="Atalho para --proxy http://127.0.0.1:8080 --no-verify")
148
+ parser.add_argument("--retry", type=int, default=0, metavar="N", help="Tentativas de repetição em erro de rede")
149
+ parser.add_argument("--retry-delay", type=float, default=0.0, metavar="SEGUNDOS", help="Espera entre tentativas")
150
+ parser.add_argument("--compressed", action="store_true", help="Pede resposta comprimida")
151
+ parser.add_argument("--http2", action="store_true", help="Usa HTTP/2 se suportado")
152
+ parser.add_argument("--output", metavar="CAMINHO", default="", help="Salva o corpo da resposta num arquivo")
153
+ parser.add_argument("--pretty", action="store_true", help="Formata o corpo (JSON já é formatado por padrão)")
154
+ parser.add_argument("--raw", action="store_true", help="Desativa a formatação da resposta")
155
+ parser.add_argument("--env-file", metavar="CAMINHO", help="Carrega variáveis de ambiente p/ substituições")
156
+ parser.add_argument("--no-redirect", action="store_true", help="Não segue redirects")
157
+ parser.add_argument("--no-verify", action="store_true", help="Desativa a verificação do certificado SSL")
158
+ parser.add_argument(
159
+ "--timeout", type=float, default=30.0, metavar="SEGUNDOS", help="Timeout da requisição (padrão: 30)"
160
+ )
161
+ asserts = parser.add_argument_group("asserções (modo de teste)")
162
+ asserts.add_argument("--assert-status", type=int, metavar="N", help="Afirma que o status HTTP é igual a N")
163
+ asserts.add_argument(
164
+ "--assert-header",
165
+ action="append",
166
+ dest="assert_headers",
167
+ default=[],
168
+ metavar="Nome: Valor",
169
+ help="Afirma cabeçalho (repetível)",
170
+ )
171
+ asserts.add_argument(
172
+ "--assert-body-contains",
173
+ action="append",
174
+ dest="assert_body",
175
+ default=[],
176
+ metavar="STR",
177
+ help="Afirma que o corpo contém STR (repetível)",
178
+ )
179
+ asserts.add_argument(
180
+ "--assert-jsonpath",
181
+ action="append",
182
+ dest="assert_jsonpath",
183
+ default=[],
184
+ metavar="EXPR",
185
+ help="Afirma JSONPath (ex.: '$.user.id==42')",
186
+ )
187
+ asserts.add_argument("--assert-max-ms", type=float, metavar="MS", help="Afirma tempo de resposta abaixo de MS ms")
188
+ asserts.add_argument("--report", choices=["json", "junit"], help="Emite um relatório de asserções no stdout")
189
+
190
+ parser.add_argument(
191
+ "--no-redact", action="store_true", help="Guarda credenciais em texto claro no histórico (inseguro)"
192
+ )
193
+ parser.add_argument("--fail", action="store_true", help="Sai com 22 em status HTTP >= 400 (como curl --fail)")
194
+ parser.add_argument("--curl-only", action="store_true", help="Imprime o comando curl sem enviar")
195
+ parser.add_argument("--save", action="store_true", help="Salva no histórico mesmo com --curl-only")
196
+ parser.add_argument("--gui", action="store_true", help="Abre a TUI (Textual)")
197
+ parser.add_argument("--log-file", metavar="CAMINHO", help="Escreve logs estruturados e redigidos em CAMINHO")
198
+ parser.add_argument("--log-level", choices=["debug", "info", "warning", "error"], help="Verbosidade do log")
199
+ parser.add_argument("--version", action="version", version=_version_line())
200
+ return parser
201
+
202
+
203
+ def build_subcommand_parser() -> argparse.ArgumentParser:
204
+ """Parser used when a subcommand (history, replay, curl, clear-history) is detected."""
205
+ parser = ArgParser(
206
+ prog="curlcmd",
207
+ description="CurlCommander — subcomandos",
208
+ )
209
+ subparsers = parser.add_subparsers(dest="subcommand", required=True)
210
+
211
+ history_p = subparsers.add_parser("history", help="Lista o histórico de requisições")
212
+ history_p.add_argument("--reveal", action="store_true", help="Resolve as referências de segredo {{VAR}}")
213
+
214
+ replay_p = subparsers.add_parser("replay", help="Repete uma entrada do histórico pelo ID")
215
+ replay_p.add_argument("id", type=int, help="ID da entrada do histórico")
216
+
217
+ curl_p = subparsers.add_parser("curl", help="Imprime o curl de uma entrada do histórico")
218
+ curl_p.add_argument("id", type=int, help="ID da entrada do histórico")
219
+ curl_p.add_argument("--reveal", action="store_true", help="Resolve as referências de segredo {{VAR}}")
220
+
221
+ export_p = subparsers.add_parser("export-history", help="Exporta o histórico para JSON")
222
+ export_p.add_argument("-o", "--output", default="history.json", metavar="CAMINHO", help="Caminho do arquivo JSON")
223
+ export_p.add_argument("--reveal", action="store_true", help="Resolve as referências de segredo {{VAR}}")
224
+
225
+ delete_p = subparsers.add_parser("delete-history", help="Apaga uma entrada do histórico pelo ID")
226
+ delete_p.add_argument("id", type=int, help="ID da entrada do histórico")
227
+
228
+ subparsers.add_parser("clear-history", help="Limpa todo o histórico")
229
+
230
+ # setup (install optional extras + payloads; PT-BR, idempotent)
231
+ setup_p = subparsers.add_parser("setup", help="Instalar recursos opcionais (navegador/proxy/socks/payloads)")
232
+ setup_p.add_argument("--all", action="store_true", help="Instalar todos os recursos opcionais e payloads")
233
+ setup_p.add_argument("--browser", action="store_true", help="Validadores em navegador (Playwright)")
234
+ setup_p.add_argument("--proxy", action="store_true", help="Proxy interceptador (mitmproxy)")
235
+ setup_p.add_argument("--socks", action="store_true", help="Suporte a proxy SOCKS")
236
+ setup_p.add_argument("--clipboard", action="store_true", help="Área de transferência (pyperclip)")
237
+ setup_p.add_argument("--payloads", action="store_true", help="Baixar/atualizar as fontes de payloads")
238
+ setup_p.add_argument("-y", "--yes", action="store_true", help="Não perguntar; assumir sim (uso em scripts/CI)")
239
+
240
+ # doctor (diagnose install; --fix installs what it can)
241
+ doctor_p = subparsers.add_parser("doctor", help="Diagnosticar a instalação e recursos opcionais")
242
+ doctor_p.add_argument("--fix", action="store_true", help="Tentar instalar/corrigir o que estiver faltando")
243
+ doctor_p.add_argument("-y", "--yes", action="store_true", help="Não perguntar durante o --fix (scripts/CI)")
244
+
245
+ # self-update (update curlcmd via the detected install method)
246
+ selfup_p = subparsers.add_parser("self-update", help="Atualizar o curlcmd pelo método de instalação detectado")
247
+ selfup_p.add_argument("-y", "--yes", action="store_true", help="Rodar a atualização sem perguntar")
248
+
249
+ # payloads sync / update / list / search / show
250
+ payloads_p = subparsers.add_parser("payloads", help="Gerencia fontes de payloads e o catálogo")
251
+ p_sub = payloads_p.add_subparsers(dest="payloads_cmd", required=True)
252
+ p_sync = p_sub.add_parser("sync", help="Clona/atualiza uma fonte de payloads")
253
+ p_sync.add_argument("source", nargs="?", help="Nome da fonte (padrão: todas)")
254
+ p_sub.add_parser("update", help="Atualiza todas as fontes sincronizadas")
255
+ p_list = p_sub.add_parser("list", help="Lista categorias do catálogo / fontes sincronizadas")
256
+ p_list.add_argument("--category", metavar="CAT", help="Lista os arquivos que compõem uma categoria")
257
+ p_search = p_sub.add_parser("search", help="Busca por nomes de arquivos de wordlist")
258
+ p_search.add_argument("term", help="Trecho a buscar")
259
+ p_show = p_sub.add_parser("show", help="Prévia dos payloads de uma categoria")
260
+ p_show.add_argument("category", help="Nome da categoria")
261
+ p_show.add_argument("--limit", type=int, default=20, help="Máx. de linhas a mostrar")
262
+ p_show.add_argument("--count", action="store_true", help="Imprime só o total")
263
+ p_show.add_argument("--all", action="store_true", dest="all_sources", help="Usa todas as fontes sincronizadas")
264
+
265
+ # discover (content discovery via the fuzz engine)
266
+ disc = subparsers.add_parser("discover", help="Descoberta de conteúdo / dirbusting contra uma URL")
267
+ disc.add_argument("url", help="URL base")
268
+ disc.add_argument("-w", "--wordlist", action="append", dest="wordlists", default=[], metavar="SPEC")
269
+ disc.add_argument("--payloads", action="append", dest="payloads", default=[], metavar="CAT")
270
+ disc.add_argument("-e", "--extensions", metavar="EXT", help="Comma-separated extensions (php,bak,old)")
271
+ disc.add_argument("--recurse", type=int, default=0, metavar="DEPTH", help="Shallow recursion depth")
272
+ disc.add_argument("--concurrency", type=int, default=20, metavar="N")
273
+ disc.add_argument("--rate", type=float, default=0.0, metavar="R")
274
+ disc.add_argument("--mc", metavar="CODES")
275
+ disc.add_argument("--fc", metavar="CODES", default="404")
276
+ disc.add_argument("--ms", type=int, metavar="N")
277
+ disc.add_argument("--fs", type=int, metavar="N")
278
+ disc.add_argument("--mr", metavar="REGEX")
279
+ disc.add_argument("--scope", metavar="PATH")
280
+ disc.add_argument("--no-verify", action="store_true")
281
+ disc.add_argument("--timeout", type=float, default=30.0)
282
+
283
+ # proxy (intercepting HTTPS proxy with its own CA)
284
+ prox = subparsers.add_parser("proxy", help="Roda um proxy HTTPS interceptador (mitmproxy)")
285
+ prox.add_argument("--port", type=int, default=8080)
286
+ prox.add_argument("--scope", metavar="CAMINHO", help="Intercepta só hosts em escopo; tunela o resto")
287
+ prox.add_argument("--engagement", metavar="LABEL", help="Rótulo de autorização (obrigatório)")
288
+ prox.add_argument(
289
+ "--replace",
290
+ action="append",
291
+ dest="replace",
292
+ default=[],
293
+ metavar="REGRA",
294
+ help="Match-and-replace: [req|resp:]padrão==>substituição (repetível)",
295
+ )
296
+ prox.add_argument("--launch-browser", action="store_true", help="Abre o Chromium roteado pelo proxy")
297
+ prox.add_argument("--ca", action="store_true", help="Imprime o caminho da CA + guia de instalação/remoção e sai")
298
+
299
+ # validate (browser-executed / HTTP vulnerability validators)
300
+ val = subparsers.add_parser("validate", help="Valida uma vulnerabilidade (navegador/HTTP)")
301
+ val.add_argument("kind", choices=["xss", "cors", "open-redirect", "clickjacking", "csrf"])
302
+ val.add_argument("url", help="URL alvo (use marcadores §PAYLOAD§/§DEST§ onde couber)")
303
+ val.add_argument("--engagement", metavar="LABEL", help="Rótulo de autorização (obrigatório)")
304
+ val.add_argument("--scope", metavar="CAMINHO")
305
+ val.add_argument("--origin", metavar="ORIGEM", default="https://evil.example", help="Origem atacante p/ CORS")
306
+ val.add_argument("--headed", action="store_true", help="Mostra a janela do navegador")
307
+ val.add_argument("--evidence", metavar="DIR", help="Salva screenshot/DOM/HAR aqui")
308
+ val.add_argument("--no-verify", action="store_true")
309
+ val.add_argument("--timeout", type=float, default=30.0)
310
+
311
+ # bounty-scan (discover + per-category fuzz, consolidated by severity)
312
+ bounty = subparsers.add_parser("bounty-scan", help="Perfil encadeado de discovery + fuzz de payloads")
313
+ bounty.add_argument("url", help="URL alvo")
314
+ bounty.add_argument("--scope", metavar="CAMINHO")
315
+ bounty.add_argument("--engagement", metavar="LABEL", help="Rótulo de autorização (obrigatório)")
316
+ bounty.add_argument("--categories", metavar="LIST", default="xss,sqli,traversal,ssti")
317
+ bounty.add_argument("--concurrency", type=int, default=10)
318
+ bounty.add_argument("--rate", type=float, default=0.0)
319
+ bounty.add_argument("--no-verify", action="store_true")
320
+ bounty.add_argument("--timeout", type=float, default=30.0)
321
+
322
+ return parser
323
+
324
+
325
+ SUBCOMMANDS: frozenset[str] = frozenset(
326
+ {
327
+ "history",
328
+ "replay",
329
+ "curl",
330
+ "export-history",
331
+ "delete-history",
332
+ "clear-history",
333
+ "setup",
334
+ "doctor",
335
+ "self-update",
336
+ "payloads",
337
+ "discover",
338
+ "bounty-scan",
339
+ "validate",
340
+ "proxy",
341
+ }
342
+ )