symfony/ux-toolkit: Path Traversal Allows Arbitrary File Write and Read via Crafted Recipe Manifest
Description
Description
The ux:install console command installs files from a recipe kit by copying paths listed in a copy-files map. The only guard against malicious paths was Path::isRelative(), which returns true for paths like ../../../etc. Path::join() then resolves the .. segments without complaint, so the final path can escape the intended directory entirely. A crafted or compromised kit can therefore write attacker-controlled content to arbitrary locations on the developer's machine or CI runner.
Because the copy operation creates missing parent directories and can overwrite existing files silently (with --force or in non-interactive environments), an attacker who controls a kit can overwrite files such as controllers, git hooks, or .env to achieve code execution. The source side of copy-files is symmetrically affected, enabling local file reads outside the recipe directory.
Resolution
The fix introduces an Assert::pathDoesNotEscapeDirectory() helper that rejects any copy-files source or destination path containing a .. segment, regardless of whether / or \ is used as the separator. This check is enforced in both RecipeManifest (which also guards the source Finder) and File. As a last line of defense, the installer re-verifies the fully resolved paths with Path::isBasePath() immediately before each filesystem read and write.
Credits
Symfony would like to thank Pascal Cescon for reporting the issue and Hugo Alliaume for providing the fix.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Affected products
1Patches
Vulnerability mechanics
Root cause
"Missing validation of `..` segments in `copy-files` paths allowed path traversal outside the intended recipe and target directories."
Attack vector
An attacker who controls a recipe kit (e.g., via a compromised dependency or social engineering) crafts a `copy-files` entry with a destination path containing `..` segments, such as `../../../../tmp/PWNED`. The `ux:install` command's only guard, `Path::isRelative()`, passes this path because it is technically relative. `Path::join()` then resolves the `..` segments, allowing the copy operation to write attacker-controlled content to an arbitrary location on the developer's machine or CI runner. Because the copy creates missing parent directories and can overwrite existing files silently (with `--force` or in non-interactive environments), the attacker can overwrite files like controllers, git hooks, or `.env` to achieve code execution. The source side of `copy-files` is symmetrically affected, enabling local file reads outside the recipe directory. [ref_id=1]
Affected code
The vulnerability resides in the `ux:install` console command, specifically in the `RecipeManifest` and `File` classes of the Symfony UX Toolkit. The `copy-files` map in a recipe manifest was only guarded by `Path::isRelative()`, which returns `true` for paths like `../../../etc`, allowing `Path::join()` to resolve `..` segments and escape the intended directory. The `Installer::handlePool()` method then performed the filesystem operations without verifying the resolved paths stayed within the base directory. [patch_id=6640819]
What the fix does
The fix introduces `Assert::pathDoesNotEscapeDirectory()`, a helper that splits the path on `/` or `\)` separators and rejects any path containing a literal `..` segment. This check is enforced in `RecipeManifest` (which validates the `copy-files` map when the recipe is loaded) and in `File` (which validates each source/destination pair at construction time). As a last line of defense, the `Installer::handlePool()` method re-verifies the fully resolved absolute paths with `Path::isBasePath()` immediately before each filesystem read and write, ensuring the copy can never land outside its base directory. [patch_id=6640819]
Preconditions
- configAttacker must control a recipe kit (e.g., via a compromised dependency or social engineering) that is installed via the `ux:install` command.
- configThe `ux:install` command must be executed in an environment where `--force` is used or the command runs non-interactively, allowing silent file overwrites.
- networkNo network precondition; the attack is triggered locally when the kit is installed.
Generated on Jun 19, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.