← Back to Blog

Every onclick= in your HTML is a security hole waiting to happen.

Every onclick= in your HTML is a security hole waiting to happen.

You inherited a codebase with onclick="doStuff()" scattered across 200 HTML files. It works. Nobody's complained. So what's the problem?

The problem shows up the moment you try to deploy a Content Security Policy. CSP is the single most effective defense against cross-site scripting attacks. But CSP with unsafe-inline allowed is barely a policy at all. And inline event handlers require unsafe-inline to function. Every onclick=, onerror=, onload=, and javascript: href in your markup forces you to keep the biggest hole in your security posture wide open.

Why inline handlers exist

HTML has supported inline event handlers since the 1990s. Netscape 2.0 shipped onclick in 1995. It was the only way to add interactivity before the DOM API matured. Three decades later, the pattern persists because it's easy to write and it works immediately without any JavaScript file.

But the web security model has changed. XSS is consistently in the OWASP Top 10. The defense is CSP with script-src directives that whitelist specific scripts by hash or nonce. Inline handlers can't be hashed individually the way <script> blocks can (technically unsafe-hashes exists in CSP Level 3, but browser support is incomplete and the approach doesn't scale). The practical result: if you have inline handlers, you either allow unsafe-inline (defeating CSP) or your page breaks.

The security math

With unsafe-inline allowed in your CSP, an attacker who finds any injection point (a reflected query parameter, a stored comment, a misconfigured template) can execute arbitrary JavaScript. Without unsafe-inline, injected <script> tags and inline handlers are blocked by the browser before they execute. That's the difference between "we got hacked" and "the attack was blocked."

This isn't theoretical. The 2018 British Airways breach exploited inline script injection on payment pages. Magecart attacks routinely inject inline handlers into e-commerce checkout flows. The pattern is well-established: find an injection point, drop inline JavaScript, exfiltrate credentials or payment data.

What to look for

The most common inline patterns:

<!-- Event handler attributes -->
<button onclick="submitForm()">Submit</button>
<img onerror="handleError(this)" src="photo.jpg">
<body onload="init()">

<!-- javascript: protocol -->
<a href="javascript:void(0)" onclick="navigate()">Click here</a>

<!-- document.write (different category, same CSP problem) -->
<script>document.write('<div>' + userInput + '</div>')</script>

Each of these requires unsafe-inline in your CSP to function. Each one is a pattern you can replace with a cleaner alternative.

The refactoring path

Replace inline handlers with addEventListener:

// Before: <button onclick="submitForm()">Submit</button>
// After:  <button id="submit-btn">Submit</button>
document.getElementById('submit-btn')
  .addEventListener('click', submitForm);

For javascript: hrefs, use real URLs or button elements:

<!-- Before -->
<a href="javascript:void(0)" onclick="navigate()">Click</a>

<!-- After -->
<button type="button" class="link-style" id="nav-trigger">Click</button>

For onerror fallback images, handle it in your JavaScript initialization:

document.querySelectorAll('img[data-fallback]').forEach(img => {
  img.addEventListener('error', () => {
    img.src = img.dataset.fallback;
  });
});

The refactoring is mechanical. It's not intellectually hard. It's tedious on large codebases, which is exactly why an automated audit helps.

What the tool does

The Inline Event Handler Audit scans your pages for all inline event handler attributes, javascript: protocol links, and document.write calls. It reports each instance with its location, the handler code, and a suggested refactoring approach.

The tool also checks your CSP header. If you have a CSP with unsafe-inline and the tool finds inline handlers, it connects the dots: these handlers are the reason you can't tighten your policy.

For sites with dozens or hundreds of inline handlers, the tool generates a prioritized remediation list starting with the highest-risk patterns (form handlers, error handlers on user-controlled content) and working down to low-risk patterns (decorative onload animations).

When it matters most

If you process payments, handle authentication, or store personal data, inline handlers are a compliance and liability issue, not just a best-practice concern. PCI DSS 4.0 (effective March 2025) explicitly requires protection against script injection. GDPR enforcement actions have cited inadequate XSS protection as a factor in breach penalties.

Even if you're running a content site with no sensitive data, cleaning up inline handlers lets you deploy a strict CSP. A strict CSP protects your visitors from injected crypto miners, redirect chains, and malvertising that might get planted through a compromised ad network or third-party widget.

If you're building a business on the web and want to understand the security foundations that protect both you and your visitors, The $100 Network ($9.99 on Kindle) covers the infrastructure decisions that separate durable web properties from fragile ones.

Fact-check notes and sources

Related reading

This post is informational, not security-consulting advice. Tool mentions are descriptive. No affiliation with browser vendors or standards bodies is implied.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility StatementPrivacy Policy

Last updated: April 2026