feat: Binärdateien auf Änderungen prüfen (check-assets / approve-assets)

Neue Befehle:
- `python -m scanner check-assets`    — SHA-256-Hashes aller JS/CSS/Bild-Assets
  aus dem letzten Snapshot fetchen und mit Baseline vergleichen
- `python -m scanner approve-assets`  — aktuelle Hashes als neue Baseline setzen

Scoring: geändertes JS 40 Pkt, CSS 30 Pkt, Bild/PDF 20 Pkt.
Asset-Baseline liegt in data/baseline/asset_hashes.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-06-12 09:26:59 +02:00
commit d803f79efe
7 changed files with 506 additions and 1 deletions

View file

@ -147,6 +147,49 @@ def check_external_links(
return results
# ---------------------------------------------------------------------------
# Asset hashing
# ---------------------------------------------------------------------------
def fetch_asset_hashes(
urls: list[str],
timeout: int = 15,
delay: float = 0.2,
) -> dict[str, dict]:
"""
Fetch binary assets and compute SHA-256 hashes via streaming GET.
Returns {url: {"sha256": str|None, "size": int|None, "error": str|None}}.
"""
import hashlib
results: dict[str, dict] = {}
session = requests.Session()
session.headers["User-Agent"] = "integrity-scanner/1.0 (asset-check)"
try:
for i, url in enumerate(urls):
if i > 0 and delay > 0:
time.sleep(delay)
logger.debug("Hashing asset: %s", url)
try:
resp = session.get(url, timeout=timeout, stream=True)
if resp.status_code == 200:
h = hashlib.sha256()
size = 0
for chunk in resp.iter_content(chunk_size=65536):
h.update(chunk)
size += len(chunk)
results[url] = {"sha256": h.hexdigest(), "size": size, "error": None}
else:
results[url] = {"sha256": None, "size": None, "error": f"HTTP {resp.status_code}"}
except requests.exceptions.RequestException as exc:
results[url] = {"sha256": None, "size": None, "error": str(exc)}
finally:
session.close()
return results
def is_suspicious_url(url: str) -> bool:
"""Flag URLs with names typical for webshells / backdoors."""
filename = urlparse(url).path.rsplit("/", 1)[-1]