How to fix Inline Event Handlers?

inline-event-handlers-present page

The issue

Event handlers written into HTML attributes, such as onclick and onerror, keep a site from enforcing a Content Security Policy that blocks injected code. The browser can’t tell a handler you wrote from one an attacker injected, so allowing yours means allowing theirs.

The cause is that the policy treats a handler attribute as inline script. It runs only if script-src allows 'unsafe-inline', and 'unsafe-inline' lets every injected handler and inline script run as well.

Scope

Handlers live in the markup of each page: templates, components, server-rendered fragments, third-party widgets and any HTML users can submit. Each source of markup has to be checked.

How to check you have it

  • In probeo: the policy side is reported as csp:script-src-unsafe-inline, graded major, when the policy lets inline scripts and handlers run.
  • By hand: in DevTools, search the Elements panel for attributes starting with on, such as onclick=, onload= and onerror=. In page source, search for on followed by an event name and =.
  • In the browser: with a policy that doesn’t allow 'unsafe-inline', each blocked handler is reported in the console.

The fix

Replace each handler attribute with addEventListener in a script file.

Mark the element with a class or data- attribute, select it in the script and attach the listener there. For elements added later, attach one listener to a parent and check event.target. Then remove 'unsafe-inline' from script-src.

Done when: no element carries an on… attribute, the policy doesn’t allow 'unsafe-inline' for scripts, and the check above comes back clean.

Do not

  • Do not add 'unsafe-inline' to the policy to keep handlers working.
  • Do not move a handler’s code into an inline <script> block instead.
  • Do not replace handlers with eval() or new Function().
  • Do not expect a nonce to cover a handler attribute.

FAQ

Why are inline event handlers considered separate from inline scripts?

They aren’t, as far as the policy is concerned. Both are inline script, both are blocked without 'unsafe-inline', and both are how injected code usually runs.

What about handlers on dynamically generated elements?

Use event delegation: one listener on a parent that exists at load, checking event.target for the element that was clicked. New children are covered without a listener each.

Can nonces be used with inline event handlers?

No. A nonce goes on a <script> element, and a handler attribute can’t carry one. The policy keyword 'unsafe-hashes' can allow specific handlers by hash, but moving them into a script file is the cleaner fix.

How does this affect third-party scripts that add handlers?

A third-party script that calls addEventListener is fine. One that writes HTML containing on… attributes breaks once 'unsafe-inline' is gone. Ask the vendor for a version that works under a strict policy, or replace it.

What happens to existing onclick handlers during migration?

Move each one’s code into a script file, attach it with addEventListener, test it and then delete the attribute. Doing it in that order keeps the page working throughout.

Are there performance differences between inline handlers and addEventListener?

Not in a way that matters for most pages. One delegated listener can replace many attribute handlers, which is slightly lighter.

Which product detects this

Probeo checks this on every page of your site as part of Site Clarity. This page explains one instance; the product tells you every page that has it.