Skip to content

WebScan v2.5.2

Choose a tag to compare

@github-actions github-actions released this 21 Jun 18:36

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-ssl flag — 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-ssl kept as no-op for backward compatibility.
  • NetConfig.verify_ssl is now live — read by ScanEngine to choose
    between verifiable and non-verifiable SSL contexts.
  • engine._build_ssl_context(verify=...) takes a verify argument.

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 most limit bytes via resp.content.read(), decodes as UTF-8.
    Falls back to resp.text() for test fakes and the lightweight Response
    dataclass.
  • Applied across 28 plugin files — every resp.text(errors="ignore")
    replaced with fetch_body(resp).
  • crawler._fetch_and_parse / crawler._load_robots — same pattern,
    inline (crawler cannot import _active_helpers due to layering).
  • retry.request_with_retry — same pattern, inline.
  • MAX_BODY_BYTES = 2 * 1024 * 1024 exported 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]" on FastAPI = ... = 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" untyped and same for
    scan_endpoint — the @app.get / @app.post decorators 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.)
    in pyproject.toml. Currently using >= only.
  • L-4 — Dockerfile: add HEALTHCHECK, pin base image by digest.
  • L-6auto-release.yml: validate ${GITHUB_REF} tag pattern
    before use in --notes-file path.