← Community
bugfixed

the MCP live-execution path silently disables the new team-approval gate if building it raises, instead of failing closed like the sibling

ShwetaShweta#136d ago · 60 views
affected: station-v0.71fixed in: station-v0.74

the MCP live-execution path silently disables the new team-approval gate if
building it raises, instead of failing closed like the sibling HTTP path does

Reproduction steps:

  1. Extract a clean station-v0.71 tarball.
  2. Read workbench/workflow_mcp.py lines ~330-334 (the actual shipped code,

quoted verbatim):
try:
from routes.team import build_workflow_team_gate as _bwtg
_team_gate = _bwtg(ws)
except Exception:
_team_gate = None
Any exception while importing or constructing the gate -- not just
ImportError -- is swallowed and silently replaced with None.

  1. Compare to workbench/routes/dispatch_workflow.py lines ~711-713, which

calls the SAME constructor with no try/except of its own:
from routes.team import build_workflow_team_gate as _bwtg
res = E.run_workflow(engine_spec, ws=WS, signing=signing,
team_gate=_bwtg(WS))
An identical failure here propagates up to the route's own
except Exception as e: at line ~749, which fails the run visibly (an
error response, receipt, no live effect) rather than proceeding ungated.

  1. Script (repro_failopen_v071.py) simulates the constructor raising and

runs both patterns against the real workflow_engine.run_workflow():
workflow_mcp.py pattern -> team_gate passed to run_workflow(): None
run outcome with team_gate silently dropped to None: ROLLED_BACK
| receipt has team_approval key: False
dispatch_workflow.py pattern -> constructor failure PROPAGATES:
RuntimeError(...) (caught by the route's own except-and-fail-closed
handler, run never proceeds ungated)
"ROLLED_BACK" (rather than an early BLOCKED_BY_TEAM_APPROVAL /
AWAITING_TEAM_APPROVAL return) confirms the run walked straight past
where the gate check should have been and into real node execution.

Expected: primitives/team_approval.py's own gate() docstring states the
design intent explicitly -- "FAIL CLOSED throughout: a configured
requirement that cannot be satisfied is a block, not a bypass." A failure
while constructing the gate is exactly the kind of "cannot be satisfied"
case that principle is meant to cover, and the sibling call site
(dispatch_workflow.py) already honors it by construction (no swallow).

Actual: workflow_mcp.py -- the ONE call site where team_gate can drive a
genuinely live external effect (live_http=(want_live and allow_live),
~line 331-334) -- is the one call site that fails open on that same class
of error.

Root cause: workbench/workflow_mcp.py ~line 330-334, bare
except Exception: _team_gate = None.

Caveat on confidence: build_workflow_team_gate() itself already has an
inner ImportError fallback (`from primitives import ... except ImportError:
from workbench.primitives import ...`), so under a healthy, correctly
laid-out install this outer except is unlikely to actually fire today --
this is a defense-in-depth / fail-safe-direction defect, not a
demonstrated walk-up exploit against a normal install. Flagging it because
(a) it directly contradicts the module's own stated fail-closed contract,
(b) the asymmetry with dispatch_workflow.py's handling of the identical
call is a real inconsistency worth fixing regardless of how it's currently
triggered, and (c) it sits on the one path that can fire a live effect.

Suggested fix: don't swallow the constructor failure. Either drop the
try/except (let it propagate and fail the run, matching
dispatch_workflow.py), or explicitly fail closed on exception, e.g.:

try:
from routes.team import build_workflow_team_gate as _bwtg
_team_gate = _bwtg(ws)
except Exception as e:
return {"ok": False, "error": f"team-approval gate unavailable: {str(e)[:160]}"}

5 pts

1 reply

Verified: the MCP live-exec path wrapped gate construction in try/except: _team_gate = None, so any build error silently dropped the quorum requirement (fail-open). New team_gate_or_fail_closed() returns a blocking gate when the station is in a team and construction fails; only a non-team station gets a no-op. Confirmed against the code and fixed on the v0.74 batch (verified + regression-tested); ships in station-v0.74. Thanks shweta.

Sign in to reply.