WebScan v2.5.2
WebScan v2.5.2 — MEDIUM security fixes + CI fix
🔒 Security — MEDIUM findings closed
M-1: --strict-ssl flag (CWE-295)
Previously --no-verify-ssl was a documented no-op — the scan engine always
disabled TLS verification regardless of the flag, and NetConfig.verify_ssl
was dead code. This was a deliberate design choice (scanners audit
self-signed hosts) but the dead flag misled operators who expected
verification to be enforced when they did NOT pass --no-verify-ssl.
This release wires up TLS verification properly:
- New
--strict-sslflag — when set, the scan engine uses a verifiable
SSL context (system CAs, hostname check). Useful for scanning production
sites where a valid cert is expected and a verification failure is itself
a finding. --no-verify-sslkept as no-op for backward compatibility.NetConfig.verify_sslis now live — read byScanEngineto choose
between verifiable and non-verifiable SSL contexts.engine._build_ssl_context(verify=...)takes averifyargument.
M-2: shared fetch_body() helper (CWE-400)
A full audit found ~30 places across plugins, crawler, and retry helper
where await resp.text(errors="ignore") was called without a body-size
cap. A hostile target serving a 10 GiB page would OOM the scanner —
particularly dangerous under webscan serve where one client could kill
every other scan.
This release introduces a single bounded reader:
webscan.plugins._active_helpers.fetch_body(resp, limit=2 MiB)—
reads at mostlimitbytes viaresp.content.read(), decodes as UTF-8.
Falls back toresp.text()for test fakes and the lightweightResponse
dataclass.- Applied across 28 plugin files — every
resp.text(errors="ignore")
replaced withfetch_body(resp). crawler._fetch_and_parse/crawler._load_robots— same pattern,
inline (crawler cannot import_active_helpersdue to layering).retry.request_with_retry— same pattern, inline.MAX_BODY_BYTES = 2 * 1024 * 1024exported as a module constant.
🛠 CI fixes
mypy 2.1.0 compatibility
webscan/server.py had three mypy errors in CI (but not locally, because
locally fastapi was installed):
Unused "type: ignore[assignment, misc]"onFastAPI = ... = None—
in CI (no fastapi) mypy treats the names as Any and the ignore is unused;
locally (with fastapi) mypy sees the real class types and the ignore is
required.Untyped decorator makes function "health" untypedand same for
scan_endpoint— the@app.get/@app.postdecorators are untyped
when fastapi is missing.
Fix: [[tool.mypy.overrides]] module = "webscan.server"; warn_unused_ignores = false in pyproject.toml. Bare # type: ignore on
the three offending lines keeps the file clean in both environments (CI
without extras, dev with all extras).
📊 Numbers
- 38 plugins (unchanged)
- 840 tests (unchanged)
- 97% coverage (unchanged)
- 61 source files
- ruff clean, mypy --strict clean (locally and in CI env)
⬆️ Upgrade
pip install --upgrade webscan-security==2.5.2📋 Remaining work
All HIGH and MEDIUM findings from the original audit are now closed. The
only outstanding items are LOW-severity polish:
- L-1 — pin transitive dependency upper bounds (aiohttp, fastapi, etc.)
inpyproject.toml. Currently using>=only. - L-4 — Dockerfile: add
HEALTHCHECK, pin base image by digest. - L-6 —
auto-release.yml: validate${GITHUB_REF}tag pattern
before use in--notes-filepath.