In 0.6.5 we shipped a plugin platform and told you to write plugins for it. In 0.7.0 we made sure those plugins can’t burn down the house.
The dual-runtime system now has the security and operational layers it needs for production: every plugin runs in a sandbox, binaries can be cryptographically signed, resource policies are admin-tunable, and deployments happen with zero downtime. If 0.6.5 was “the plugin platform works,” 0.7.0 is “the plugin platform is safe to hand to strangers.”
┌─────────────────────────────────────────────┐
│ GoatFlow Host │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ WASM Plugin │ │ gRPC Plugin │ │
│ │ (wazero) │ │ (namespace) │ │
│ │ │ │ │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ Sandboxed │ │ │ │ Sandboxed │ │ │
│ │ │ HostAPI │ │ │ │ HostAPI │ │ │
│ │ └─────┬─────┘ │ │ └─────┬─────┘ │ │
│ └───────┼───────┘ └───────┼───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Resource Policies │ │
│ │ SQL whitelist · email scoping │ │
│ │ rate limits · call depth · identity│ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
Plugin Sandbox Isolation
Every plugin now runs inside a SandboxedHostAPI that enforces permissions, rate limits, and resource accounting at the host boundary. Plugins only see what their policy allows — and they don’t get to argue about it.
WASM plugins were already memory-sandboxed by wazero. The new layer adds API-level controls: which database tables a plugin can query, which email domains it can send to, and how many resources it can consume. Think of it as giving the plugin a library card instead of the master key.
gRPC plugins gain OS-level process isolation on Linux. Each plugin process runs in its own namespace with Pdeathsig set, a minimal environment (no inherited host variables), and restricted filesystem access. If the parent dies, the plugin dies with it. No orphans.
Resource Policies
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. “Trust but verify” — except we skip the trust part.
| Policy | What it does |
|---|---|
| SQL table whitelisting | Query parsing rejects access to undeclared tables |
| Email domain scoping | Plugins can only send to domains they declared, 10/min rate limit |
| Config key blacklist | Sensitive configuration patterns blocked by default |
| Call depth limiting | Plugin-to-plugin chains capped at 10 (no infinite loops) |
| Caller identity stamping | Host stamps every outbound call — no impersonation |
Policy updates take effect immediately via RWMutex-protected hot swap. No restart, no downtime, no “please wait while we reconfigure.” Policies persist as JSON in the sysconfig_modified table.
Signed Plugin Verification
Plugins can be signed with ed25519 keys. A .sig file alongside the plugin ZIP is verified at load time. During development, unsigned plugins still load — we’re not monsters. But in production, administrators can flip the switch and reject anything without a valid signature.
Hot Reload
Save a WASM binary or recompile a gRPC plugin and GoatFlow picks up the change automatically via fsnotify. No manual restart, no “did I remember to reload?” Both runtimes are supported. Your edit-compile-test loop just got shorter.
Blue-Green Plugin Reload
Production reloads use an atomic blue-green swap. The new plugin version loads and registers alongside the running instance. Once ready, traffic switches in a single pointer swap — no request-dropping window. If the new version fails to load, the old one keeps serving. Your users never notice.
This was one of those features where the implementation is subtle but the outcome is simple: plugin updates don’t cause blips.
ZIP Distribution Security
We hardened plugin ZIP extraction against the attacks you’d expect and a few you wouldn’t:
- Symlink detection — no sneaking references to
/etc/passwd - File size and count limits — zip bombs get rejected at the door
- Path traversal rejection —
../../is not a valid plugin directory
Statistics & Reporting
The first-party Statistics plugin — our own dogfooding exercise — gained dashboard API endpoints, CSV/Excel export, and RBAC filtering on all statistics endpoints. Queue-level permission enforcement ensures agents only see data for queues they have access to. Dashboard widgets with Chart.js visualisations are coming in 0.8.1.
By the Numbers
- 26 plugin platform checklist items, all complete
- 10 host API functions available to plugins (db, http, email, cache, cron, log, config, i18n, state, register)
- 0 request-dropping windows during plugin reload
- 1 pointer swap to go live
What’s Next
0.8.0 begins the GoatKit PaaS Core: universal custom fields on every entity, a plugin UI system for independent app frontends, organisations and multi-tenancy, secure encrypted settings, and entity deletion with recycle bin support. This is where GoatKit stops being a plugin platform and starts being an application platform.
0.8.1 will ship the Statistics plugin’s Chart.js dashboard widgets, mobile optimisation, WebAuthn/FIDO2 hardware key support, and performance benchmarks.
Bonus Track: Lessons from the Sandbox
Building plugin isolation taught us that the hardest part isn’t the sandbox — it’s 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 cross-database placeholder styles. The email rate limiter had to account for plugins that legitimately need to send bursts (onboarding flows) versus plugins that are just badly written. Getting the defaults right so that most plugins “just work” without custom policy tuning took more iteration than the sandbox itself.
- Source & containers: GoatFlow on GitHub
- Full changelog: CHANGELOG.md
- Helm chart:
oci://ghcr.io/goatkit/charts/goatflow
Questions? Feedback? Open a GitHub Discussion and let us know what you think!