* Reduce and tighten comments and docstrings in tests Shorten verbose comments and docstrings across the test suite without changing any test logic. Remove narration that restates the next line, collapse long module and test docstrings to a single line, and drop banner separators. Keep regression context (issue and PR references, run ids), skip reasons, mocking and timing rationale, license headers, lint and type directives, and commented-out code. Comments and docstrings only: an AST signature check confirms no code, assertions, or string literals changed, and the suite byte-compiles cleanly. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
80 lines
2.3 KiB
Python
80 lines
2.3 KiB
Python
"""Security suite fixtures: an autouse network blocker refuses non-loopback socket.connect() so a regression reaching the internet fails loudly."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import socket
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
# Make `scripts/` importable so tests can grab scanner constants directly.
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
if str(REPO_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
|
|
|
|
_LOOPBACK_PREFIXES = ("127.", "::1", "localhost")
|
|
|
|
|
|
def _is_loopback(host: str | bytes) -> bool:
|
|
if isinstance(host, bytes):
|
|
try:
|
|
host = host.decode("utf-8")
|
|
except UnicodeDecodeError:
|
|
return False
|
|
if not host:
|
|
return False
|
|
host = host.strip()
|
|
if host in {"::1", "localhost", "0.0.0.0"}:
|
|
return True
|
|
return host.startswith("127.")
|
|
|
|
|
|
class _BlockedSocket(socket.socket):
|
|
"""Socket subclass that refuses any non-loopback connect()."""
|
|
|
|
def connect(self, address): # type: ignore[override]
|
|
host = None
|
|
if isinstance(address, tuple) and address:
|
|
host = address[0]
|
|
if not _is_loopback(host or ""):
|
|
raise RuntimeError(
|
|
f"network access blocked by tests/security/conftest.py "
|
|
f"(attempted connect to {address!r}); the scanner suite "
|
|
"must run fully offline"
|
|
)
|
|
return super().connect(address)
|
|
|
|
def connect_ex(self, address): # type: ignore[override]
|
|
host = None
|
|
if isinstance(address, tuple) and address:
|
|
host = address[0]
|
|
if not _is_loopback(host or ""):
|
|
raise RuntimeError(
|
|
f"network access blocked by tests/security/conftest.py "
|
|
f"(attempted connect_ex to {address!r})"
|
|
)
|
|
return super().connect_ex(address)
|
|
|
|
|
|
@pytest.fixture(scope = "session", autouse = True)
|
|
def network_blocker():
|
|
"""Swap socket.socket for the blocker, restored at teardown."""
|
|
original = socket.socket
|
|
socket.socket = _BlockedSocket # type: ignore[assignment]
|
|
try:
|
|
yield
|
|
finally:
|
|
socket.socket = original # type: ignore[assignment]
|
|
|
|
|
|
@pytest.fixture(scope = "session")
|
|
def repo_root() -> Path:
|
|
return REPO_ROOT
|
|
|
|
|
|
@pytest.fixture(scope = "session")
|
|
def fixtures_dir() -> Path:
|
|
return Path(__file__).resolve().parent / "fixtures"
|