Skip to content

GoatFlow 0.7.0: The Sandbox Release

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.

PolicyWhat it does
SQL table whitelistingQuery parsing rejects access to undeclared tables
Email domain scopingPlugins can only send to domains they declared, 10/min rate limit
Config key blacklistSensitive configuration patterns blocked by default
Call depth limitingPlugin-to-plugin chains capped at 10 (no infinite loops)
Caller identity stampingHost 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.


Questions? Feedback? Open a GitHub Discussion and let us know what you think!

Back to Blog