From f32c53738df979e30fbb7f9ea81c064c30fb0f69 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:10:31 +0000 Subject: [PATCH] Remove hardcoded fast.com API token and use SecretStr Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> --- src/netsleuth/config.py | 2 +- src/netsleuth/speed.py | 72 ++++++++++++++++++++++++++++++++--------- tests/test_speed.py | 10 +++--- 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/src/netsleuth/config.py b/src/netsleuth/config.py index 4c14b24..a50c39d 100644 --- a/src/netsleuth/config.py +++ b/src/netsleuth/config.py @@ -78,7 +78,7 @@ class Speedtest(BaseModel): upload_sizes_bytes: list[int] = Field(default_factory=lambda: [1_000_000, 5_000_000]) cloudflare_base_url: str = "https://speed.cloudflare.com" fastcom_api_url: str = "https://api.fast.com/netflix/speedtest/v2" - fastcom_api_token: str = "YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm" + fastcom_api_token: SecretStr | None = None ndt7_locate_url: str = "https://locate.measurementlab.net/v2/nearest/ndt/ndt7" bufferbloat_probe_interval_seconds: float = 0.2 diff --git a/src/netsleuth/speed.py b/src/netsleuth/speed.py index 505e1f0..bb71dc3 100644 --- a/src/netsleuth/speed.py +++ b/src/netsleuth/speed.py @@ -39,7 +39,10 @@ def parse_server_timing_cfl4(header: str) -> CfL4Stats | None: match = _CFL4_RE.search(header or "") if not match: return None - fields = {k: v[0] for k, v in parse_qs(match.group("query"), keep_blank_values=True).items()} + fields = { + k: v[0] + for k, v in parse_qs(match.group("query"), keep_blank_values=True).items() + } return CfL4Stats( rtt_ms=_us_to_ms(fields.get("rtt")), min_rtt_ms=_us_to_ms(fields.get("min_rtt")), @@ -51,7 +54,9 @@ def parse_server_timing_cfl4(header: str) -> CfL4Stats | None: ) -def bufferbloat_delta(idle_rtt_ms: float | None, loaded_rtts_ms: list[float]) -> float | None: +def bufferbloat_delta( + idle_rtt_ms: float | None, loaded_rtts_ms: list[float] +) -> float | None: if idle_rtt_ms is None or not loaded_rtts_ms: return None loaded = percentile(sorted(loaded_rtts_ms), 95.0) @@ -93,10 +98,17 @@ async def run_speed_cascade( duration_ms = int((time.perf_counter() - began) * 1000) if not result.download_mbps: attempts.append( - TierAttempt(tier=name, ok=False, reason="no throughput measured", duration_ms=duration_ms) + TierAttempt( + tier=name, + ok=False, + reason="no throughput measured", + duration_ms=duration_ms, + ) ) continue - attempts.append(TierAttempt(tier=name, ok=True, reason=None, duration_ms=duration_ms)) + attempts.append( + TierAttempt(tier=name, ok=True, reason=None, duration_ms=duration_ms) + ) result.tier_attempts = attempts return result return SpeedResult(method="none", tier_attempts=attempts) @@ -138,15 +150,21 @@ async def tier_ookla( server_info = payload.get("server") or {} return SpeedResult( method="ookla_bin", - download_mbps=round(mbps(download.get("bytes", 0), (download.get("elapsed", 0) or 0) / 1000), 3), - upload_mbps=round(mbps(upload.get("bytes", 0), (upload.get("elapsed", 0) or 0) / 1000), 3), + download_mbps=round( + mbps(download.get("bytes", 0), (download.get("elapsed", 0) or 0) / 1000), 3 + ), + upload_mbps=round( + mbps(upload.get("bytes", 0), (upload.get("elapsed", 0) or 0) / 1000), 3 + ), server=f"{server_info.get('name', '')} ({server_info.get('location', '')})".strip(), idle_rtt_ms=(payload.get("ping") or {}).get("latency"), server_country=server_info.get("country") or None, ) -async def tier_cloudflare(client: httpx.AsyncClient, cfg: Speedtest, timeout: float) -> SpeedResult: +async def tier_cloudflare( + client: httpx.AsyncClient, cfg: Speedtest, timeout: float +) -> SpeedResult: down_samples: list[tuple[int, float]] = [] cfl4: CfL4Stats | None = None for size in cfg.download_sizes_bytes: @@ -156,12 +174,16 @@ async def tier_cloudflare(client: httpx.AsyncClient, cfg: Speedtest, timeout: fl ) response.raise_for_status() down_samples.append((len(response.content), time.perf_counter() - began)) - cfl4 = parse_server_timing_cfl4(response.headers.get("server-timing", "")) or cfl4 + cfl4 = ( + parse_server_timing_cfl4(response.headers.get("server-timing", "")) or cfl4 + ) up_samples: list[tuple[int, float]] = [] for size in cfg.upload_sizes_bytes: payload = b"\x00" * size began = time.perf_counter() - response = await client.post(f"{cfg.cloudflare_base_url}/__up", content=payload, timeout=timeout) + response = await client.post( + f"{cfg.cloudflare_base_url}/__up", content=payload, timeout=timeout + ) response.raise_for_status() up_samples.append((size, time.perf_counter() - began)) return SpeedResult( @@ -173,10 +195,13 @@ async def tier_cloudflare(client: httpx.AsyncClient, cfg: Speedtest, timeout: fl ) -async def tier_fastcom(client: httpx.AsyncClient, cfg: Speedtest, timeout: float) -> SpeedResult: +async def tier_fastcom( + client: httpx.AsyncClient, cfg: Speedtest, timeout: float +) -> SpeedResult: + token = cfg.fastcom_api_token.get_secret_value() if cfg.fastcom_api_token else "" response = await client.get( cfg.fastcom_api_url, - params={"https": "true", "token": cfg.fastcom_api_token, "urlCount": "3"}, + params={"https": "true", "token": token, "urlCount": "3"}, timeout=timeout, ) response.raise_for_status() @@ -197,14 +222,21 @@ async def tier_fastcom(client: httpx.AsyncClient, cfg: Speedtest, timeout: float method="fastcom", download_mbps=throughput_from_samples(samples), upload_mbps=None, - server=targets[0].get("url").split("/")[2] if targets and targets[0].get("url") else None, + server=targets[0].get("url").split("/")[2] + if targets and targets[0].get("url") + else None, netflix_oca_onnet=on_net, - server_country=(targets[0].get("location") or {}).get("country") if targets else None, + server_country=(targets[0].get("location") or {}).get("country") + if targets + else None, ) async def tier_ndt7( - client: httpx.AsyncClient, cfg: Speedtest, timeout: float, source_ip: str | None = None + client: httpx.AsyncClient, + cfg: Speedtest, + timeout: float, + source_ip: str | None = None, ) -> SpeedResult: import websockets @@ -225,7 +257,11 @@ async def tier_ndt7( message = await asyncio.wait_for(socket.recv(), timeout=timeout) except (TimeoutError, Exception): break - total += len(message) if isinstance(message, (bytes, bytearray)) else len(message.encode()) + total += ( + len(message) + if isinstance(message, (bytes, bytearray)) + else len(message.encode()) + ) elapsed = time.perf_counter() - began return SpeedResult( method="ndt7", @@ -276,7 +312,11 @@ async def measure_with_bufferbloat( result.bufferbloat_down_ms = bufferbloat_delta(idle_rtt_ms, down_samples) result.bufferbloat_up_ms = bufferbloat_delta(idle_rtt_ms, up_samples) worst_delta = max( - [d for d in (result.bufferbloat_down_ms, result.bufferbloat_up_ms) if d is not None], + [ + d + for d in (result.bufferbloat_down_ms, result.bufferbloat_up_ms) + if d is not None + ], default=None, ) result.bufferbloat_grade = grade_bufferbloat(worst_delta, bands) diff --git a/tests/test_speed.py b/tests/test_speed.py index 3722359..2a666bb 100644 --- a/tests/test_speed.py +++ b/tests/test_speed.py @@ -463,7 +463,7 @@ async def test_tier_fastcom_happy_path(httpx_mock): config = Speedtest() api_url = config.fastcom_api_url httpx_mock.add_response( - url=f"{api_url}?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=3", + url=f"{api_url}?https=true&token=&urlCount=3", json={ "targets": [ { @@ -498,7 +498,7 @@ async def test_tier_fastcom_no_targets(httpx_mock): config = Speedtest() api_url = config.fastcom_api_url httpx_mock.add_response( - url=f"{api_url}?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=3", + url=f"{api_url}?https=true&token=&urlCount=3", json={"targets": []}, ) async with httpx.AsyncClient() as client: @@ -513,7 +513,7 @@ async def test_tier_fastcom_missing_urls(httpx_mock): config = Speedtest() api_url = config.fastcom_api_url httpx_mock.add_response( - url=f"{api_url}?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=3", + url=f"{api_url}?https=true&token=&urlCount=3", json={ "targets": [ {"location": {"country": "US"}}, @@ -540,7 +540,7 @@ async def test_tier_fastcom_api_error(httpx_mock): config = Speedtest() api_url = config.fastcom_api_url httpx_mock.add_response( - url=f"{api_url}?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=3", + url=f"{api_url}?https=true&token=&urlCount=3", status_code=500, ) async with httpx.AsyncClient() as client: @@ -553,7 +553,7 @@ async def test_tier_fastcom_download_error(httpx_mock): config = Speedtest() api_url = config.fastcom_api_url httpx_mock.add_response( - url=f"{api_url}?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=3", + url=f"{api_url}?https=true&token=&urlCount=3", json={ "targets": [ {