aniket/awwfensive

Breaking Flyto's MCP Recipe Runner: A Path Traversal Vulnerability

Breaking Flyto's MCP Recipe Runner: A Path Traversal Vulnerability

What is flyto-core?

Flyto Core is the core execution engine of the Flyto platform that runs automation workflows by orchestrating modules, managing execution state, handling variables and secrets, and recording evidence throughout the workflow lifecycle.

repo

What are Flyto recipes?

Flyto supports reusable, pre-built YAML workflows called recipes. They are intended to live in the bundled src/recipes/ directory, be listed through list_recipes(), and be executed by name through the CLI or MCP run_recipe tool.

That boundary is not enforced.

A user-controlled recipe name is interpolated directly into a filesystem path. As a result, an MCP caller can use ../ path segments to load and execute a YAML workflow located outside the bundled recipes directory.

Recipes are multi-step YAML workflows that compose Flyto modules. A recipe may launch a browser, navigate to a page, extract data, write a file, or invoke other available modules.

The intended model is straightforward:

This makes the recipe directory an important trust boundary. The MCP tool is described as running a pre-built recipe “by name,” not as accepting an arbitrary path to a workflow file.

The vulnerable code

In src/cli/recipe.py, load_recipe() constructs a path from the caller-supplied recipe name:

repo

There is no validation of recipe_name, and the resulting path is not resolved and checked to ensure it remains under RECIPES_DIR.

Python’s pathlib does not prevent traversal simply because paths are joined with /. If recipe_name contains ../, the filesystem resolves it normally.

For example:

src/recipes/ + ../../traversal-poc.yaml

can resolve outside the intended recipe directory.

Reaching it through MCP

The issue is reachable through Flyto’s MCP handler:

repo

An MCP caller controls recipe_name, so it can provide traversal sequences directly. The normal listing path does not help here: list_recipes() only enumerates src/recipes/*.yaml, but run_recipe() accepts names that were never listed.

That creates a mismatch between the advertised interface and the effective one:

Intended behaviorActual behavior
Execute a bundled recipe by nameLoad any reachable YAML workflow path ending in .yaml
list_recipes() defines available workflowsCallers can reference workflows that are never listed
Recipes are constrained to src/recipes/../ can escape that directory

Proof of concept

A harmless YAML workflow outside src/recipes/ is sufficient to demonstrate the issue:

name: Traversal POC
description: Harmless proof that run_recipe can load outside src/recipes
steps: []

Calling:

load_recipe("../../traversal-poc")

causes Flyto to resolve and load traversal-poc.yaml outside the recipe bundle.

The same behavior is reachable through the MCP-facing execution path:

result = await run_recipe("../../traversal-poc", args={})

Instead of returning “Recipe not found,” Flyto loads and executes the external workflow.

POC Validation

From repo root, create a harmless workflow outside src/recipes:

cat > traversal-poc.yaml <<'YAML'
name: Traversal POC
description: Harmless proof that run_recipe can load outside src/recipes
steps: []
YAML

Confirm load_recipe() can escape the recipe directory:

python -c 'from cli.recipe import load_recipe; print(load_recipe("../../traversal-poc"))'

repo
This confirm’s the load_recipe() function is considering the attacker input as a name.

Let’s validate through MCP handler because that’s the attack surface where the user can supply name via prompt.

repo

Why this matters

This is not automatically remote code execution. Flyto’s module policy may block dangerous modules such as shell execution, and the eventual impact depends on the modules permitted in the deployment.

However, the vulnerability bypasses a meaningful workflow-integrity boundary. An MCP caller can run YAML workflows that maintainers did not bundle, review, expose through list_recipes(), or intend to make available.

Depending on the allowed module set and filesystem layout, an attacker-controlled or otherwise reachable YAML file could invoke permitted capabilities such as:

The key issue is not that every external YAML file is necessarily dangerous. It is that run_recipe claims to execute a curated built-in recipe while actually accepting a filesystem traversal primitive.

Fixing the boundary

repo

The proposed patch resolves both the trusted recipes directory and the caller-derived recipe path before opening the file. Resolving matters because it asks the filesystem for the final location: .. segments are collapsed and symlinks are followed before the security decision is made.

For a normal bundled recipe such as daily_report, the resolved path remains beneath RECIPES_DIR:

RECIPES_DIR:                       /app/src/recipes
recipe_name:                       daily_report
resolved recipe path:              /app/src/recipes/daily_report.yaml

For a traversal attempt, the same resolution exposes that the final path has escaped the trusted directory:

RECIPES_DIR:                       /app/src/recipes
recipe_name:                       ../../traversal-poc
resolved recipe path:              /app/traversal-poc.yaml

The proposed containment check rejects that second path because /app/src/recipes is not one of its parent directories.

In other words, this proposed fix uses a final-path check rather than a string check. A caller may supply an unusual path string, but load_recipe() would only proceed when the filesystem-resolved target remains inside the bundled recipe tree. This also covers paths that escape through a symlink.

The regression tests should cover a valid bundled recipe, ../ traversal, absolute paths, and a symlink that points outside RECIPES_DIR.

Disclosure Timeline

DateEvent
Before v2.26.8run_recipe accepted a caller-controlled recipe name without proving that the resolved YAML path remained beneath RECIPES_DIR.
14 Jul 2026This report documented the traversal condition, its MCP reachability, and the proposed resolved-path containment check.
v2.26.6 → v2.26.8The upstream comparison records the code changes between the affected baseline and the release containing the remediation.
CVE pendingThe issue is tracked as GHSA-mxcc-cr6x-2mvr. A CVE identifier has not yet been assigned.

References