Design: remote plugins — one protocol, two localities, and a secret-release policy before values cross a network
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 9
- Forks
- 0
- Avg merge
- 3h 3m
- Merged PRs (30d)
- 509
Description
A plugin should be able to run locally (a subprocess beside the worker, the Terraform-provider shape) or remotely (a network service, the remote-MCP-server shape) — same tasks, same Flowfile spelling, different locality. This issue carves what that actually requires, because most of it already exists and the parts that do not are security design, not plumbing.
Related but distinct: #97 is distribution (how a plugin binary gets to a machine); this is execution locality (where the plugin process runs). #108 (MCP both directions), #114 (Nexus is grammar for invocation, plugin tasks for administration — remote plugins do not reopen that decision; the task still executes in the worker's activity, which calls out), #146 (integrity/pinning), #100 (ecosystem posture).
1. The protocol is already transportable — remote is identity + policy, not a new protocol
The plugin protocol is Connect RPC (proto/flowstate/plugin/v1) with the byte-cap enforced on the http.RoundTripper below the RPC library (plugin/transport.go). Nothing about Describe/Health/Execute assumes a subprocess. A remote plugin is the same contract over a network transport plus:
- Transport identity — TLS always; the endpoint's identity pinned at registration, not trusted on first use.
- Worker→plugin authn (see §3).
- Egress governance — the worker calling a plugin endpoint is egress and goes through
netpolicylike any other outbound call, deny-by-default. A plugin endpoint must never be a hole around the policy that governs everything else.
2. The unforgeable qualifier survives, by a different mechanism
Locally, a task's github. prefix comes from discovery (the binary's name) — the plugin can never name its own prefix, so it cannot impersonate another (TaskManifest.name forbids dots; that is the anti-spoofing seam). Remotely there is no binary name. The property is preserved because the operator's registration names the qualifier: flow worker --plugin github=https://plugins.corp/github. Same invariant — the segment that distinguishes plugins is never the plugin's to write — new mechanism, worth a test in the same spirit as the local one.
3. AuthN, a ladder not a single answer
Worker → remote plugin, in preference order, all brokered through the existing auth/ machinery (federation, exchange, broker) rather than a parallel system:
- Workload identity / OIDC — the worker presents its own identity token, audience-bound to the plugin endpoint (RFC 8707 resource indicators). Short-lived, no stored secret, per-run attributable. The MCP world converged on OAuth 2.1 + PKCE + resource indicators for exactly this surface; match it rather than inventing.
- mTLS — for same-org service meshes where that is the house standard.
- OAuth2 client credentials — a stored secret reference, weakest, supported because it is what some endpoints offer.
Three-legged OAuth (authorization code + PKCE, device flow for headless) is a different thing and belongs in the design as such: it is how a human delegates their own authority to a plugin — "this plugin may act as me against GitHub". The grant ceremony happens once via flow (browser or device code), the resulting refresh token is held as a secret reference like any other, and runs then use it under the same tenancy scoping. Agents get the same shape minus the browser: a pre-provisioned grant, or workload identity where the upstream supports it. This is user-level delegation layered on top of worker↔plugin authn, not a substitute for it.
4. The interaction that must be decided fail-closed: host-resolved secrets × remote transport
The in-flight SDK fix (claude/plugin-secrets) has the host resolve ${secret(...)} and hand the plugin values over the local transport. Correct locally — same machine, policy enforcement stays in one host. Over a network it becomes credential disclosure to a third party's process.
So remote plugins require a secret-release policy: which secret references may be released to which plugin endpoints, deny by default, decided by the operator's configuration and evaluated where every other policy is. And the often-better alternative should be first-class: a remote plugin holds its own credentials (a remote github-plugin service owns its GitHub App key), and the question inverts from "may this secret flow out" to "who may invoke this capability" — authz on Execute, per tenant, with audit. Both models are legitimate; the design must support both and default to the second for anything third-party.
The local-transport guarantee in claude/plugin-secrets must be scoped in its comments to local transport so it cannot be quietly generalized later.
5. The rest of the deltas
- Unknown outcome now applies to the plugin call itself — a network failure after Execute may-have-happened; the
ErrorKindUpstreamUnknownsemantics being added for plugin errors apply to the transport too. - Bounds: response caps exist; add per-call deadlines and the count-the-round-trips rule for anything iterative.
- Pinning (#146) extends: local = binary hash; remote = endpoint identity + manifest hash at registration, re-verified on change, refuse on drift.
- Trust tiers: local-subprocess / remote-first-party / remote-third-party, with policy (secret release, netpolicy, tenancy fan-out) settable per tier rather than per plugin only.
- Locality is invisible to the Flowfile —
github.issue_comment:means the same thing wherever the plugin runs; locality is deployment configuration. That is the same rule as everywhere else in this system, and it is what makes local rehearsal honest.
6. Posture
The plugins in this repo are products, not examples — production-grade is the bar (#149's factoring and auth ladder are part of that). Remote execution is what makes them deployable the way enterprises actually run integrations: one hardened github-plugin service per org, credentials held once, workers everywhere invoking it under policy.
Sequencing: nothing here blocks #148/#149; §4's release-policy decision should be made before the plugin-secrets work merges its guarantee language, and the rest designs against the existing protocol.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading proto/flowstate/plugin/v1, plugin/transport.go, the existing auth/ and netpolicy/ machinery, and the scoped guarantee in claude/plugin-secrets. Define the remote execution design around endpoint identity, authentication, secret-release policy, unknown outcomes, deadlines, pinning, and trust tiers; done means the policy decisions and required test seams are documented without changing Flowfile locality semantics.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend-api-design, distributed-systems, networking, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100