GHSA-85V3-4M8G-HRH6
Vulnerability from github – Published: 2026-04-01 22:28 – Updated: 2026-04-06 17:32Summary
Copier's _subdirectory setting is documented as the subdirectory to use as the template root. However, the current implementation accepts parent-directory traversal such as .. and uses it directly when selecting the template root.
As a result, a template can escape its own directory and make Copier render files from the parent directory without --UNSAFE.
Details
The relevant code path is:
- the template defines
_subdirectory - Copier renders that string
template_copy_rootreturnsself.template.local_abspath / subdir- Copier walks that directory as the template root
Relevant code:
- https://github.com/copier-org/copier/blob/7aa7021bd73797c982492bac3535515d4484fdb7/copier/_main.py#L1056-L1062
- https://github.com/copier-org/copier/blob/7aa7021bd73797c982492bac3535515d4484fdb7/copier/_template.py#L504-L513
The effective sink is:
subdir = self._render_string(self.template.subdirectory) or ""
return self.template.local_abspath / subdir
There is no check that the resulting path stays inside the template directory.
The documentation for _subdirectory describes it as:
Subdirectory to use as the template root when generating a project.
and explains it as a way to separate template metadata from template source code:
That description fits values like template or poetry, but not ...
PoC
PoC 1: _subdirectory: .. escapes to the parent directory
mkdir -p root/template dst
echo 'loot' > root/loot.txt
printf '%s\n' '_subdirectory: ..' > root/template/copier.yml
copier copy --overwrite root/template dst
find dst -maxdepth 3 -type f | sort
cat dst/loot.txt
Expected output includes:
dst/loot.txt
dst/template/copier.yml
loot
This shows Copier is rendering from root/ rather than from root/template/.
Impact
If a user runs Copier on an untrusted template, that template can change the effective template root and make Copier render files from outside the intended template directory.
Practical impact:
- template-root escape via
.. - rendering of parent-directory files that were not meant to be part of the template
- possible without
--UNSAFE
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "copier"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "9.14.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-34726"
],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-01T22:28:49Z",
"nvd_published_at": "2026-04-02T19:21:32Z",
"severity": "MODERATE"
},
"details": "### Summary\n\nCopier\u0027s `_subdirectory` setting is documented as the subdirectory to use as the template root. However, the current implementation accepts parent-directory traversal such as `..` and uses it directly when selecting the template root.\n\nAs a result, a template can escape its own directory and make Copier render files from the parent directory without `--UNSAFE`.\n\n### Details\n\nThe relevant code path is:\n\n1. the template defines `_subdirectory`\n2. Copier renders that string\n3. `template_copy_root` returns `self.template.local_abspath / subdir`\n4. Copier walks that directory as the template root\n\nRelevant code:\n\n- \u003chttps://github.com/copier-org/copier/blob/7aa7021bd73797c982492bac3535515d4484fdb7/copier/_main.py#L1056-L1062\u003e\n- \u003chttps://github.com/copier-org/copier/blob/7aa7021bd73797c982492bac3535515d4484fdb7/copier/_template.py#L504-L513\u003e\n\nThe effective sink is:\n\n```python\nsubdir = self._render_string(self.template.subdirectory) or \"\"\nreturn self.template.local_abspath / subdir\n```\n\nThere is no check that the resulting path stays inside the template directory.\n\nThe documentation for `_subdirectory` describes it as:\n\n\u003e Subdirectory to use as the template root when generating a project.\n\nand explains it as a way to separate template metadata from template source code:\n\n\u003chttps://github.com/copier-org/copier/blob/7aa7021bd73797c982492bac3535515d4484fdb7/docs/configuring.md#L1582-L1646\u003e\n\nThat description fits values like `template` or `poetry`, but not `..`.\n\n### PoC\n\n#### PoC 1: `_subdirectory: ..` escapes to the parent directory\n\n```sh\nmkdir -p root/template dst\necho \u0027loot\u0027 \u003e root/loot.txt\nprintf \u0027%s\\n\u0027 \u0027_subdirectory: ..\u0027 \u003e root/template/copier.yml\n\ncopier copy --overwrite root/template dst\nfind dst -maxdepth 3 -type f | sort\ncat dst/loot.txt\n```\n\nExpected output includes:\n\n```text\ndst/loot.txt\ndst/template/copier.yml\nloot\n```\n\nThis shows Copier is rendering from `root/` rather than from `root/template/`.\n\n### Impact\n\nIf a user runs Copier on an untrusted template, that template can change the effective template root and make Copier render files from outside the intended template directory.\n\nPractical impact:\n\n- template-root escape via `..`\n- rendering of parent-directory files that were not meant to be part of the template\n- possible without `--UNSAFE`",
"id": "GHSA-85v3-4m8g-hrh6",
"modified": "2026-04-06T17:32:29Z",
"published": "2026-04-01T22:28:49Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/copier-org/copier/security/advisories/GHSA-85v3-4m8g-hrh6"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34726"
},
{
"type": "WEB",
"url": "https://github.com/copier-org/copier/commit/cb80a3ffc9c3787de3ed837e04ca29a0ff8ca3df"
},
{
"type": "PACKAGE",
"url": "https://github.com/copier-org/copier"
},
{
"type": "WEB",
"url": "https://github.com/copier-org/copier/releases/tag/v9.14.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Copier `_subdirectory` allows template root escape via parent-directory traversal"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.