From b8cb47badb17806b2de7cf16d8a1d79c6d1ae5c1 Mon Sep 17 00:00:00 2001 From: pax Date: Sat, 11 Apr 2026 16:19:17 -0500 Subject: [PATCH] =?UTF-8?q?security:=20fix=20#6=20=E2=80=94=20escape=20sou?= =?UTF-8?q?rce=20via=20build=5Fsource=5Fhtml=20in=20InfoPanel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the inline f-string concatenation of post.source into the RichText document with a call through build_source_html(), which escapes both the href value and the visible display text. Also escapes the filetype field for defense-in-depth — the value comes from a parsed URL suffix (effectively booru-controlled) and the previous code interpolated it raw. Removes the dead duplicate setText() call that wrote a plain-text version before being overwritten by the RichText version on the next line. Audit-Ref: SECURITY_AUDIT.md finding #6 Severity: Medium --- booru_viewer/gui/info_panel.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/booru_viewer/gui/info_panel.py b/booru_viewer/gui/info_panel.py index 84ffd02..cd16174 100644 --- a/booru_viewer/gui/info_panel.py +++ b/booru_viewer/gui/info_panel.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +from html import escape from pathlib import Path from PySide6.QtCore import Qt, Property, Signal @@ -12,6 +13,7 @@ from PySide6.QtWidgets import ( ) from ..core.api.base import Post +from ._source_html import build_source_html log = logging.getLogger("booru") @@ -115,28 +117,12 @@ class InfoPanel(QWidget): log.debug(f"InfoPanel: tag_categories={list(post.tag_categories.keys()) if post.tag_categories else 'empty'}") self._title.setText(f"Post #{post.id}") filetype = Path(post.file_url.split("?")[0]).suffix.lstrip(".").upper() if post.file_url else "unknown" - source = post.source or "none" - # Truncate display text but keep full URL for the link - source_full = source - if len(source) > 60: - source_display = source[:57] + "..." - else: - source_display = source - if source_full.startswith(("http://", "https://")): - source_html = f'{source_display}' - else: - source_html = source_display - from html import escape - self._details.setText( - f"Score: {post.score}\n" - f"Rating: {post.rating or 'unknown'}\n" - f"Filetype: {filetype}" - ) + source_html = build_source_html(post.source) self._details.setTextFormat(Qt.TextFormat.RichText) self._details.setText( f"Score: {post.score}
" f"Rating: {escape(post.rating or 'unknown')}
" - f"Filetype: {filetype}
" + f"Filetype: {escape(filetype)}
" f"Source: {source_html}" ) self._details.setOpenExternalLinks(True)