-
v0.2.6 Stable
released this
2026-04-11 21:45:40 +00:00 | 119 commits to main since this releasev0.2.6
Security: 2026-04-10 audit remediation
Closes 12 of the 16 findings from the read-only audit at
docs/SECURITY_AUDIT.md. Two High, four Medium, four Low, and two Informational findings fixed; the four skipped Informational items are documented at the bottom. Each fix is its own commit on thesecurity/audit-2026-04-10branch with anAudit-Ref:trailer.- #1 SSRF (High): every httpx client now installs an event hook that resolves the target host and rejects loopback, RFC1918, link-local (including the 169.254.169.254 cloud-metadata endpoint), CGNAT, unique-local v6, and multicast. Hook fires on every redirect hop, not just the initial request. Behavior change: user-configured boorus pointing at private/loopback addresses now fail with
blocked request target ...instead of being probed. Test Connection on a local booru will be rejected. - #2 mpv (High): the embedded mpv instance is constructed with
ytdl=no,load_scripts=no, anddemuxer_lavf_o=protocol_whitelist=file,http,https,tls,tcp, plusinput_conf=/dev/nullon POSIX. Closes the yt-dlp delegation surface (CVE-prone extractors invoked on attacker-supplied URLs) and theconcat:/subfile:local-file-read gadget via ffmpeg's lavf demuxer. Behavior change: anyfile_urlwhose host is only handled by yt-dlp (youtube.com, reddit.com, ...) no longer plays. Boorus do not legitimately serve such URLs, so in practice this only affects hostile responses. - #3 Credential logging (Medium):
login,api_key,user_id, andpassword_hashare now stripped from URLs and params before any logging path emits them. Single redaction helper incore/api/_safety.py, called from the booru-base request hook and from each per-clientlog.debugline. - #4 DB + data dir permissions (Medium): on POSIX,
~/.local/share/booru-viewer/is now0o700andbooru.db(plus the-wal/-shmsidecars) is0o600. Behavior change: existing installs are tightened on next launch. Windows is unchanged — NTFS ACLs handle this separately. - #5 Lock leak (Medium): the per-URL coalesce lock table is capped at 4096 entries with LRU eviction. Eviction skips currently-held locks so a coroutine mid-
async withcan't be ripped out from under itself. - #6 HTML injection (Medium):
post.sourceis escaped before insertion into the info-panel rich text. Non-http(s) sources (includingjavascript:anddata:) render as plain escaped text without an<a>tag, so they can't become click targets. - #7 Windows reserved names (Low):
render_filename_templatenow prefixes filenames whose stem matches a reserved Windows device name (CON,PRN,AUX,NUL,COM1-9,LPT1-9) with_, regardless of host platform. Cross-OS library copies stay safe. - #8 PIL bomb cap (Low):
Image.MAX_IMAGE_PIXELS=256Mmoved fromcore/cache.py(where it was a side-effect of import order) tocore/__init__.py, so anybooru_viewer.core.*import installs the cap first. - #9 Dependency bounds (Low): upper bounds added to runtime deps in
pyproject.toml(httpx<1.0,Pillow<12.0,PySide6<7.0,python-mpv<2.0). Lock-file generation deferred — seeTODO.md. - #10 Early content validation (Low):
_do_downloadnow accumulates the first 16 bytes of the response and validates magic bytes before committing to writing the rest. A hostile server omitting Content-Type previously could burn up toMAX_DOWNLOAD_BYTES(500MB) of bandwidth before the post-download check rejected. - #14 Category fetcher body cap (Informational): HTML body the regex walks over in
CategoryFetcher.fetch_postis truncated at 2MB. Defense in depth — the regex is linear-bounded but a multi-MB hostile body still pegs CPU. - #16 Logging hook gap (Informational): e621 and detect_site_type clients now install the
_log_requesthook so their requests appear in the connection log alongside the base client. Absorbed into the #1 wiring commits since both files were already being touched.
Skipped (Wontfix), with reason:
- #11 64-bit hash truncation: not exploitable in practice (audit's own words). Fix would change every cache path and require a migration.
- #12 Referer leak through CDN redirects: intentional — booru CDNs gate downloads on Referer matching. Documented; not fixed.
- #13 hyprctl batch joining: user is trusted in the threat model and Hyprland controls the field. Informational only.
- #15 dead code in
core/images.py: code quality, not security. Out of scope under the no-refactor constraint. Logged inTODO.md.
Downloads
- #1 SSRF (High): every httpx client now installs an event hook that resolves the target host and rejects loopback, RFC1918, link-local (including the 169.254.169.254 cloud-metadata endpoint), CGNAT, unique-local v6, and multicast. Hook fires on every redirect hop, not just the initial request. Behavior change: user-configured boorus pointing at private/loopback addresses now fail with