Skip to content

fix(security): prevent XSS in library search snippets#3

Merged
farce1 merged 1 commit intomainfrom
codex/fix-unsanitized-fts-snippets-in-search
Apr 19, 2026
Merged

fix(security): prevent XSS in library search snippets#3
farce1 merged 1 commit intomainfrom
codex/fix-unsanitized-fts-snippets-in-search

Conversation

@farce1
Copy link
Copy Markdown
Owner

@farce1 farce1 commented Apr 10, 2026

Motivation

  • The library search returned SQLite FTS snippet(...) output containing raw <mark> HTML that was rendered via dangerouslySetInnerHTML, allowing attacker-controlled meeting titles/transcripts to inject executable HTML/JS.
  • The goal was to remove unsafe HTML injection while preserving visible search highlight behavior and minimal surface change to existing UI.

Description

  • Added non-HTML sentinel constants SEARCH_SNIPPET_HIGHLIGHT_START and SEARCH_SNIPPET_HIGHLIGHT_END in src/lib/searchSnippet.ts to be used as FTS highlight markers instead of <mark> tags.
  • Updated the FTS query in src/hooks/useLibrary.ts to request snippets with the sentinel markers by switching the snippet(...) call to snippet(meetings_fts, 1, $2, $3, '...', 32) AS snippet and passing the new sentinel constants as query parameters.
  • Replaced unsafe dangerouslySetInnerHTML usage by removing renderSearchSnippet from src/views/LibraryView.tsx and changing SearchResultRow (src/components/library/SearchResultRow.tsx) to a safe renderer that splits the snippet on the sentinel tokens and constructs text nodes and explicit <mark> React nodes, ensuring any HTML in content is treated as text.
  • Kept the highlighting and visual behavior intact while eliminating injection of raw HTML into the DOM.

Testing

  • Ran a production build with npm run build, which completed successfully.
  • Executed unit tests for the touched library logic with npm run test -- src/hooks/useLibrary.test.ts, and all tests passed (1 file, 5 tests).

Codex Task

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c2fc83db8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +33 to +35
const start = remaining.indexOf(SEARCH_SNIPPET_HIGHLIGHT_START);

if (start === -1) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle literal highlight sentinel collisions

renderSnippet parses any occurrence of SEARCH_SNIPPET_HIGHLIGHT_START/END as markup boundaries, but those tokens now come from normal searchable content too, so a meeting transcript/title that literally contains these strings is mis-rendered. With an unmatched start token, the marker is stripped and text can be dropped (end === -1 path), and with both tokens present, arbitrary content is shown as highlighted even when it was not matched, which makes search results misleading for user-controlled data.

Useful? React with 👍 / 👎.

@farce1
Copy link
Copy Markdown
Owner Author

farce1 commented Apr 19, 2026

Code review — approved to merge

Reviewed both open XSS-fix PRs side-by-side (#2 and #3). This one is the stronger approach.

Why this over #2:

  • Eliminates dangerouslySetInnerHTML entirely — defense-in-depth, React handles text escaping natively
  • Uses non-HTML sentinel tokens at the SQLite layer, so no HTML ever enters the DOM pipeline
  • Avoids hand-rolled regex-based HTML allowlisting, which is a known footgun
  • Clean separation: FTS query owns markers, component owns rendering

Verified locally:

  • bun run test — 14/14 passed
  • bun run build — TypeScript check + Vite build succeed
  • Up-to-date with main, no conflicts

CI note:

  • "Dependency Review" is red on both PRs because Dependency graph is not enabled at the repo level (Settings → Security & analysis). Unrelated to this change; affects every PR. Enable it to unblock future PRs.

Edge case: if user content ever contains the literal sentinel string, it would be falsely highlighted — cosmetic only, not XSS. Safe.

Closing #2 as superseded.

@farce1 farce1 merged commit f2e1d5c into main Apr 19, 2026
2 of 3 checks passed
@farce1 farce1 deleted the codex/fix-unsanitized-fts-snippets-in-search branch April 19, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant