← Community
bugfixed

Direct `/api/workflow/dag/run` ignores declared workflow capability scope and executes an out-of-scope irreversible provider action on stati

DaveDave#318d ago · 141 views
fixed in: station-v0.89

Reproduction steps:

  1. Use a clean RailCall Station v0.78 workspace.
  2. Store this engine spec as an installed workflow:
{
  "id": "cap-bypass",
  "capabilities": {
    "providers": ["safe_provider"],
    "allow_irreversible": false
  },
  "nodes": [
    {
      "id": "pay",
      "type": "effect",
      "action_id": "fakepay_charge",
      "args": {"amount_cents": 1}
    }
  ]
}
  1. Resolve fakepay_charge to a harmless local stub with action_class="external_send"; its apply() only writes an in-memory marker.
  2. Call the real routes.dispatch_workflow._handle_dag_run() with dry_run=false, live execution capability enabled, and Team gate proceed.
  3. Control case: submit the same workflow to workflow_mcp.stage_workflow().

Expected:

  • providers is an allowlist. A provider not in the list must be refused.
  • allow_irreversible:false must refuse an external_send action.
  • Direct DAG execution must enforce the same declared scope as the workflow staging path.

Actual:

direct route outcome = "COMPLETED"
effect marker        = [1]
plan systems_touched= ["fakepay"]
plan irreversible    = ["pay:fakepay_charge"]

Control result:

workflow_mcp.stage_workflow() -> ok=false
error -> "capability exceeded: workflow touches ['fakepay'] but its declared providers are ['safe_provider']"

The MCP control refused before creating a run token. The direct DAG route executed the same out-of-scope effect.

Root cause:

  • workbench/workflow_mcp.py:137-175 implements the declared capability checks for providers, egress_hosts, max_spend_cents, and allow_irreversible.
  • workbench/workflow_mcp.py:187-205 invokes those checks during staging.
  • workbench/routes/dispatch_workflow.py:681-725 never calls _validate_wf() or _check_capabilities() before plan pin/run.
  • workbench/workflow_engine.py:681-686 enforces only the runtime spend field; it does not enforce provider, egress, or irreversible scope.

Impact:

A workflow can execute a provider and irreversible action outside the capability scope it declares. The harness used no network or financial provider; it proved the out-of-scope execution boundary was reached.

Suggested fix:

Apply the same capability validation to direct DAG runs before plan pinning and execution, or move capability enforcement into the shared engine so every execution surface receives identical scope checks.

3 pts

2 replies

Confirmed and credited, @davelab.tech. Direct /api/workflow/dag/run honoring the live-execution gate but ignoring the workflow's own declared capability scope is a real gap — the per-node policy work in v0.85/v0.86 narrowed the blast radius (per-node decisions are now sealed into the signed plan and enforced), but the declared-capability check on the direct run path is still the missing piece and stays open until it genuinely lands. Credited now.

Fixed in station-v0.89, both ways you suggested, @davelab.tech: (1) the direct dag/run route now runs the same _check_capabilities the staging path runs, on the static pre-plan, refusing with identical error text; (2) the engine itself backstops providers + allow_irreversible per effect node (new CapabilityExceeded, saga rolls back) — so every surface, including a direct run_workflow call, inherits the declared scope, mock or live. Your marker-stub repro is a regression test: the marker stays empty. (Points awarded at confirmation.)

Sign in to reply.