Capability taxonomy, the exec gate, and the out-of-the-box suite (design record)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 9
- Forks
- 0
- Avg merge
- 3h 3m
- Merged PRs (30d)
- 509
Description
Design record from a platform-capability strategy pass (tree at 885bd62). The charge: Lego-like blocks expressing real enterprise/CI-CD/AI-agent workloads without users writing plugin code where possible, while dangerous capabilities — the motivating example being a raw exec — stay explicitly opt-in with defense in depth through the policy system that already exists.
The axis: tier = the resource a hostile author gains
The tenancy model already establishes the workflow author as the adversary (a Flowfile cannot name its own tenant; task-shape policy exists because "a control in a Flowfile is a control the author can delete"). So danger is not "network vs disk vs exec" — that groups by the wrong axis, the same mistake the interaction-shape table warns about. It is blast radius:
| Tier | Resource a hostile author gains | Gating surface | Example |
|---|---|---|---|
| 0 | nothing external | input bounds | log |
| 1 | the worker's network position (SSRF, metadata) | egress policy | http |
| 2 | confidentiality of worker-reachable data | secret-access + task-shape | sql.query, github.*_get, git.clone |
| 3 | integrity of one named downstream | task-shape + which credential the deployment minted | sql.exec, git.commit_push |
| 4 | the worker itself — every credential, network path, the filesystem | launch gate (#146) + task-shape + deployment sandbox ceiling + scrubber | exec.run, codex.exec |
| 5 | the identity system — manufacturing authority | assumption policy; deliberately not a task | federation broker |
The tier informs; the name enforces. A self-declared danger_class in a manifest is a value a malicious plugin sets to escape — that inverts fail-closed. Enforcement keys off the operator-controlled qualified name (task-shape policy) and the launch gate. For humans and tooling, derive danger from the name-suffix convention the existing plugins already invented on their own (query/exec, get/commit_push) and document it — don't add an enum the security model must then remember never to trust.
exec: in-tree, official, unbundled — and the invariant behind it
The trusted base must contain no code whose only safe state is "absent."
A built-in exec behind --allow-exec compiles arbitrary host execution into every engine binary — every worker, every flow run local, every embedded use — one flag from live. A plugin makes absence physical: no binary in a vetted --plugin-dir, no task exists. Nothing to disable, nothing to misconfigure. In-tree because the isolation and credential logic is precisely the code we cannot afford to get wrong and must meet this repo's review bar; unbundled because absence must be physical. A third-party exec would put the most dangerous capability's sandbox logic outside the boundary that vets it — exactly backwards.
Five independent fail-closed layers: binary absent → task unknown; plugin config absent → every call refused; task-shape policy → identity+name gate at dispatch (before Fn, before secrets resolve); deployment sandbox ceiling; secret_inputs + scrubber.
The sandbox is its own deployment-side spec, not egress policy — egress governs HTTP the worker makes; a subprocess's network is syscalls the child makes, which egress never sees. And it follows the codex three-layer model verbatim (plugins/codex/doc.go:160-190): plugin fail-closed baseline → operator ceiling → task inputs refused outright when wider than the ceiling, never silently downgraded. Process isolation is not the sandbox (a plugin runs with the worker's authority), so the strongest posture delegates to a sandbox-provider (container/OCI, gVisor, Modal) rather than the worker's host — same tier, strictly safer, and it should be the recommended default.
The suite: where "no plugin code" holds, and exactly where it breaks
Tested against three real shapes: REST orchestration (http + call + for_each + CEL) holds fully; data-enrichment fan-out holds; approval-driven deploy holds for the gate — and the deploy action is the test.
The foundational blocks express any workload whose external effects are HTTP-shaped. They cannot express "run a program," "touch the filesystem," or protocol-native wire access (SQL/git/gRPC). That is not a gap to close — it is the correct seam between the free tier and the plugin tier.
Triage: KEEP exec (T4, the one dangerous keeper), git/github/sql/codex (exist; protocol-native or credential-handling that http refuses). DEFER, high value: cloud object-store (SigV4 belongs in an AWS-aware task, per ARCHITECTURE.md's own refusal) and container/OCI run — which is the sandbox-provider, and should ship alongside exec as the preferred substrate for all Tier-4 work. REJECT: a general fs task (unrooted fs is Tier-4-adjacent — path traversal reads any worker-readable secret; build a rooted fs on the exec sandbox spec instead), a "broader REST helper" (DSL sugar, not a process boundary), a jq/template task (pure data-shaping is what CEL is for; a Tier-0 process boundary buys no isolation).
Triage guardrail: a task earns the official base only if it speaks a protocol the foundational blocks cannot, or needs credential handling http refuses.
AI agents: pattern, plus one real gap
No missing workflow primitive — agent step = a Tier-4 task, iteration = loop with carried state (#220), human-in-the-loop = wait_for_signal, sub-agent = call (a callee is a sub-agent: isolated scope, declared inputs/outputs). The gap is inward: today an agent task runs with its own opaque tool-calling loop, so the engine governs it as a black box. The VISION bet — a policy-governed durable substrate makes agents safe — only pays off when the agent's individual tool calls flow back through the engine's policy surfaces as attributed, gated task dispatches. That is the MCP-inward bridge (interface + identity delegation, the RFC 8693 act-chain VISION already contemplates), not a new node kind.
Proposed constitution entries
- Built-in / official-plugin / ecosystem: built-in is Tier 0 or Tier 1 already bounded by a default-enabled bound (today:
log,http— nothing Tier 2+ is ever built-in); official-plugin is in-tree + CI-tested + separate binary; everything else ecosystem. - Tier → gating surface, reusing the three existing CEL operator-predicate surfaces — no fourth policy spelling (#234-compliant).
- Never reachable by default: a capability above Tier 1 is never reachable in a default configuration; absence must be physical or fail-closed, never a disabled flag.
- The name enforces; the tier only informs.
- Deployment sandbox ceilings narrow, never widen — refuse, don't downgrade.
Follow-ups surfaced
- The #236 egress seam is the one real inconsistency: secret-access and task-shape policies both carry an identity dimension; egress does not — so Tier-1 I/O cannot be tenant-scoped in the rule. Adding a workload attribute to the egress CEL environment makes the tier→surface mapping uniform.
- Caveat for implementers: the task-shape policy this design leans on is real but unmerged (#228), and the
inputs.*granularity needed to gate on a danger input likesandbox_mode(rather than just the task name) is its slice 2. The exec design is sound today at name granularity.
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 plugins/codex/doc.go:160-190, ARCHITECTURE.md, and the referenced issues #146, #220, #228, #234, and #236. Trace how task-shape, egress, secret-access, and sandbox policies currently work; this record is complete when the proposed constitution entries, exec/plugin boundary, and follow-up decisions have been accepted or turned into scoped implementation issues.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100