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.
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.
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.
// 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'); }
});
ID: plugin-extension-architecture.isolation-security.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:
global, window, globalThis, or process.envArray.prototype.customMethod = ...)require('dotenv').config() modifying process.env for all modules)Object.freeze() on decorated properties, encapsulated plugin contextpostMessage communication — complete isolationPass 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.
// 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'); }
});