security: fix #6 — escape source via build_source_html in InfoPanel

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
This commit is contained in:
pax 2026-04-11 16:19:17 -05:00
parent fa4f2cb270
commit b8cb47badb

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from html import escape
from pathlib import Path from pathlib import Path
from PySide6.QtCore import Qt, Property, Signal from PySide6.QtCore import Qt, Property, Signal
@ -12,6 +13,7 @@ from PySide6.QtWidgets import (
) )
from ..core.api.base import Post from ..core.api.base import Post
from ._source_html import build_source_html
log = logging.getLogger("booru") 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'}") log.debug(f"InfoPanel: tag_categories={list(post.tag_categories.keys()) if post.tag_categories else 'empty'}")
self._title.setText(f"Post #{post.id}") self._title.setText(f"Post #{post.id}")
filetype = Path(post.file_url.split("?")[0]).suffix.lstrip(".").upper() if post.file_url else "unknown" filetype = Path(post.file_url.split("?")[0]).suffix.lstrip(".").upper() if post.file_url else "unknown"
source = post.source or "none" source_html = build_source_html(post.source)
# 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'<a href="{source_full}" style="color: #4fc3f7;">{source_display}</a>'
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}"
)
self._details.setTextFormat(Qt.TextFormat.RichText) self._details.setTextFormat(Qt.TextFormat.RichText)
self._details.setText( self._details.setText(
f"Score: {post.score}<br>" f"Score: {post.score}<br>"
f"Rating: {escape(post.rating or 'unknown')}<br>" f"Rating: {escape(post.rating or 'unknown')}<br>"
f"Filetype: {filetype}<br>" f"Filetype: {escape(filetype)}<br>"
f"Source: {source_html}" f"Source: {source_html}"
) )
self._details.setOpenExternalLinks(True) self._details.setOpenExternalLinks(True)