v2.1.9
Resilience: close confirmed ingestion-boundary fault-injection gaps
A fault-injection sweep across every external-input boundary (DNS, CT / cert,
identity endpoints, HTTP, file / cache) confirmed four gaps reachable by an
attacker who controls a queried domain or a CT provider. Each now degrades
cleanly. The same sweep rejected seven other candidates as already neutralized
by existing guards (recorded in docs/security-audit-resolutions.md).
- HTTP decompression bomb (High): the 10 MB body cap counts compressed
transfer bytes, but httpx decodes Content-Encoding downstream, so a ~9 MB gzip
body could decode to ~9 GB and exhaust memory onresp.json()/resp.text.
recon now requestsAccept-Encoding: identityand the SSRF transport refuses
any response that still carries a compressing Content-Encoding (a host that
ignores the identity request is the bomb vector). The twohttp.pydocstrings
that claimed the byte cap defended against this are corrected. - Poisoned-cache RecursionError (Medium): a deeply-nested JSON file under
~/.reconraisesRecursionError(aRuntimeError, notValueError), which
the cache loaders did not catch, so a poisoned file crashed the next lookup
instead of degrading to a clean miss.RecursionErroris now caught in
cache_get,ct_cache_get,ct_cache_show, andrate_limit._load_persisted,
with a pre-read file-size cap on the cache loaders. - CT graph entry-count amplification (Medium): a small SAN set reused across
many CertSpotter issuances never tripped the node cap but re-ran the per-cert
clique build and grew the per-edge issuer list without bound (about 21 s and
150 MB worst case, blocking the event loop). Graph construction is now bounded
by_MAX_GRAPH_ENTRIES, per-edge issuer samples by_MAX_EDGE_ISSUER_SAMPLES,
and CertSpotter's accumulated entry list is capped like crt.sh's. - CT provider RecursionError (Low): the providers'
resp.json()guard caught
onlyValueError, so a deeply-nested payload skipped the provider-local
degrade (the orchestrator still prevented a crash); it now catches
(ValueError, RecursionError).
Gate: full pytest (2853 passed), ruff, pyright (0 errors), validate_fingerprint (841), branch coverage 85%.