Plugin error boundaries
Why it matters
A plugin invocation loop without per-handler try/catch means a single third-party plugin throwing an uncaught error crashes the host, stops subsequent handlers from running, and surfaces as a 500 to every user — not just the user whose request triggered the broken plugin. CWE-755 (Improper Exception Handling) and ISO 25010 reliability.fault-tolerance both require that component failures be contained. The economic impact is proportional: one buggy plugin in an ecosystem of ten takes down applications that have nine functioning plugins installed.
Severity rationale
Critical because an unguarded hook invocation loop lets a single plugin error crash the host and stop all other plugins' handlers from executing, making application reliability contingent on the worst plugin installed.
Remediation
Wrap each handler invocation in an individual try/catch inside the loop. Log the plugin name and hook name alongside the error so operators can identify the offending plugin without a stack trace investigation.
async function invokeHook(name: string, context: HookContext): Promise<void> {
for (const { pluginName, handler } of this.getHandlers(name)) {
try {
await handler(context);
} catch (error) {
this.logger.error(`Plugin "${pluginName}" threw in hook "${name}":`, error);
this.emit('plugin:error', { pluginName, hook: name, error });
// Continue to next handler
}
}
}
Detection
-
ID:
error-boundaries -
Severity:
critical -
What to look for: Examine how the host invokes plugin code. Check whether each plugin's handlers are wrapped in try/catch (or equivalent error handling). Specifically look for:
- The hook invocation loop — does it catch errors from individual handlers and continue to the next, or does one uncaught error stop all subsequent handlers?
- Plugin lifecycle method calls (init, activate, etc.) — are they individually wrapped?
- What happens to the host when a plugin throws during a critical path (e.g., request handling)?
- Is there an error reporting mechanism so the host can log or surface which plugin caused the error? Real-world patterns:
- VS Code: extensions run in a separate extension host process — a crash only kills extensions, not the editor
- Fastify: plugin encapsulation with
avvio— plugin errors are contained - WordPress: fatal error handler catches plugin crashes and can disable the offending plugin
-
Pass criteria: Count all hook execution points with error handling. Plugin code execution is wrapped in error boundaries. A thrown error in one plugin does not crash the host application or prevent other plugins from running. The host logs or reports which plugin caused the error. At minimum, the hook invocation loop catches errors per-handler. 100% of hook execution paths must have error boundaries that prevent plugin failures from crashing the host.
-
Fail criteria: Must not pass when a single plugin error can crash the entire host application. Plugin handlers run without try/catch. An error thrown in any plugin's handler propagates up and crashes the host, stops the request, or prevents other plugins' handlers from executing. OR errors are caught but silently swallowed with no logging (impossible to debug which plugin is failing).
-
Skip (N/A) when: The system runs only first-party plugins that are tested as part of the host's CI (no third-party plugin support). Even then, consider marking as fail rather than skip for robustness.
-
Detail on fail:
"The invokeHook() function iterates handlers in a for loop with no try/catch. If the second of five handlers throws, handlers 3-5 never execute and the error propagates to the request handler, returning a 500 to the user. One broken plugin breaks the entire application for all users." -
Remediation: Error boundaries are non-negotiable for any plugin system that accepts third-party code. Without them, your application's reliability is only as good as the worst plugin installed.
async function invokeHook(name: string, context: HookContext): Promise<void> { for (const { pluginName, handler } of this.getHandlers(name)) { try { await handler(context); } catch (error) { this.logger.error(`Plugin "${pluginName}" threw in hook "${name}":`, error); this.emit('plugin:error', { pluginName, hook: name, error }); // Continue to next handler — don't let one plugin break others } } } -
Cross-reference: For resource cleanup on plugin failure, see the
resource-cleanupcheck in the Isolation & Security category.
External references
- iso-25010:2011 · reliability.fault-tolerance — Fault tolerance — plugin error boundaries prevent a single plugin failure from crashing the host
- cwe · CWE-755 — Improper Handling of Exceptional Conditions
Taxons
History
- 2026-04-18·v1.0.0·Initial import from plugin-extension-architecture·automated