Skip to content
winfunc
Disclosure record · Meltano

Traversing include paths allow cross-project YAML overwrite

Included YAML paths were checked for existence but not canonical project-root containment

MeltanoDisclosedSource record

CVSS 3.1 base score

High
7.8/ 10

Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:H

Record

Project
Meltano
Severity
High7.8
CVE
None listed
Disclosed
Trace
3 steps

Summary

Source

ProjectFiles.include_paths

src/meltano/core/project_files.py:67

Sink

ProjectFiles._write_file()

src/meltano/core/project_files.py:375

A contributor to a lower-trust Meltano project could add a traversing include_paths pattern and cause an operator's routine configuration mutation to replace or blank a writable sibling project's valid YAML configuration.

Root cause. ProjectFiles._resolve_include_paths() accepted any glob match that existed and was a file. ProjectFiles.update() later treated every accepted match as an authorized persistence destination, and _write_file() performed an atomic replacement without rechecking containment.

Remediation evidence. Meltano confirmed the report and opened exact PR #10182 two days later; it merged on 22 July 2026. The patch enforces canonical project-root containment at resolution and again at the write sink, including traversal and symlink regression tests.

Severity

Attack vectorAV
Local
Attack complexityAC
Low
Privileges requiredPR
Low
User interactionUI
Required
ScopeS
Changed
ConfidentialityC
None
IntegrityI
High
AvailabilityA
High

Metric values as published in the disclosure vector. Meters show how far each value raises exposure.

Source-to-sink trace

  1. Source · attacker-controlledsrc/meltano/core/project_files.py:67

    ProjectFiles.include_paths

  2. Step 01src/meltano/core/project_files.py:67

    The contributed project supplies a relative glob that resolves to an existing sibling YAML file.

    python
    include_paths:
      - ../privileged-project/meltano.yml
    
  3. Step 02src/meltano/core/project_files.py:_resolve_include_paths()

    The vulnerable resolver verified only file existence/type and retained the external path.

    python
    for path in self.root.glob(pattern):
        self._is_valid_include_path(path)
        include_paths.append(path)
    
  4. Step 03src/meltano/core/project_files.py:375

    A normal environment/plugin mutation serializes merged configuration and atomically replaces the out-of-root destination.

    python
    fd, tmp_name = tempfile.mkstemp(dir=file_path.parent)
    # serialize YAML to tmp_name
    os.replace(tmp_name, file_path)
    
  5. Sinksrc/meltano/core/project_files.py:375

    ProjectFiles._write_file()

Impact

Reported impact

The attacker can corrupt or destroy another project's plugin, environment, schedule, or job configuration, crossing the repository/project trust boundary and causing material integrity and availability impact.

Attack surface

Meltano projects that accept contributor-controlled meltano.yml changes and are processed by an operator or CI identity with write access to sibling paths.

Preconditions

The attacker can modify one project's configuration but cannot directly write the target project. An operator or CI job must run a normal Meltano configuration mutation, and the process account must be able to replace the target file.

Attack path

  1. 1

    Add a traversing include such as ../sibling/meltano.yml to the lower-trust project.

  2. 2

    Have the operator run a routine mutation such as meltano environment add.

  3. 3

    The resolver accepts the sibling path because it exists.

  4. 4

    Update processing routes the external path to _write_file().

  5. 5

    The sink replaces or blanks the sibling project's YAML configuration.

Proof of concept

Reproduction5 stages
  1. 01

    Environment setup

    Create two disposable sibling Meltano projects under one writable parent directory and seed the victim project with a valid configuration plus a unique marker.

  2. 02

    Target configuration

    In the attacker project, set include_paths to ../victim/meltano.yml without granting the attacker direct victim-directory access in the collaboration model.

  3. 03

    Exploit delivery

    Run a normal write-producing Meltano command against the attacker project, such as adding an environment or plugin.

  4. 04

    Expected response

    On the vulnerable release, the victim file's bytes change or the file is replaced with the blank-subfile serialization.

  5. 05

    Outcome

    PR #10182 rejects the external canonical path and preserves the victim marker; legitimate in-project includes continue to work.

Remediation

Guidance

Upgrade to a Meltano release containing PR #10182. Resolve every include and write destination canonically, require it to be beneath the project root, and recheck at the final sink to cover future callers and symlinks.

Before and after−3+3
for path in self.root.glob(pattern):    self._is_valid_include_path(path)    include_paths.append(path)candidate = path.resolve(strict=True)candidate.relative_to(self.root.resolve(strict=True))include_paths.append(candidate)
View original source record

Check the upstream record for the project's current remediation status.

Your codebase

Investigate the paths that matter in your codebase.

Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.