Website problems
Inline event handlers
An event handler written as an HTML attribute is inline script execution, and a Content-Security-Policy that blocks inline scripts blocks it too. Unlike an inline script element, it cannot be rescued with a nonce — there is nowhere to put one. So a page carrying handler attributes either has a policy permitting inline execution, which is the defence being given up, or has a policy that is silently breaking those handlers.
Why a nonce cannot cover them
A nonce is an attribute on a script element, matched against a value in the policy.
An event handler attribute is not a script element; it is an attribute on an arbitrary element with code as its value.
The specification provides no way to mark one as permitted, so a policy blocking inline execution blocks all of them.
The only keyword that allows them is the one that allows every inline script, which is the whole thing you were trying not to do.
Which makes them the single most common reason a strict policy is abandoned.
What the failure looks like
The page renders normally. Nothing about it appears broken until someone interacts with the element.
The handler does nothing, silently, with a violation message in the browser console that nobody has open.
So the breakage is discovered by users rather than by deployment, frequently days later.
Which is why moving the handlers comes before enforcing the policy rather than after.
How to move them
Replace the attribute with an event listener attached from a script file, matching on a selector or a data attribute.
One delegated listener on a container frequently replaces dozens of attributes, which is less work than it looks.
Where the handler carries state — an identifier, a value — put that in a data attribute and read it from the listener.
Server-rendered handlers generated in a template are the hardest case, and the same delegation approach applies once the data moves to attributes.
Framework code rarely produces these, so on a modern codebase they are usually concentrated in legacy templates and third-party snippets.
Why they are worth removing anyway
They mix behaviour into markup, which makes both harder to change and impossible to lint properly.
They cannot be attached with modern listener options, so passive and capture behaviour is unavailable.
Only one handler per event per element is possible, so two features touching the same element conflict silently.
They frequently contain the only copy of a piece of logic, duplicated across templates.
The policy argument is the urgent one; these are the reasons the change is worth keeping.
The related markup to look for
URLs using the JavaScript scheme in a link target, which are inline execution in a different position and blocked by the same directive.
Inline style attributes, which a style directive blocks under the same reasoning and which break layout rather than behaviour.
Server-rendered state written into an inline script element, which a nonce can cover and which is worth moving to a data attribute anyway.
Third-party snippets that inject handler attributes at runtime, which are invisible in the served markup and appear only in the console.
How to check it yourself
Search the served HTML for attributes beginning with `on` followed by an event name.
View source rather than using the element inspector, since the inspector shows the live DOM including handlers attached by script.
Check the pages generated by older templates, which is where they cluster.
Deploy the policy in report-only mode and read the violation reports, which name the exact elements.
Search for the JavaScript scheme in link targets at the same time, since they fail identically.
How VeriFixScan detects it
`structure.html_quality` flags inline event handlers in the stored markup sample, and reports the issue code for them.
`security.csp` reads the policy, which is what decides whether the handlers are permitted or silently broken.
`accessibility.interactive_elements` reports elements made interactive without the semantics that make them reachable, which frequently overlaps with handler attributes on non-interactive elements.
Only the served markup is examined, so handlers attached by script after load are outside this.
Why a report-only policy finds them first
A report-only policy sends a violation report for each blocked construct without blocking anything, so the site keeps working while the list is built.
The reports name the element and the document, which is far more precise than searching templates by hand.
They also catch the handlers injected at runtime by third-party snippets, which never appear in the served markup.
Running both a report-only and an enforcing policy at once is supported, which is how a stricter policy is tested against real traffic before it replaces the current one.
The reporting endpoint is worth keeping after enforcement begins, since it is what surfaces a newly added handler before a user finds it.
Frequently asked questions
- Can a nonce allow my inline handlers?
- No. A nonce is an attribute on a script element and an event handler is not one. The only keyword that permits them permits every inline script.
- Do I have to rewrite everything?
- Usually not. One delegated listener on a container replaces many attributes, with any state moved to data attributes. The work is concentrated in old templates.
- Are inline style attributes the same problem?
- Same mechanism, different directive, and a much smaller consequence — a blocked style breaks layout rather than behaviour. Many sites permit inline styles and not inline scripts.
Sources
Related
VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.
Scan a website