DEV Community

Jaime David Martinez
Jaime David Martinez

Posted on

Why 90% of CMS Hacks Happen Through Plugins (And How We Solved It With OS Process Isolation)

If you have managed websites or web applications for any length of time, you know the single most common vector for security breaches: untrusted or outdated third-party plugins.

According to security research across the WordPress ecosystem and traditional CMS architectures, over 90% of all CMS vulnerabilities originate in third-party add-ons, themes, and plugins — not in the core framework itself.

Why does this happen? And how can modern JavaScript architectures fix it at the operating-system level?


💥 The Fundamental Flaw of Traditional CMS Architectures

In almost every traditional CMS (WordPress, Ghost, Strapi, Payload):

+-------------------------------------------------------------+
|                      HOST PROCESS                           |
|  +------------------+  +------------------+                 |
|  |   Core CMS DB    |  |  Environment     |  Full Trust    |
|  |   Credentials    |  |  Secrets (.env)  |  ============== |
|  +------------------+  +------------------+                 |
|                             ^                               |
|                             | (Unchecked Direct Heap Access)|
|                             v                               |
|                     +---------------+                       |
|                     | Plugin Code   |                       |
|                     +---------------+                       |
+-------------------------------------------------------------+
Enter fullscreen mode Exit fullscreen mode

When you install an add-on or plugin:

  1. The plugin runs inside the host runtime process.
  2. It shares the host memory heap and permissions.
  3. It gets the keys to your entire infrastructure: your database passwords, .env files, filesystem, customer session cookies, and network access.

If a plugin author makes a mistake — or if their npm package/vendor dependency is compromised — the attacker inherits full host privileges. A simple slider plugin can read your .env file, exfiltrate your API keys, or turn your server into a botnet.


🛡️ The WordJS Solution: OS-Enforced Plugin Isolation

When building WordJS (an open-source website builder built on Node.js and SQLite), we decided to rethink plugin execution from scratch.

Instead of trusting plugins by default, WordJS treats every third-party plugin like an untrusted app on a mobile operating system (like Android or iOS).

Here is how the isolation architecture works:

+------------------------------------------------------------------------+
|                          HOST PROCESS                                  |
|   +----------------------------------------------------------------+   |
|   | Core Engine (Secrets, Core DB Tables, User Auth, HTTPS)        |   |
|   +----------------------------------------------------------------+   |
|                               ^                                        |
|                               | RPC Bridge (Permission-Checked)        |
|                               v                                        |
|   +----------------------------------------------------------------+   |
|   | Capability Guard (Verifies requested vs granted capabilities)  |   |
|   +----------------------------------------------------------------+   |
+-------------------------------+----------------------------------------+
                                | (IPC Pipe Only - No Shared Heap)
                                v
+------------------------------------------------------------------------+
|                       CHILD PROCESS (OS Sandbox)                        |
|   +----------------------------------------------------------------+   |
|   | Plugin Heap & Event Loop (`child_process.fork`)                 |   |
|   | - Egress Socket Trap (Blocks Private IPs & Cloud Metadata)     |   |
|   | - Filesystem Scoped View                                        |   |
|   | - Database Scoped View (`wjp_<slug>_` tables only)             |   |
|   +----------------------------------------------------------------+   |
+------------------------------------------------------------------------+
Enter fullscreen mode Exit fullscreen mode

⚙️ How It Works (Grounded in Code)

1. Separate OS Processes per Plugin (child_process.fork)

Plugins do not execute in the host heap or inside shared thread memory. Each plugin is spawned into its own isolated process:

// backend/src/core/plugin-isolate.ts
const child = fork(workerPath, [pluginSlug], {
  execArgv: ['--max-old-space-size=128'], // Enforced OS memory cap
  stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
  env: secureEnvironment() // Strips host secrets & .env credentials
});
Enter fullscreen mode Exit fullscreen mode

If a plugin suffers a memory leak, infinite loop, or heap exploit, only that child process dies. The host server and all other plugins continue running uninterrupted.

2. Default-Deny Android-Style Capability Grants

When a plugin is installed, its manifest.json must explicitly declare the capabilities it requires:

{
  "name": "sample-plugin",
  "capabilities": ["database", "users:read"]
}
Enter fullscreen mode Exit fullscreen mode

By default, all capabilities are denied. An admin must explicitly approve what each plugin is allowed to access:

// backend/src/core/plugin-permissions.ts
export function verifyPluginPermission(
  pluginSlug: string, 
  capability: CapabilityName
): boolean {
  const granted = getGrantedCapabilities(pluginSlug);
  return granted.includes(capability); // Strict default-deny check
}
Enter fullscreen mode Exit fullscreen mode

Plugins cannot access core system tables (users, sessions, options). Database queries are automatically constrained to dedicated wjp_<slug>_ table prefixes.

3. Egress Network Trap (Anti-Exfiltration)

One of the most dangerous plugin exploits is silent data exfiltration. WordJS traps socket connections at the low-level binding layer:

// backend/src/core/egress-guard.ts
export function checkEgressTarget(host: string, ip: string): void {
  if (isPrivateIP(ip) || isCloudMetadataEndpoint(ip)) {
    throw new Error(`[Security Egress Trap] Blocked attempt to reach private destination: ${ip}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Even if a plugin is granted the network capability to call an external API, it cannot reach internal loopback services, local subnet IPs (RFC1918), or cloud metadata services (169.254.169.254).

4. Install-Time AST Code Scanner

Before a plugin is activated, WordJS parses its source code using acorn to detect forbidden patterns:

// backend/src/core/plugins.ts
const ast = acorn.parse(sourceCode, { ecmaVersion: 'latest' });
walk.full(ast, (node) => {
  if (node.type === 'CallExpression' && node.callee.name === 'eval') {
    throw new SecurityViolation('Forbidden use of eval() detected.');
  }
});
Enter fullscreen mode Exit fullscreen mode

If dynamic code generation (eval, Function(), obfuscated require) is detected, the plugin installation fails closed.


🎨 Beyond Security: Modern Web Building

Security shouldn't come at the cost of developer experience or usability. WordJS ships with:

  • 🎨 Visual Drag-and-Drop Editor: Native page builder in core powered by Puck with 30+ responsive blocks.
  • Zero-Config SSR: Server-side rendered pages with built-in metadata, OpenGraph cards, RSS, and sitemap.xml.
  • 📦 Single-Process Deployment: Run your whole site on Node.js + SQLite in one command:
npx create-wordjs@latest my-site
Enter fullscreen mode Exit fullscreen mode

🚀 Try WordJS & Explore the Code

WordJS is 100% free and open-source under the MIT license.

We would love to hear your thoughts on process sandboxing, capability-based security models, and the future of web building in Node.js!

Top comments (0)