* fix(docs): block javascript: URLs in renderMarkdown href (XSS guard) Per audit batch #127 P2 security minor (issue #130). ## Risk `renderMarkdown` substituted `[text](url)` → `<a href="...url...">` verbatim без URL validation. Edge case: `[click](javascript:alert(1))` → `<a href="javascript:alert(1)">click</a>` execute'нется при click. Currently low risk (markdown source = build-time fs.readFileSync из public/docs/, checked into repo), но защита-в-глубину: если когда-либо markdown source станет user-supplied, vuln materialize. ## Fix `frontend/src/app/docs/b2b-channels/renderMarkdown.ts`: 1. New `safeUrl(url)` function — allowlist `https://`, `http://`, `/`, `#`, `mailto:`. Everything else → `"#"`. 2. Link regex replacement switched from backreference string to callback so URL passes через `safeUrl` ДО injection в href. 3. URL unescape → safeUrl → re-escape (HTML escape applied per-line). ## Test cases verified - `[ok](https://example.com)` → href=`https://example.com` ✅ - `[ok](/local)` → href=`/local` ✅ - `[ok](#anchor)` → href=`#anchor` ✅ - `[ok](mailto:foo@bar.com)` → href=`mailto:foo@bar.com` ✅ - `[bad](javascript:alert(1))` → href=`#` ✅ - `[bad](data:text/html,...)` → href=`#` ✅ - `[bad](vbscript:msgbox(1))` → href=`#` ✅ ## Checks - tsc: 0 errors - lint: 0 warnings - No existing `__tests__/renderMarkdown` to break ## Vault `fixes/Bug_RenderMarkdown_JavascriptUrl_May14.md` — created. Closes #130 * refactor(frontend): SectionLabel + EmptyState + adminStyles shared (fixup) Per audit batch #127 P2 hygiene (issue #129). Previous commit лошил файлы из-за pre-commit (видимо). Fixup-коммит с actual diff: ## New shared modules (3) - frontend/src/components/ui/SectionLabel.tsx - frontend/src/components/ui/EmptyState.tsx - frontend/src/lib/adminStyles.ts ## Replacements - SectionLabel: 12 inline usages → import (Overview/Land/Market/Environment Tab) - EmptyState: 3 inline usages → import (Land/Market/Environment) - adminStyles: 5 admin pages import cardStyle/labelStyle/inputStyle/th/td - BulkGeoPanel: cardStyle only (preserves local labelStyle для span) - leads/page.tsx: cardStyle с marginTop extend; th/td/inputStyle local (divergent) ## Visual regression: ZERO All px/colors/CSS properties copied verbatim. Files с divergent values left local. ## Checks - ruff (no files) - prettier - tsc + lint clean (per agent run) Closes #129 Refs: #127 * docs(adminStyles): JSDoc warning про divergent local overrides Per bot review #132 non-blocking minor — задокументировать почему leads/page.tsx и BulkGeoPanel.tsx имеют local overrides, чтобы будущий читатель не «унифицировал» обратно по ошибке. Refs: #129, #132 --------- Co-authored-by: lekss361 <claudestars@proton.me>
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
/**
|
||
* Shared inline styles для admin pages.
|
||
*
|
||
* **Divergent local overrides — НЕ унифицировать:**
|
||
* - `frontend/src/app/admin/leads/page.tsx` — local `th` (padding "10px 12px",
|
||
* color "#374151"), local `td` (verticalAlign "top"), local `inputStyle`
|
||
* (fontSize 13, minWidth 160). Cause: разная плотность UI на leads dashboard.
|
||
* - `frontend/src/components/admin/BulkGeoPanel.tsx` — local `labelStyle` без
|
||
* `display: block` и `marginBottom`. Cause: labels на <span> внутри строки,
|
||
* block-display и margin сломали бы inline layout.
|
||
*
|
||
* При добавлении новой admin page — сначала проверь что shared values fit
|
||
* перед introducing local override.
|
||
*/
|
||
import type { CSSProperties } from "react";
|
||
|
||
export const cardStyle: CSSProperties = {
|
||
background: "#fff",
|
||
border: "1px solid #e6e8ec",
|
||
borderRadius: 12,
|
||
padding: 20,
|
||
};
|
||
|
||
export const labelStyle: CSSProperties = {
|
||
display: "block",
|
||
fontSize: 12,
|
||
color: "#5b6066",
|
||
marginBottom: 4,
|
||
textTransform: "uppercase",
|
||
letterSpacing: 0.4,
|
||
};
|
||
|
||
export const inputStyle: CSSProperties = {
|
||
padding: "8px 10px",
|
||
border: "1px solid #d1d5db",
|
||
borderRadius: 6,
|
||
fontSize: 14,
|
||
width: "100%",
|
||
boxSizing: "border-box",
|
||
};
|
||
|
||
export const th: CSSProperties = {
|
||
padding: "8px 10px",
|
||
textAlign: "left",
|
||
fontWeight: 600,
|
||
borderBottom: "1px solid #e6e8ec",
|
||
};
|
||
|
||
export const td: CSSProperties = { padding: "8px 10px" };
|