Traversing include paths allow cross-project YAML overwrite
Included YAML paths were checked for existence but not canonical project-root containment
CVSS 3.1 base score
HighVector: CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:H
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
- Source · attacker-controlledsrc/
meltano/ core/ project_files.py:67 ProjectFiles.include_paths
- 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 - 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) - 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) - 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
Add a traversing include such as
../sibling/meltano.ymlto the lower-trust project. - 2
Have the operator run a routine mutation such as
meltano environment add. - 3
The resolver accepts the sibling path because it exists.
- 4
Update processing routes the external path to
_write_file(). - 5
The sink replaces or blanks the sibling project's YAML configuration.
Proof of concept
- 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.
- 02
Target configuration
In the attacker project, set
include_pathsto../victim/meltano.ymlwithout granting the attacker direct victim-directory access in the collaboration model. - 03
Exploit delivery
Run a normal write-producing Meltano command against the attacker project, such as adding an environment or plugin.
- 04
Expected response
On the vulnerable release, the victim file's bytes change or the file is replaced with the blank-subfile serialization.
- 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.
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)
Check the upstream record for the project's current remediation status.
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.

