fix(tradein): санитизация brand.logo_url (safeUrl) + валидация brand-цветов

#766 (security, чейн с #765): brand-поля рендерились без проверки.
- Topbar: logo_url → safeUrl (блок javascript:/data: схем); невалидный URL →
  fallback на TI-марку (без broken/опасного img), brand.name сохраняется.
- page.tsx: primary/accent_color под regex-guard (hex/rgb/hsl) перед setProperty;
  невалидный цвет → дефолт (защита от CSS-инъекции подменённым брендом).
Happy-path (praktika) не тронут.

Refs #766
This commit is contained in:
bot-frontend 2026-05-30 19:53:02 +03:00
parent a45d4b3cf4
commit 639ab3f63d
2 changed files with 22 additions and 4 deletions

View file

@ -58,8 +58,15 @@ export default function TradeInPage() {
useEffect(() => {
const root = document.documentElement;
if (!brand) return;
root.style.setProperty("--accent", brand.primary_color); // зелёный (бренд)
root.style.setProperty("--accent-2", brand.accent_color); // циан (акцент)
// Бренд-цвета идут в CSS custom-property без доверия (#766: подменённый бренд
// → CSS-инъекция). Применяем только валидный hex / rgb()/hsl(); иначе дефолт.
const COLOR_RE = /^#[0-9a-fA-F]{3,8}$|^(rgb|hsl)a?\([\d.,%\s/]+\)$/;
if (COLOR_RE.test(brand.primary_color)) {
root.style.setProperty("--accent", brand.primary_color); // зелёный (бренд)
}
if (COLOR_RE.test(brand.accent_color)) {
root.style.setProperty("--accent-2", brand.accent_color); // циан (акцент)
}
root.classList.add("brand-active");
return () => {
root.style.removeProperty("--accent");

View file

@ -5,6 +5,7 @@
import { UserMenu } from "@/components/auth/UserMenu";
import { API_BASE_URL, HTTPError } from "@/lib/api";
import { isPathAllowed } from "@/lib/isPathAllowed";
import { safeUrl } from "@/lib/safeUrl";
import { useBrand } from "@/lib/useBrand";
import { useMe } from "@/lib/useMe";
@ -94,9 +95,19 @@ export function Topbar({ active }: TopbarProps) {
<header className="topbar">
<div className="topbar-inner">
<div className="brand">
{brand?.logo_url ? (
{brand ? (
<>
<img src={brand.logo_url} alt={brand.name} className="brand-logo" />
{/* logo_url из бренд-конфига safeUrl (блок javascript:/data: схем, #766).
Невалидный URL fallback на TI-марку, без broken/опасного img. */}
{safeUrl(brand.logo_url) ? (
<img
src={safeUrl(brand.logo_url) as string}
alt={brand.name}
className="brand-logo"
/>
) : (
<span className="brand-mark">TI</span>
)}
<span className="brand-product">{brand.name}</span>
</>
) : (