Prompt of the Day: Implement Input Sanitization for User Content
Part 29 of 30 -- Prompt of the Day
Every input field in your application is a door. Most users walk through it normally. A few will try to kick it open. Input sanitization is the lock -- it ensures that whatever comes through that door cannot execute as code, rewrite your page, or compromise your users. XSS (cross-site scripting) and injection attacks remain among the most exploited vulnerability classes in production software: CVE-2025-67991, disclosed in early 2025, demonstrated a reflected XSS flaw in a widely used WordPress plugin caused entirely by unsanitized URL parameters. Sanitizing user content is not optional -- it is the baseline.
The Prompt
You are a security-focused software engineer auditing this application for input sanitization vulnerabilities.
Step 1 -- Map every user input surface:
- Identify all form fields, text areas, search inputs, and comment boxes.
- Identify all URL parameters and query strings that get read and rendered anywhere in the UI.
- Identify file upload fields and any metadata (filename, alt text, description) that gets stored or displayed.
- Produce a list of each input point, the component or route that handles it, and whether its output is rendered as plain text, markdown, or raw HTML.
Step 2 -- Implement server-side validation with Zod:
- Define a Zod schema for every API route that accepts user input. Validate req.body, req.query, and req.params before any business logic runs.
- Use allowlist patterns: restrict string fields to expected character sets (e.g., /^[a-zA-Z0-9 _-]+$/ for usernames), enforce .min() and .max() lengths on all text fields, and use .url() with a protocol refine check for any URL inputs.
- Return a structured 400 response on validation failure -- never leak schema details in the error message.
Step 3 -- Add HTML sanitization for rendered content:
- For any user content rendered as HTML on the client, wrap it with DOMPurify.sanitize() before passing it to dangerouslySetInnerHTML. Never render raw user HTML without this step.
- For server-side rendering or email templates, use the sanitize-html package with an explicit allowlist of tags and attributes. Strip script tags, on* event handlers, and data: URIs by default.
- Treat content type separately: plain text fields should be escaped (not sanitized); markdown fields should be converted to HTML first, then sanitized with DOMPurify; rich HTML fields need a fully configured allowlist that preserves formatting tags while removing anything executable.
Step 4 -- Handle file upload metadata safely:
- Sanitize filenames: strip path traversal characters (../, ..\), remove null bytes, and normalize to a safe slug before storage.
- Validate MIME type on the server using the file's actual bytes (magic numbers), not the Content-Type header.
- Never render a raw filename in an HTML context without escaping it first.
Step 5 -- Add Content Security Policy headers:
- Set a Content-Security-Policy header that restricts script-src to 'self' (plus any known CDN hashes). Disallow inline scripts with 'unsafe-inline' removed.
- Add X-Content-Type-Options: nosniff and X-Frame-Options: DENY.
- Configure a CSP report-uri or report-to endpoint so violations surface in your monitoring.
After each step, show me the code changes and confirm that legitimate content (formatted text, safe links, images from known domains) is preserved while script tags, event handlers, and data URIs are stripped.
Why It Works
This prompt forces the AI to think in layers rather than patching one field at a time. By starting with an input surface audit, it prevents the most common failure mode: sanitizing the obvious fields while missing URL parameters, file metadata, or a secondary rendering path. Separating validation (Zod) from sanitization (DOMPurify / sanitize-html) is deliberate -- validation rejects malformed structure before it ever reaches the sanitizer, reducing attack surface at two independent checkpoints.
The instruction to treat content types differently -- plain text, markdown, and rich HTML -- is what separates a thorough implementation from a false-security one. Applying DOMPurify to a plain text field is unnecessary; failing to apply it before rendering markdown-converted HTML is a real vulnerability. The prompt names both libraries with known, battle-tested track records: DOMPurify, developed by Cure53 and relied on by thousands of production applications, uses the browser's own DOM parser to sanitize HTML with context-awareness that regex-based filters cannot replicate. The final CSP step provides defense-in-depth -- even if a sanitizer is misconfigured, a strict policy can block script execution at the browser level.
The Anti-Prompt
Do not use this:
Make my app safe from XSS. Sanitize the inputs.
Why it fails: This tells the AI nothing about where inputs exist, how they are rendered, or which library to use. The AI will likely add a single sanitization call to one obvious form and consider the task complete, leaving URL parameters, file upload metadata, and server-side rendering paths completely unprotected. It also gives no guidance on content type distinctions, so the AI may strip formatting from a rich text editor or, worse, skip sanitization on markdown fields because they "look like plain text." Vague security prompts produce vague security implementations.
Variations
Variation 1 -- Rich text editor with DOMPurify allowlist:
For the rich text editor component, configure DOMPurify with an explicit ALLOWED_TAGS list limited to block and inline formatting elements (p, br, strong, em, h1-h3, ul, ol, li, blockquote, a, pre, code). Set ALLOWED_ATTR to href, target, and rel only. Add a DOMPurify hook that enforces rel="noopener noreferrer" on all anchor tags and rejects any href that does not begin with https:// or mailto:.
Variation 2 -- Markdown content pipeline:
For fields that store user-authored markdown, implement a two-step pipeline: first convert markdown to HTML using a library like marked or remark, then immediately pass the resulting HTML string through DOMPurify.sanitize() with a restricted allowlist before any rendering. Never render the raw markdown-converted HTML without the sanitization step.
Variation 3 -- Next.js API route validation middleware:
Create a reusable validateRequest(schema) middleware for Next.js API routes that accepts a Zod schema, runs schema.safeParse() against { body, query, params }, replaces req.body and req.query with the parsed (and transformed) values on success, and returns a 400 with structured field-level errors on failure. Apply this middleware to every route that touches user-supplied data.
Real-World Context
The OWASP Top 10 for 2025 lists injection -- the family that includes XSS -- as a top-five web application risk, noting that applications are vulnerable whenever user-supplied data is not validated, filtered, or sanitized before use. In December 2025, a stored XSS vulnerability was disclosed in the widely used Tiptap rich text editor extension (CVE-2025-14284), caused by unsanitized user input in link handling -- exactly the kind of edge case that a thorough, content-type-aware sanitization pass catches. The pattern is consistent: vulnerabilities appear not where developers remember to sanitize, but in the places they forgot to think about.
Ask The Guild
Where has input sanitization bitten you in the wild -- or where have you found a sanitization gap in a codebase you inherited? Share your war story (or your narrowest near-miss) in the comments.