Using eval(), new Function(), or innerHTML with user-controlled strings enables arbitrary code execution in the browser. [CWE-95 · A03:2021]
prevents arbitrary JavaScript execution in the user's browser
BeforeMerge scans your pull requests against this rule and 4+ others. Get actionable feedback before code ships.
Impact: CRITICAL (prevents arbitrary JavaScript execution in the user's browser)
eval(), new Function(), setTimeout(string), and setInterval(string) all parse and execute strings as JavaScript. If any part of the string originates from user input — URL parameters, form fields, API responses, postMessage events — an attacker can execute arbitrary code in the user's browser context. This grants access to cookies, localStorage, the DOM, and any API the user can call.
Incorrect (executing user-controlled strings):
// ❌ eval with user input for "dynamic filtering"
function applyFilter(filterExpression: string, data: any[]) {
return data.filter((item) => eval(filterExpression))
}
// ❌ new Function() with user-controlled template
function renderTemplate(template: string, context: Record<string, unknown>) {
const fn = new Function('ctx', `return \`${template}\``)
return fn(context)
}
// ❌ setTimeout with string argument
function scheduleAction(userCode: string) {
setTimeout(userCode, 1000)
}
// ❌ innerHTML with user content (bypasses React's escaping)
function RawContent({ html }: { html: string }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (ref.current) {
ref.current.innerHTML = html // ❌ XSS vector
}
}, [html])
return <div ref={ref} />
}Correct (use safe alternatives):
// ✅ Use a predefined set of filter functions instead of eval
const FILTERS: Record<string, (item: Product) => boolean> = {
inStock: (item) => item.quantity > 0,
onSale: (item) => item.discount > 0,
premium: (item) => item.price > 100,
}
function applyFilter(filterName: string, data: Product[]) {
const filterFn = FILTERS[filterName]
if (!filterFn) throw new Error(`Unknown filter: ${filterName}`)
return data.filter(filterFn)
}
// ✅ Use template literals with explicit interpolation
function renderTemplate(template: string, context: Record<string, string>) {
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
return context[key] ?? ''
})
}
// ✅ setTimeout with function reference, not string
function scheduleAction(action: () => void) {
setTimeout(action, 1000)
}
// ✅ Use dangerouslySetInnerHTML with DOMPurify (see sec-dangerouslysetinnerhtml rule)
import DOMPurify from 'dompurify'
function RawContent({ html }: { html: string }) {
const clean = DOMPurify.sanitize(html)
return <div dangerouslySetInnerHTML={{ __html: clean }} />
}Additional context:
script-src directives can block eval() and new Function() at the browser level, providing defense in depth. Set 'unsafe-eval' only if absolutely necessary.expr-eval or mathjs that does not execute arbitrary JavaScript.no-eval catches eval() but not new Function() — enable no-new-func as well.Detection hints:
# Find eval, new Function, and string-based setTimeout/setInterval
grep -rn "eval(\|new Function(\|setTimeout(\s*['\"\`]\|setInterval(\s*['\"\`]" src/ --include="*.ts" --include="*.tsx"
# Find direct innerHTML assignments
grep -rn "\.innerHTML\s*=" src/ --include="*.ts" --include="*.tsx"Reference: MDN eval() — Never use eval!