renderMd() now correctly renders safe inline HTML tags that AI models
emit in their responses:
Pre-pass (ui.js):
Converts <strong>, <b>, <em>, <i>, <code>, <br> to their markdown
equivalents (**text**, *text*, `text`, newline) before the pipeline
runs. Code blocks and backtick spans are stashed first so their content
is never modified.
inlineMd() helper (ui.js):
New helper for processing inline formatting inside list items,
blockquotes, and headings. Previously these used esc() directly, which
escaped <strong>/<code> tags that had already been converted from HTML
by the pre-pass — causing them to appear as literal <strong> text
instead of rendering as bold. inlineMd() applies bold/italic/code
processing and then escapes only unknown tags.
Safety net (ui.js):
After the full pipeline, any HTML tags NOT emitted by our own renderer
(i.e. <img>, <script>, <iframe>, <svg>, <object>, etc.) are escaped
via esc(). The SAFE_TAGS allowlist covers every tag the pipeline itself
produces. XSS is fully blocked.
Active session gold style (sessions.js, style.css):
Active session item now uses gold/amber (#e8a030) instead of blue,
matching the logo gradient color for better visual hierarchy.
Project color border-left is skipped when the session is active
(gold always wins). Session items get border-radius: 0 8px 8px 0
to complement the left border indicator.
Tests (tests/test_sprint16.py — 74 tests):
- Static analysis: pre-pass, SAFE_TAGS, SAFE_INLINE, inlineMd present
- Behavioural: all safe tags render in paragraphs, list items (ul+ol),
blockquotes, headings (h1/h2/h3)
- Exact screenshot regression: the 4-item list with <strong> labels
and <code> values that was showing as literal text
- XSS: 7 attack vectors blocked (<img>, <script>, <iframe>, <svg>,
<object>, XSS inside bold, XSS nested inside <strong>)
- Edge cases: code block protection, double-escaping guards, br tag,
mixed markdown+HTML, inlineMd called in list/blockquote handlers
Tests: 312 passed, 0 failed.
1. Path traversal in _serve_static() [CRITICAL]
Sandbox resolved path to static/ directory using relative_to().
GET /static/../../../../etc/passwd now returns 404.
2. Skill category path traversal [HIGH]
Validate category param in skill save: reject values with '/' or '..'.
3. Gate /api/approval/inject_test to loopback only [HIGH]
Endpoint now returns 404 for any non-127.0.0.1 client,
preserving test functionality while blocking remote access.
4. Escape captured groups in renderMd() [HIGH]
All inline markdown regexes (bold, italic, headings, blockquote,
list items, table cells/headers, link labels) now run captured
text through esc() before inserting into innerHTML, preventing
XSS via AI-generated content.
5. SRI hashes for CDN resources + pin Mermaid version [MEDIUM]
Added integrity= + crossorigin= to all three PrismJS CDN tags.
Pinned Mermaid from floating @10 to @10.9.3 with SRI hash.
Tests: 224 passed, 0 failed.