# No global state pollution

- **Pattern:** `ab-002085` (`plugin-extension-architecture.isolation-security.no-global-pollution`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002085
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002085)

## Why it matters

When plugins receive mutable references to shared objects — config, `process.env`, shared caches — any plugin can modify state that all other plugins and the host depend on. Plugin A changes `config.apiUrl` and Plugin B silently uses the wrong endpoint. A plugin calling `require('dotenv').config()` overwrites environment variables for every module in the process. CWE-454 (Unintended Variable Set from External Scope) and CWE-829 cover this class. The failure mode is load-order-dependent and nearly impossible to reproduce in isolation, making it one of the hardest bugs to diagnose in plugin ecosystems.

## Severity rationale

High because plugins writing to shared global state or mutating passed-by-reference objects create load-order-dependent bugs between plugins that are invisible during individual plugin testing.

## Remediation

Freeze or deep-clone any object before passing it to plugin code. For config objects, a `Proxy` that throws on write gives a clear error attributing the mutation attempt to the offending plugin.

```typescript
// Freeze config before passing to plugins:
const pluginConfig = Object.freeze(structuredClone(hostConfig));

// Or use a Proxy to prevent writes:
const readonlyConfig = new Proxy(config, {
  set() { throw new Error('Config is read-only for plugins'); },
  deleteProperty() { throw new Error('Config is read-only for plugins'); }
});
```

## Detection

- **ID:** `no-global-pollution`
- **Severity:** `high`
- **What to look for:** Check whether the plugin system prevents plugins from modifying global state, monkey-patching host code, or polluting shared namespaces. Look for:
  1. Plugins modifying `global`, `window`, `globalThis`, or `process.env`
  2. Plugins monkey-patching prototype methods (e.g., `Array.prototype.customMethod = ...`)
  3. Plugins modifying shared objects passed by reference (e.g., mutating the host config object)
  4. Module-level side effects that affect other plugins (e.g., `require('dotenv').config()` modifying `process.env` for all modules)
  5. Whether the host passes frozen/readonly objects or mutable references to plugins
  Prevention patterns:
  - Fastify: `Object.freeze()` on decorated properties, encapsulated plugin context
  - Node.js worker threads: separate V8 isolates, no shared global scope
  - Figma: iframe-based sandbox with `postMessage` communication — complete isolation
- **Pass criteria:** Count all plugin files and check for global variable assignments. The plugin system prevents or discourages global state pollution. At least one of: plugins run in isolated scopes (workers, iframes, separate processes), shared objects are frozen or readonly, the plugin API does not expose mutable global references, OR documentation explicitly prohibits global modification with enforcement in reviews/testing. 0% of plugin code may write to global scope or modify host prototypes.
- **Fail criteria:** Quote the specific global assignments or prototype modifications found. Do not pass when any plugin modifies `window`, `global`, or built-in prototypes. Plugins have unrestricted access to global state. No frozen objects, no isolation, no documentation warning against global modification. Plugins can freely modify `global`, `process.env`, shared config objects, or prototype methods, affecting all other plugins and the host.
- **Skip (N/A) when:** Plugins run in a truly isolated environment by design (separate processes or VMs with no shared memory) where global pollution is architecturally impossible.
- **Detail on fail:** `"Plugins receive the config object by mutable reference. Plugin A can modify config.apiUrl and Plugin B will see the changed value. No Object.freeze(), no defensive copies, no isolation. Plugins also have access to process.env and can modify environment variables visible to the host and all other plugins."`
- **Remediation:** Global state pollution is the most common source of plugin-to-plugin conflicts. Two plugins that both modify the same global create an intermittent bug that depends on load order and is nearly impossible to reproduce.

  ```typescript
  // Freeze config before passing to plugins:
  const pluginConfig = Object.freeze(structuredClone(hostConfig));

  // Or use a Proxy to prevent writes:
  const readonlyConfig = new Proxy(config, {
    set() { throw new Error('Config is read-only for plugins'); },
    deleteProperty() { throw new Error('Config is read-only for plugins'); }
  });
  ```

## External references

- cwe CWE-829
- cwe CWE-454
- owasp A03

Taxons: injection-and-input-trust, access-control

HTML version: https://auditbuffet.com/patterns/ab-002085
