Adds the PyQt6 Windows client (Homepage, Scan & results, About & updates, Settings), ClamAV scanning and quarantine, browser-download watch with an exclusive scan gate, Bolt loopback/PKCE sign-in with DPAPI key storage, Gitea release feed with checksum verification, opt-in diagnostics, and Windows/portable build tooling. Co-Authored-By: Claude Code <noreply@anthropic.com>
80 lines
3.4 KiB
Python
80 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
from boltclam import paths
|
|
from boltclam.config import SettingsStore
|
|
from boltclam.logging_setup import redact
|
|
from boltclam.logs import build_bundle, redact_paths, sanitize_text, upload_bundle
|
|
from boltclam.models import ClamAVInfo, Detection
|
|
from boltclam.storage import Storage
|
|
from boltclam.system_info import collect
|
|
|
|
|
|
def test_redaction_covers_tokens_and_emails():
|
|
text = "key=bolt_live_abcdef password=hunter2 user aaron@example.com"
|
|
redacted = sanitize_text(text)
|
|
assert "bolt_live_abcdef" not in redacted
|
|
assert "hunter2" not in redacted
|
|
assert "aaron@example.com" not in redacted
|
|
assert "[email]" in redacted or "[redacted]" in redacted
|
|
|
|
|
|
def test_path_redaction_uses_userprofile(isolated_env):
|
|
sample = str(Path.home() / "Downloads" / "bad.exe")
|
|
assert "%USERPROFILE%" in redact_paths(sample)
|
|
assert "Downloads" in redact_paths(sample)
|
|
|
|
|
|
def test_bundle_contains_redacted_logs_and_scan_summary(isolated_env):
|
|
settings = SettingsStore().load()
|
|
storage = Storage()
|
|
scan_id = storage.create_scan("homes", [str(Path.home() / "Downloads")])
|
|
detection = Detection(path=str(Path.home() / "Downloads" / "bad.exe"),
|
|
signature="Win.Test", size=10)
|
|
detection.id = storage.add_detection(scan_id, detection)
|
|
storage.finish_scan(scan_id, status="completed", scanned_files=10,
|
|
infected_files=1, errors=0, duration_seconds=2, cancelled=False)
|
|
paths.logs_dir().mkdir(parents=True, exist_ok=True)
|
|
(paths.logs_dir() / "boltclam.log").write_text(
|
|
f"token=bolt_live_supersecret user aaron@example.com path={Path.home()}/x\n",
|
|
encoding="utf-8")
|
|
info = ClamAVInfo(available=True, path=Path("/fake/clamscan"),
|
|
engine_version="1.4.3", db_version="27452",
|
|
db_path=Path("/fake/db"), db_date="2026-10-22",
|
|
db_age_days=1.0)
|
|
snapshot = collect(settings, info, storage, realtime_running=False, force=True)
|
|
bundle = build_bundle(settings, info, system_snapshot=snapshot,
|
|
account={"user": {"email": "aaron@example.com"}},
|
|
realtime_roots=[str(Path.home() / "Downloads")])
|
|
assert bundle.is_file()
|
|
with zipfile.ZipFile(bundle) as archive:
|
|
names = set(archive.namelist())
|
|
assert {"manifest.json", "boltclam-logs.txt", "system-summary.json",
|
|
"README.txt"} <= names
|
|
logs = archive.read("boltclam-logs.txt").decode()
|
|
assert "bolt_live_supersecret" not in logs
|
|
assert "aaron@example.com" not in logs
|
|
assert "%USERPROFILE%" in logs
|
|
manifest = json.loads(archive.read("manifest.json"))
|
|
assert manifest["app"] == "BoltClam"
|
|
assert manifest["account"]["email"] == "aaron@example.com"
|
|
summary = json.loads(archive.read("system-summary.json"))
|
|
assert summary["stats"]["latest_scan"]["id"] == scan_id
|
|
|
|
|
|
class FakeApi:
|
|
def upload_diagnostics(self, path, metadata=None):
|
|
return {"id": "42/test.zip", "bytes": Path(path).stat().st_size}
|
|
|
|
|
|
def test_upload_bundle_returns_server_id(isolated_env):
|
|
settings = SettingsStore().load()
|
|
storage = Storage()
|
|
info = ClamAVInfo(available=False, error="not installed")
|
|
bundle = build_bundle(settings, info)
|
|
result = upload_bundle(FakeApi(), bundle, settings)
|
|
assert result["id"] == "42/test.zip"
|