npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Block script injection by encoding output, sanitizing HTML, and enforcing Content Security Policy
Prevents XSS attacks via input sanitization, output encoding, CSP headers, DOMPurify, and safe DOM APIs. Use for user-generated content, rich text editors, comments, and dynamic HTML.
Prevents XSS attacks via input sanitization, output encoding, CSP headers, and secure practices. Guides Node.js, Python, React implementations for user-generated content like comments and rich editors.
Implements secure frontend coding practices for XSS prevention, safe DOM manipulation, output sanitization with DOMPurify, CSP configuration, and client-side security code reviews.
Share bugs, ideas, or general feedback.
Block script injection by encoding output, sanitizing HTML, and enforcing Content Security Policy
innerHTML, dangerouslySetInnerHTML, or evalReact auto-escapes JSX expressions. The main risk is dangerouslySetInnerHTML:
// BAD — direct HTML injection
function Comment({ html }: { html: string }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
// GOOD — sanitize with DOMPurify before rendering
import DOMPurify from 'dompurify';
function Comment({ html }: { html: string }) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'ul', 'li', 'p'],
ALLOWED_ATTR: ['href', 'rel', 'target'],
});
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
In Express/NestJS template engines, always use auto-escaping. For raw HTML responses:
import escapeHtml from 'escape-html';
// Escapes <, >, &, ", ' — safe for HTML context
const safeOutput = escapeHtml(userInput);
// For HTML attribute context — additional encoding needed
function encodeAttr(value: string): string {
return value
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
CSP is the strongest browser-level defense. Set via HTTP header (not meta tag):
import helmet from 'helmet';
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'nonce-{NONCE}'"], // nonce-based is better than unsafe-inline
styleSrc: ["'self'", "'unsafe-inline'"], // Tailwind requires this (or use nonces)
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameAncestors: ["'none'"],
upgradeInsecureRequests: [],
},
})
);
For nonce-based CSP with Next.js or Express:
import crypto from 'crypto';
function cspMiddleware(req: Request, res: Response, next: NextFunction) {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.cspNonce = nonce;
res.setHeader(
'Content-Security-Policy',
`script-src 'self' 'nonce-${nonce}'; object-src 'none';`
);
next();
}
Avoid these JavaScript APIs with user input:
// DANGEROUS SINKS — never pass untrusted data to these
element.innerHTML = userInput; // use textContent instead
document.write(userInput);
eval(userInput);
setTimeout(userInput, 0); // string form of setTimeout
location.href = userInput; // validate URL schema first
element.setAttribute('src', userInput); // validate URL schema
// SAFE alternatives
element.textContent = userInput; // no HTML parsing
element.setAttribute('data-val', userInput); // data attributes are safe
// URL validation before href/src assignment
function isSafeUrl(url: string): boolean {
try {
const parsed = new URL(url, window.location.origin);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}
Three XSS types:
Defense priority:
DOMPurify server-side (SSR/Node):
import { JSDOM } from 'jsdom';
import createDOMPurify from 'dompurify';
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window as any);
const clean = DOMPurify.sanitize(dirty);
https://owasp.org/www-project-top-ten/