Problem
Contact form messages submitted by restaurant customers are stored in the database and later displayed in the admin panel without HTML sanitization. Menu item descriptions can also be edited by admins and displayed publicly without escaping. If any path allows HTML to reach the DOM unescaped, it creates an XSS vector. Additionally, next.config.ts has no Content Security Policy headers.
Proposed Solution
- Install
dompurify and @types/dompurify
- Create
frontend/lib/utils/sanitize.ts with a sanitize(input: string): string function wrapping DOMPurify.sanitize() with a strict allowlist (no <script>, no event handler attributes, no javascript: URIs)
- Apply
sanitize() to: contact messages in ContactDetailsModal, menu item descriptions in the public menu
- Add CSP headers in
next.config.ts via the headers() config function
- Add an ESLint rule to flag any new
dangerouslySetInnerHTML usage for review
Acceptance Criteria
Problem
Contact form messages submitted by restaurant customers are stored in the database and later displayed in the admin panel without HTML sanitization. Menu item descriptions can also be edited by admins and displayed publicly without escaping. If any path allows HTML to reach the DOM unescaped, it creates an XSS vector. Additionally,
next.config.tshas no Content Security Policy headers.Proposed Solution
dompurifyand@types/dompurifyfrontend/lib/utils/sanitize.tswith asanitize(input: string): stringfunction wrappingDOMPurify.sanitize()with a strict allowlist (no<script>, no event handler attributes, nojavascript:URIs)sanitize()to: contact messages inContactDetailsModal, menu item descriptions in the public menunext.config.tsvia theheaders()config functiondangerouslySetInnerHTMLusage for reviewAcceptance Criteria
<script>alert(1)</script>as a contact message renders as escaped text, not executable markupContent-Security-Policyheader present on all page responsesdompurifyallowlist is explicit and documented in the sanitize utilno-danger(or equivalent) added to.eslintrc