fix(bitdefender): fourth-pass - urllib reset safety, Retry-After clamp, sweep/install-links id validation

From a third review pass (converging - all MEDIUM/LOW):
- urllib fallback: a post-send reset (RemoteDisconnected/ConnectionReset, which
  urllib wraps in URLError) was misclassified as always-safe 'connect' and could
  retry a non-idempotent write after a server commit. Now only ConnectionRefused/
  DNS (socket.gaierror) -> 'connect'; everything else -> 'timeout' (write-gated).
- _retry_delay clamps a negative numeric Retry-After to 0 (was -> time.sleep(-1) ValueError).
- cmd_sweep + cmd_install_links now validate --company; cmd_company_create validates
  --parent (finished _require_oid consistency - these mislogged as errorlog noise).
- cmd_push_test parses --extra-json before gating (validate->gate order, matches siblings).
- selftest: +sweep/install-links bad-company assertions. 81/81. Units: clamp + reset classification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 13:59:19 -07:00
parent 5af2fc09ec
commit 3d6cb467bf
3 changed files with 24 additions and 7 deletions

View File

@@ -23,6 +23,7 @@ import base64
import json
import os
import random
import socket
import subprocess
import sys
import tempfile
@@ -86,7 +87,9 @@ def _retry_delay(headers, attempt: int) -> float:
# An explicit server-mandated Retry-After is honored up to a HIGHER cap
# than the exponential backoff (don't retry early into another 429).
try:
return min(float(ra), RETRY_AFTER_MAX_SECONDS)
# clamp to [0, ceiling]: a malformed negative Retry-After must not
# reach time.sleep() (which raises ValueError on a negative value).
return min(max(float(ra), 0.0), RETRY_AFTER_MAX_SECONDS)
except (TypeError, ValueError):
try:
dt = parsedate_to_datetime(ra)
@@ -373,11 +376,16 @@ class GravityZoneClient:
except TimeoutError as exc:
raise _RetryableHTTP("timeout", detail=str(exc)) from exc
except urllib.error.URLError as exc:
# A bare URLError (not HTTPError) is a connection-level failure. If it
# wraps a read timeout it is ambiguous ('timeout'); otherwise it is a
# pre-send connect failure ('connect', always safe to retry).
# Classify conservatively: only a KNOWN pre-send failure (connection
# refused / DNS failure) is the always-safe 'connect'. Anything else -
# a connect/read timeout, or an ambiguous post-send reset like
# RemoteDisconnected/ConnectionResetError that urllib also wraps in
# URLError - is 'timeout' so a non-idempotent write is NOT retried.
reason = getattr(exc, "reason", None)
code = "timeout" if isinstance(reason, TimeoutError) else "connect"
if isinstance(reason, (ConnectionRefusedError, socket.gaierror)):
code = "connect"
else:
code = "timeout"
raise _RetryableHTTP(code, detail=str(exc)) from exc
try:
return json.loads(raw.decode("utf-8"))