Files
rengine/shared/services/proxy_resolve.py
T
Yogesh OjhaandClaude Opus 5 6b4eac833f fix(scan): close the last gaps between a scan context and its tools
A pass over every tool the resolved config reaches, driving a real run
end to end and reading the argv each client builds.

vhost swept http:// whatever the run asked for. FfufClient took a scheme
argument nothing passed, so https_only did not hold for virtual-host
discovery — a hole in the setting shipped earlier today.

tlsx never went through the proxy. It was handed HTTP_PROXY env vars,
which a raw TLS dial does not read, and tlsx does define -proxy:

  -proxy socks5://u:p@10.0.0.1:1080  accepted
  -proxy 10.0.0.1:1080               first path segment cannot contain colon
  -proxy http://10.0.0.1:8080        proxy: unknown scheme: http

so it takes the URL when the scan's proxy is socks5, and refuses through
availability() otherwise rather than dialling direct. is_socks5() now
holds that decision for both tlsx and naabu.

Screenshots had no rate limit at any level: no field on the config and no
-rate-limit in the capture arguments, so a ceiling did not bind the
render pass.

The context's follow-redirects override reached only nuclei. The control
offers "Engine default", which promises an override, and http_probe and
endpoint_probe used their own setting regardless. origin_probe and
session_check keep theirs, which are requirements rather than
preferences.

basic auth with both fields blank sent Authorization: Basic Og== to every
target. Every other auth type already guarded on truthiness.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5roHYDYzSPRNaTkn8hSkJ
2026-09-13 12:31:26 +05:30

51 lines
1.5 KiB
Python

import json
from urllib.parse import urlsplit
from shared.models.proxy import Proxy, ProxyEndpoint
from shared.utils.crypto import decrypt_stored
def load_proxy_endpoints(proxy: Proxy) -> list[ProxyEndpoint]:
raw = decrypt_stored(proxy.endpoints_encrypted or "", label=f"Proxy {proxy.name!r}")
if not raw:
return []
return [ProxyEndpoint(**e) for e in json.loads(raw)]
SOCKS_SCHEMES = frozenset({"socks5", "socks5h"})
def is_socks5(proxy_url: str | None) -> bool:
"""Whether a tool that speaks only socks5 can carry this proxy."""
if not proxy_url:
return False
raw = proxy_url if "://" in proxy_url else f"socks5://{proxy_url}"
return urlsplit(raw).scheme.lower() in SOCKS_SCHEMES
def proxy_env(proxy_url: str | None) -> dict[str, str] | None:
"""Proxy env vars honoured by Go HTTP clients that take no proxy flag."""
if not proxy_url:
return None
return {
"HTTP_PROXY": proxy_url,
"HTTPS_PROXY": proxy_url,
"ALL_PROXY": proxy_url,
}
def build_proxy_url(ep: ProxyEndpoint) -> str:
if ep.username:
cred = f"{ep.username}:{ep.password}@" if ep.password else f"{ep.username}@"
return f"{ep.scheme}://{cred}{ep.host}:{ep.port}"
return f"{ep.scheme}://{ep.host}:{ep.port}"
def resolve_proxy_url(proxy: Proxy | None) -> str | None:
if proxy is None or not proxy.is_active:
return None
endpoints = load_proxy_endpoints(proxy)
if not endpoints:
return None
return build_proxy_url(endpoints[0])