Conversation
Add a search feature that lets users search across all past and upcoming events by title, talk name, speaker name, or sponsor. Uses a static JSON index generated at build time and Fuse.js for fuzzy matching on the client. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
||
| const index = allEvents.map((event) => { | ||
| const override = overrides[event.eventUrl] || {}; | ||
| const description = (event.description || '').replace(/<[^>]*>/g, '').slice(0, 200); |
Check failure
Code scanning / CodeQL
Incomplete multi-character sanitization High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 11 days ago
In general, the best way to fix this is to replace the custom regex-based stripping with a robust HTML sanitization mechanism that safely converts arbitrary HTML input into plain text (or safely allowed HTML) and does not rely on a single multi-character regex pass. For this search index, the intended behavior appears to be: “strip HTML and keep a short text snippet,” so converting the HTML description to plain text is appropriate and preserves functionality.
The single best fix here, without changing the rest of the script’s behavior, is to replace the .replace(/<[^>]*>/g, '') with a safe HTML-to-text conversion function. Because this is a Node script, we can use a well-known library such as striptags, which is designed to remove HTML tags correctly and does not suffer from the incomplete multi-character sanitization problem. Concretely:
- Add an import for
striptagsat the top ofscripts/generate-search-index.mjs. - Replace the current computation of
descriptionat line 193 with a call tostriptags, followed by.slice(0, 200)as before.
No other logic needs to change: we still limit the text to 200 characters and keep the same structure of entry. The only functional difference is that descriptions are now stripped using a robust algorithm instead of a brittle regex, which addresses the CodeQL warning about incomplete sanitization.
| @@ -1,6 +1,7 @@ | ||
| import { writeFileSync } from 'node:fs'; | ||
| import { resolve, dirname } from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| import striptags from 'striptags'; | ||
|
|
||
| const MEETUP_GQL_URL = 'https://www.meetup.com/gql2'; | ||
| const LYONJS_MEETUP_ID = 18305583; | ||
| @@ -190,7 +191,7 @@ | ||
|
|
||
| const index = allEvents.map((event) => { | ||
| const override = overrides[event.eventUrl] || {}; | ||
| const description = (event.description || '').replace(/<[^>]*>/g, '').slice(0, 200); | ||
| const description = striptags(event.description || '').slice(0, 200); | ||
|
|
||
| const entry = { | ||
| id: event.id, |
| @@ -23,7 +23,8 @@ | ||
| "react-markdown": "^10.0.0", | ||
| "sharp": "^0.34.0", | ||
| "temporal-polyfill": "^0.3.0", | ||
| "yet-another-react-lightbox": "^3.21.4" | ||
| "yet-another-react-lightbox": "^3.21.4", | ||
| "striptags": "^3.2.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@playwright/test": "1.58.2", |
| Package | Version | Security advisories |
| striptags (npm) | 3.2.0 | None |
Summary
scripts/generate-search-index.mjsHow it works
generate-search-index.mjsfetches events from Meetup GraphQL API, enriches withdata-override.ts(talks, speakers, sponsors), writespublic/search-index.jsonTest plan
pnpm buildsucceeds (generates index then builds Next.js)🤖 Generated with Claude Code