Launch OpenGPEX

Security & Isolation

OpenGPEX enforces a layered security model to protect users from malicious or buggy plugins while preserving the flexibility of the plugin system.


Three-Tier Safety Net

Tier 1: Safe Mode (URL Escape Hatch)

If a plugin causes the editor to crash on startup, users can bypass it:

http://localhost:3000/?safe-mode=true

In Safe Mode:

  • Only base plugins load — all community and user plugins are forcibly excluded
  • Related shortcuts and commands are also isolated
  • Console outputs [OpenGPEX Safe Mode] warning
  • User can then disable/remove the problematic plugin

Tier 2: UI Error Boundary

Every plugin component is wrapped in <PluginErrorBoundary>:

  • If a plugin throws during React render, only that plugin crashes
  • The rest of the editor (canvas, toolbars, other plugins) continues normally
  • The crashed area shows a warning icon with the plugin ID
  • User can save work and disable the plugin from GPEX Hub

Tier 3: Command Execution Isolation

All command execution is wrapped in try/catch within the core dispatcher:

// In useEditorStore.ts — executeCommand
try {
  return command.execute(context, payload);
} catch (error) {
  console.error(`[OpenGPEX Error] Command Failed (${id}):`, error);
  return undefined; // Prevents crash propagation
}

Interceptors (beforeExecute) are also independently try/caught.


Trust Levels (sourceType)

Level Meaning Capabilities
base Official core plugins Full access, cannot be disabled
community Verified third-party Full access, user can disable
user User-installed Sandboxed metadata, user can remove

Cloud Mode Restrictions

When running in GPEX-Cloud SaaS mode (GPEX_CLOUD_MODE=true):

  • ❌ Plugin upload API (/api/plugins/upload) returns 403 Forbidden
  • ❌ Upload UI button is physically hidden
  • ✅ Pre-installed base and community plugins function normally
  • ✅ This prevents untrusted code from executing in the shared cloud environment

Namespace Protection

Rule Enforcement
base.* namespace reserved Packager refuses to build; upload API rejects
Dynamic plugins get forced sourceType: 'user' Loader overwrites any attempts to impersonate base
UID computed from author + id Prevents two plugins from claiming the same namespace

Metadata Sandboxing (Dynamic Plugins)

When a ZIP plugin is dynamically loaded, the system forces:

  1. UID override: Computed fresh from manifest (prevents hijacking existing plugins)
  2. sourceType = 'user': Cannot masquerade as base or community
  3. group = 'gpex.plugins.user.': Forced into user namespace group

Plugin Configuration Access

  • Plugins access their own config via usePluginSelfConfig() (zero-argument)
  • Reading another plugin's config is possible via usePluginConfig(uid) but modifying it directly is discouraged
  • Cross-plugin interaction should flow through:
    • Command Bus — Explicit, auditable actions
    • Public Signals — Read-only state indicators for consumers

Runtime Environment

Plugins run in the same JavaScript context as the core engine (no iframe or Worker sandboxing). Isolation is enforced by convention:

  • Accessing DOM outside the plugin's slot → discouraged
  • Importing store internals directly → discouraged
  • Network access (fetch/XHR) → allowed (standard browser sandbox applies)
  • Local file system access → browser-restricted (requires user prompt)

Best Practices for Plugin Developers

  1. Don't abuse Error Boundaries — Handle known failure cases (network, JSON parse) with your own try/catch and show toast notifications
  2. Clean up event listeners — Always remove window.addEventListener in useEffect cleanup functions
  3. Isolate side effects — Don't modify state outside your plugin's scope (state.frames, other plugins' config)
  4. Use try...finally for async commands — Always call ctx.scoped.setBusy(false) in the finally block

Next Steps


Last updated: 2026-07-30