WordPress Security Problems (and Why Headless Architectures Avoid Them)

WordPress Security Problems (and Why Headless Architectures Avoid Them)

WordPress security problems aren't a myth spread by competitors, and they aren't evidence that the WordPress team is careless — core itself is well-audited and rarely the weak point. The problem is structural: an architecture where thousands of third-party plugins run with full privileges inside a public-facing PHP application. Understanding that structure explains both why the incident statistics look the way they do, and why headless architectures sidestep most of the risk rather than patching it.

Table of Contents
  1. The Numbers, Plainly
  2. Why WordPress Is Structurally Exposed
  3. What Headless Actually Changes
  4. The Honest Caveats
  5. The Takeaway

The Numbers, Plainly

Patchstack's State of WordPress Security report counted 11,334 new vulnerabilities in the WordPress ecosystem in 2025 — a 42% jump over the previous year. The distribution is the story:

  • 91% of vulnerabilities were in plugins — only two were in WordPress core
  • The ecosystem now averages 250+ new plugin vulnerabilities per week
  • 46% of disclosed vulnerabilities had no patch available at disclosure time
  • Attackers move fast: 45% of heavily exploited flaws were exploited within 24 hours of disclosure, with a median time-to-first-exploit of about five hours

Read those last two together: nearly half of vulnerabilities go public unpatched, and exploitation starts in hours. That's the race every WordPress site owner is running, whether they know it or not.

Padlock and chain resting on a computer keyboard, symbolizing website security
The lock is only as strong as the noisiest plugin behind it. Source: Pexels

Why WordPress Is Structurally Exposed

Four architectural facts do most of the damage:

  • Every page load executes code. WordPress renders pages with PHP on request, which means the application — and every active plugin — is reachable from the public internet on every visit.
  • Plugins run with full privileges. There's no sandbox. A vulnerability in a contact form plugin can read the database, write files, and create admin users, because the plugin API allows all of it.
  • The admin lives on the same domain as the site. /wp-admin and /wp-login.php are discoverable on every WordPress site on earth, which is why every WordPress site on earth gets credential-stuffing traffic.
  • The ecosystem is the attack surface. The average business site runs 20+ plugins from 20+ different authors with wildly different security practices. You inherit the weakest one.

Security plugins — Wordfence, Sucuri, and the rest — exist to guard this surface, and they help. But note what that means: the standard fix for WordPress security is another plugin inside the same blast radius. If the pattern of plugins patching platform weaknesses sounds familiar, it's one of the signs you've outgrown the platform — and the security retainer is a recurring line in the real cost of running WordPress.

Read also:

What Headless Actually Changes

A headless architecture separates the content management system from the public website. The visitor-facing site is pre-rendered static files (or a thin rendering layer) served from a CDN; the CMS lives elsewhere, often not publicly discoverable at all. That one design decision removes most of the attack surface:

  • No code execution on the public site. A static page can't be SQL-injected; there's no database behind it and no PHP to exploit. The most common WordPress attack categories simply have no target.
  • No universal admin URL. Credential stuffing needs a login page to hammer. When the CMS isn't at a guessable path on the public domain, drive-by attacks lose their entry point.
  • A radically smaller codebase in the request path. Serving a pre-built page involves a web server and a file. No plugin hooks, no theme functions, no third-party code deciding what executes.

The Honest Caveats

Headless is not magic. The CMS itself still needs authentication, updates, and sane access control; APIs can be misconfigured; build pipelines and tokens need protecting. What changes is the blast radius and the default posture: a vulnerability in the CMS no longer automatically endangers the public site, and doing the secure thing stops requiring a stack of guard plugins. How EmDash implements this — session handling, data protection, and the update model — is documented in EmDash CMS security: how your data is protected.

The Takeaway

You can run WordPress securely — plenty of teams do — but it's an ongoing operational discipline with a real annual cost, run against attackers who exploit new flaws within hours. Or you can choose an architecture where the public site is inert by construction. That difference in defaults is a large part of why businesses keep moving to headless platforms. If security is the push that finally starts your migration, plan it properly with the WordPress migration checklist.

Share

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

TL;DR — A custom session-cookie login flow appeared to succeed on localhost (the OTP verified, the response looked fine) but every subsequent request to a login-gated page treated the visitor as logged out. Identical code worked fine on the live HTTPS site. The cookie's Secure attribute was hardcoded to true — and per the cookie spec, browsers never store or send a Secure cookie over a plain, non-HTTPS connection.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Fix
  4. Lessons learned

Diagnostic

Check the actual Set-Cookie response header and the browser's own cookie storage panel — on localhost over http://, the cookie is sent by the server but never actually stored by the browser.

Root cause

// before -- assumes the app is always served over HTTPS
setCookie("session", token, { secure: true, httpOnly: true });
emdashkits.com

A cookie config that quietly assumes "we're always on HTTPS" breaks the instant you test over plain HTTP, which local dev servers commonly are.

Read also:

Fix

// after -- derive secure from the actual request protocol
const isHttps = request.url.startsWith("https://");
setCookie("session", token, { secure: isHttps, httpOnly: true });
emdashkits.com

Lessons learned

  • Any Secure-flagged cookie needs to key off the real request scheme, not an assumption baked in once at cookie-creation time.
  • "Works in production, silently fails in local dev" is a strong signal to check cookie flags before anything else in an auth flow.
  • Check other cookies in the same codebase for the same hardcoded assumption — if one cookie has this bug, sibling cookies set the same way are worth auditing too.
Share
Previous Article

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Comments

Write a comment

Related Articles

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

July 16, 2026

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

Google Analytics (GA4) Not Tracking Anything and No Console Errors: Check Your CSP First

TL;DR — GA4's gtag.js snippet was installed correctly, the page loaded with no visible JavaScript error, and GA4's real-time report still showed zero activity. The CMS's default Content-Security-Policy had no allowance for analytics domains and no config option to add one — so every request to Google's tracking endpoints was blocked at the browser level before it could fail loudly.

Table of Contents
  1. Diagnostic
  2. Root cause
  3. Lessons learned

Diagnostic

Check the browser's dedicated CSP violation reporting, not the regular console error list — CSP blocks are reported through their own channel, not thrown as normal script errors, so "no console errors" doesn't mean nothing was blocked.

Root cause

The CSP's script-src and connect-src directives had no entry for googletagmanager.com or google-analytics.com, and the CMS exposed no configuration surface to add one — the only way in was patching the CSP directives directly.

// patch-package: add analytics domains to the existing CSP directives
scriptSrc.push("https://www.googletagmanager.com");
connectSrc.push("https://www.google-analytics.com", "https://www.googletagmanager.com");
emdashkits.com
Read also:

Lessons learned

  • "No console errors" is not proof nothing was blocked — CSP violations live in their own reporting surface and are easy to miss if you're only scanning for red error text.
  • Before adding any third-party script tag to a site with a CSP already in place, check the CSP's directives first rather than assuming a silently-empty analytics dashboard means a snippet-installation mistake.
Share
Previous Article

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Next Article

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Comments

Write a comment

Related Articles

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

July 16, 2026

Login Works in Production but Fails on localhost: The secure Cookie Flag Gotcha

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

July 16, 2026

Native Module Build Fails on Shared Hosting (node-gyp, Old glibc/Python): The .npmrc Fix

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)

July 16, 2026

Uploaded Images Disappear After Every Deploy on Shared Hosting (and Other Reverse-Proxy Gotchas)