Reproduction steps:
- Use a clean RailCall Station v0.71 installation.
- Import
routes.teamandprimitives.team_approval. - Monkeypatch
primitives.team_approval.gate()to record calls and return:
("pending", {...})
- Build a workflow team gate with
routes.team.build_workflow_team_gate(ws). - Test two equivalent one-node workflows that perform an external POST.
Effect node:
{
"id": "n1",
"type": "effect",
"provider": "slack",
"verb": "post_message",
"action_id": "slack.post_message",
"args": {}
}
HTTP node:
{
"id": "n1",
"type": "http",
"request": {
"method": "POST",
"url": "https://hooks.example.com/...",
"body": {}
}
}
- Call the generated gate for each workflow and record every call to
team_approval.gate().
Expected:
Both external POST operations should be evaluated by the team approval gate. If the policy requires quorum approval for external sends, both workflows should return pending.
Actual:
The effect node is checked by the team gate:
verdict='pending'
team_approval.gate() invoked with=[('slack', 'message_post', 'reversible')]
The HTTP node is not checked:
verdict='proceed'
team_approval.gate() invoked with=[]
The HTTP node returns proceed immediately because it is never enumerated by the team approval gate.
Root cause:
workbench/routes/team.py, inside build_workflow_team_gate(), _iter_actions() recursively handles subworkflow nodes and effect nodes, but has no branch for http nodes.
The workflow engine treats http as a first-class effect node:
workflow_engine.pyincludes HTTP nodes in workflow planning and execution.workflow_http.py:plan_http()classifies mutating HTTP methods asexternal_send.workflow_http.py:apply_http_plan()can perform the actual network request when live execution is enabled.workflow_mcp.pypasseslive_http=Truewhen bothwant_liveandallow_liveare enabled.
Because _iter_actions() skips http, the team approval policy is never consulted for that node type.
Impact:
A workflow containing a mutating http node can bypass the v0.71 team quorum-approval gate. When live HTTP execution is enabled through the MCP path, the request can reach an external destination without the required team approval decision.
This is an authorization/governance bypass affecting HTTP external effects. No real external request was performed during reproduction.
Suggested fix:
Add an http branch to _iter_actions() that derives the same provider/action class used by workflow_http.plan_http(), for example:
elif k == "http":
try:
from workbench import workflow_http as _WH
except ImportError:
import workflow_http as _WH
req = n.get("request") or {}
plan = _WH.plan_http(
req,
n.get("policy"),
allow_private=n.get("allow_private", False),
)
yield (
"http:" + plan["host"],
req.get("method", "GET"),
plan["action_class"],
)
The approval policy must also define how external_send actions from HTTP nodes are covered by quorum rules.
Relevant source paths:
workbench/routes/team.py—build_workflow_team_gate()/_iter_actions()workbench/workflow_engine.py— HTTP planning and executionworkbench/workflow_http.py—plan_http()/apply_http_plan()workbench/workflow_mcp.py— live HTTP execution path