GoatFlow 0.7.0 takes the plugin platform shipped in 0.6.5 and adds the security and operational layers needed before handing it to third-party developers. The dual WASM/gRPC runtime now has sandbox isolation, signed plugin verification, resource policies, hot reload, and zero-downtime deployments.
Plugin Sandbox Isolation
The Problem
The 0.6.5 plugin system had runtime isolation — WASM plugins were memory-sandboxed by wazero, gRPC plugins ran as separate processes. But the Host API was a flat surface. Any plugin could query any database table, send email to any domain, and consume resources without limits. Trusting plugin code is fine during development. Trusting third-party plugin code in production is not.
The Solution
Every plugin now runs inside a SandboxedHostAPI that wraps the real Host API with permission checks, rate limits, and resource accounting. The sandbox sits between the plugin and the host — every call passes through policy enforcement before reaching the actual implementation.
For gRPC plugins on Linux, we added OS-level process isolation. Each plugin process runs in its own namespace with Pdeathsig set and a minimal environment. No inherited host variables, restricted filesystem access, and automatic termination if the parent process dies.
The key design decision was making the sandbox transparent. Plugins don’t call sandbox-aware functions — they call the same Host API as before. The sandbox intercepts at the boundary. This means existing plugins work without modification, and new plugins don’t need to know about the security layer.
The Benefits
Defence in depth at two levels: runtime isolation (WASM memory sandbox / gRPC process isolation) and API-level policy enforcement (the SandboxedHostAPI). A plugin that somehow escapes its runtime sandbox still hits policy checks on every Host API call.
Resource Policies
The Problem
Different plugins need different capabilities. A statistics plugin needs read access to ticket tables. A notification plugin needs to send email. Neither should be able to do what the other does. A one-size-fits-all permission model either blocks legitimate use or allows too much.
The Solution
Plugins declare what they need via ResourceRequest in their manifest. The platform enforces ResourcePolicy limits that administrators can tune per-plugin through the admin UI.
Five policy surfaces emerged as necessary:
- SQL table whitelisting — a query parser inspects every database call and rejects access to undeclared tables. This required handling subqueries, CTEs, and cross-database placeholder styles (MySQL
?vs PostgreSQL$1). - Email domain scoping — plugins declare which domains they’ll send to. The platform rate-limits to 10 emails per minute per plugin.
- Config key blacklist — patterns like
*password*,*secret*,*token*are blocked by default so plugins can’t read sensitive configuration. - Call depth limiting — plugin-to-plugin chains are capped at 10 to prevent runaway recursion.
- Caller identity stamping — the host stamps every outbound call with the plugin’s identity, so downstream services know who’s actually making the request.
Policy updates take effect immediately through RWMutex-protected hot swap. No restart, no downtime. Policies persist as JSON in the sysconfig_modified table.
The Benefits
Administrators control exactly what each plugin can do without editing code or config files. The defaults are restrictive enough that most plugins work safely out of the box. The SQL query parser was the most complex piece — supporting the full range of query patterns across both database backends took several iterations — but it means a compromised plugin can’t SELECT * FROM users.
Signed Plugin Verification
The Problem
Plugin ZIPs are opaque binaries. Without verification, there’s no way to distinguish an official plugin from a tampered copy. In environments with compliance requirements, running unsigned code may not be acceptable.
The Solution
Optional ed25519 signature verification. Plugin authors sign their ZIP with a private key, producing a .sig file. At load time, GoatFlow checks the signature against known public keys.
The word “optional” was deliberate. During development, requiring signatures would slow the edit-compile-test cycle to a crawl. In production, administrators can enforce signature requirements. Two modes for two contexts.
The Benefits
A chain of trust from plugin author to running code, without making development painful. Ed25519 was chosen for speed and small key/signature sizes — no RSA key management overhead.
Hot Reload and Blue-Green Deployments
The Problem
Two different deployment contexts need different reload strategies. Developers want instant feedback — change code, see the result. Production operators want zero-downtime updates — no dropped requests during plugin upgrades.
The Solution
Development: fsnotify-based file watching detects when a WASM binary or gRPC plugin binary changes on disk. GoatFlow automatically reloads the plugin. Both runtimes are supported.
Production: Atomic blue-green swap. The new plugin version loads and registers alongside the running instance. Once the new version reports healthy, traffic switches in a single pointer swap. If the new version fails to load, the old one keeps serving.
The blue-green approach was necessary because the naive “stop old, start new” strategy creates a window where requests hit a missing plugin. Even a 100ms gap matters when you’re handling API traffic.
The Benefits
Developers get a tight feedback loop. Operators get invisible upgrades. The pointer swap means the transition is effectively instantaneous — there’s no window to drop requests.
ZIP Distribution Security
The Problem
Plugin distribution uses ZIP files containing WASM binaries (or native binaries), templates, assets, and i18n files. ZIP extraction is a well-known attack surface: symlink injection, zip bombs, and path traversal can all compromise the host.
The Solution
Extraction validates every entry before writing:
- Symlink detection rejects any archive containing symbolic links
- File size and count limits reject zip bombs before they consume disk
- Path traversal checks reject entries containing
../or absolute paths
The Benefits
Simple checks that close known attack vectors. These are the kind of security measures that feel obvious in hindsight but are easy to overlook when you’re focused on features.
Lessons
The hardest part of sandbox design wasn’t the sandbox itself — it was the seams. Every Host API function is a potential escape hatch, and each one needed its own policy surface. SQL whitelisting alone required a query parser that handles subqueries, CTEs, and two placeholder styles. The email rate limiter had to distinguish between plugins that legitimately send bursts (onboarding flows) and plugins that are just poorly written.
Getting defaults right — restrictive enough to be safe, permissive enough that most plugins work without custom policy tuning — took more iteration than building the sandbox. The insight: start locked down and loosen based on real plugin requirements, not hypothetical ones.
The blue-green reload was a late addition. We initially planned simple stop-start reloads, then realised during testing that even brief gaps caused 502s under load. The pointer-swap approach added complexity but eliminated a class of operational issues entirely.