diff --git a/backend/app/services/scrapers/stealth.py b/backend/app/services/scrapers/stealth.py index a1408f64..db02ad0c 100644 --- a/backend/app/services/scrapers/stealth.py +++ b/backend/app/services/scrapers/stealth.py @@ -57,7 +57,9 @@ def parse_proxy_url(proxy_url: str | None) -> dict[str, str] | None: return None parts = urlsplit(proxy_url) if not parts.hostname: - raise ValueError(f"proxy URL без host: {proxy_url!r}") + # НЕ эхо-им сырой proxy_url — он содержит пароль, а это исключение + # всплывает в kn_scrape_runs.error / log_progress / Sentry (#1945 sec-review). + raise ValueError(f"proxy URL без host (scheme={parts.scheme!r})") scheme = parts.scheme or "http" server = f"{scheme}://{parts.hostname}" if parts.port: diff --git a/backend/tests/services/scrapers/test_stealth_throttle_proxy.py b/backend/tests/services/scrapers/test_stealth_throttle_proxy.py index ac14a973..a1582e76 100644 --- a/backend/tests/services/scrapers/test_stealth_throttle_proxy.py +++ b/backend/tests/services/scrapers/test_stealth_throttle_proxy.py @@ -69,6 +69,17 @@ class TestParseProxyUrl: with pytest.raises(ValueError): parse_proxy_url("http://:8080") + def test_error_does_not_leak_credentials(self) -> None: + # ValueError всплывает в kn_scrape_runs.error / log_progress / Sentry — + # НЕ должен содержать пароль (#1945 sec-review). + bad = "http://secretuser:secretpass@:8080" # есть creds, но нет host + with pytest.raises(ValueError) as exc: + parse_proxy_url(bad) + msg = str(exc.value) + assert "secretpass" not in msg + assert "secretuser" not in msg + assert bad not in msg + class TestBrowserSessionThrottleThreading: """concurrency/jitter/proxy прокидываются в инстанс-поля BrowserSession."""