The fix, in one snippet
<!-- before --> <button onclick="save()">Save</button>
<!-- after --> <button id="save">Save</button>
<script src="/app.js"></script> // document.getElementById('save').addEventListener('click', save)
Illustrative values. Change the paths, hostnames and options to match your own site before using it.
The sections below explain what is tested, why it matters and the alternatives.
What we test
Scan.now parses the homepage and counts three constructs: <script> blocks without a src attribute, HTML attributes beginning with on (onclick, onload, onerror, and so on), and href or src values beginning with javascript:. The result is informational: it reports the counts and a few examples, and notes whether the current Content-Security-Policy, if any, allows them through 'unsafe-inline' or through nonces and hashes. The reason this inventory exists is that the constructs determine which CSP you can deploy. Nonces cover inline <script> blocks but not event-handler attributes or javascript: URLs, which need 'unsafe-hashes' plus a hash of each handler's text. Related checks: content-security-policy and csp-weak-directives.
Why it matters
A Content-Security-Policy stops cross-site scripting by refusing to run script that does not come from an approved source, and in practice the biggest obstacle to enforcing one is a codebase full of inline handlers. Teams discover that a strict policy breaks the site, add 'unsafe-inline' to make it work, and end up with a policy that permits exactly the injected inline script CSP was meant to block. Inline handlers are also where injection bugs tend to sit in server-rendered templates: a value interpolated into onclick="show('...')" is in a double context (HTML attribute and JavaScript string) that ordinary HTML escaping does not protect. Moving behaviour into external files or nonce-carrying blocks makes both problems tractable. The CSP guide describes the migration path and the XSS guide the attack it defends against.
How to fix it
Replace attribute handlers with listeners registered from a script file. Before:
<button onclick="openMenu()">Menu</button>
<a href='javascript:void(0)' onclick="track('cta')">Start</a>
After:
<button id="menu-btn">Menu</button>
<a href='/start' data-track="cta">Start</a>
<script src="/js/ui.js" defer></script>
// ui.js
document.getElementById('menu-btn').addEventListener('click', openMenu);
document.querySelectorAll('[data-track]').forEach(el =>
el.addEventListener('click', () => track(el.dataset.track)));
Inline blocks that must stay inline (configuration, analytics bootstraps) get a per-response nonce:
<script nonce="{RANDOM}">window.config = {...};</script>
For a handler you cannot yet move, CSP Level 3 allows it by hash: script-src 'unsafe-hashes' 'sha256-<hash of the handler text>'. Deploy the target policy as Content-Security-Policy-Report-Only first; every violation report points at one inline construct to migrate. Modern frameworks (React, Vue, Svelte) attach handlers programmatically and produce none of these, so a rebuild on one of them clears the finding as a side effect.
Where this fits
Inline event handlers and scripts is check 8 of 10 that the website vulnerability scanner runs under page content and javascript, ordered the way they are worth fixing. That ordering is the point: Fixing this one while the check above it still fails buys less than it looks like.
Fix this one first
Above it in the same category sits Third-party scripts without Subresource Integrity (low), where scripts or stylesheets are loaded from third-party hosts without an integrity attribute. An attacker who has that does not need this, so it is the better use of the same hour.
What fixing this still leaves open
Inline event handlers and scripts closes one route in. Immediately below it: Password field autocomplete, where password fields on the page either try to disable autocomplete, which browsers ignore and password managers dislike, or lack the autocomplete tokens that let password managers fill and generate credentials safely; Third-party script inventory, where an inventory of every script the page loads from other domains.
Found in the same scan
The website vulnerability scanner reports this alongside checks from other categories that are at least as serious, including Certificate chain and hostname validation, where the certificate presented for this hostname did not validate: The chain does not reach a trusted root, an intermediate is missing, the name does not match, or the certificate is self-signed or expired, and Exposed .env configuration file, where a .env configuration file is served from the web root. A single run of website vulnerability scanner answers all of them at once.
The first few lines
You are a senior web engineer. I ran a security and SEO scanner against my site and it reported the finding below. Fix it properly rather than suppressing the symptom.
Finding: Inline event handlers and scripts (info severity)
Scanner check id: inline-event-handlers
The rest of this prompt names the pages and line numbers we found the problem on, the configuration to change, and the constraints a good answer has to respect. It is free, it just needs an account so the work is not scraped wholesale.
Sign in with GoogleSigning in is free and takes one click. We store your email address and nothing else.
References
Related guides
9 min read · Updated Sep 16, 2026
Content Security Policy (CSP): A Practical Guide to Writing One That Works
A Content Security Policy tells the browser which scripts, styles and resources a page may load, which defeats most cross-site...
Read the guide
8 min read · Updated Sep 19, 2026
Cross-Site Scripting (XSS) Explained: Reflected, Stored, DOM-Based and How to Prevent It
XSS lets an attacker run their own script in your users' browsers. The three types, what an attacker does with it and the layered...
Read the guide