Back to Blog
high SEVERITY5 min read

How Trust-Prefix Bypass via Path Traversal Happens in Python Copier and How to Fix It

CVE-2026-53951 is a high-severity path traversal vulnerability in Copier 9.15.0 that allowed attackers to bypass trust-prefix checks and execute tasks without user confirmation. Upgrading to Copier 9.15.2 eliminates this attack vector by properly validating file paths before task execution.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 7, 2026•Reviewed September 7, 2026

Answer Summary

CVE-2026-53951 is a path traversal vulnerability (CWE-22) in Python's Copier project templating tool, affecting versions 9.15.0 and earlier. The vulnerability allowed attackers to bypass Copier's trust-prefix security mechanism using directory traversal sequences (e.g., `../`), causing tasks to execute unprompted during template generation. The fix upgrades Copier from 9.15.0 to 9.15.2 in `pyproject.toml` and `uv.lock`, which implements proper path canonicalization and trust validation before executing lifecycle tasks.

Vulnerability at a Glance

cweCWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
fixUpgrade Copier to 9.15.2 with hardened path canonicalization
riskArbitrary code execution via unprompted task execution
languagePython
root causeInsufficient path validation allowed `../` sequences to bypass trust-prefix checks
vulnerabilityPath Traversal / Trust-Prefix Bypass

Introduction

In a routine dependency audit, Trivy flagged a high-severity vulnerability in uv.lock that could silently compromise development workflows. The issue—CVE-2026-53951—resides in Copier 9.15.0, a popular Python project templating tool. A flaw in how Copier validates trusted template paths allowed attackers to bypass security prompts and execute arbitrary tasks through path traversal sequences.

The vulnerability is particularly insidious because Copier's trust-prefix mechanism is designed to protect developers from malicious templates. When this protection fails silently, automated CI/CD pipelines and developer workstations become execution vectors for supply chain attacks.

The Vulnerability Explained

Copier uses a trust system where templates from certain paths or URLs require explicit user confirmation before executing lifecycle tasks (defined in copier.yml or copier.yaml). These tasks can run arbitrary shell commands—making them powerful but dangerous.

The vulnerability exists because Copier 9.15.0 failed to properly canonicalize file paths before checking them against the trust-prefix allowlist. Consider this attack scenario:

  1. An attacker crafts a malicious template repository
  2. They embed path traversal sequences in the template path: trusted-prefix/../../../malicious-template
  3. Copier's trust check sees the trusted-prefix component and marks the path as trusted
  4. The actual resolved path points outside the trusted directory
  5. Tasks execute without user prompting, running attacker-controlled commands

The vulnerable dependency was locked in uv.lock at version 9.15.0:

-    "copier>=9.13.0",
+    "copier>=9.15.2",

And the specific vulnerable artifact:

 [[package]]
 name = "copier"
-version = "9.15.0"
+version = "9.17.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/9e/ec/e67bcf9b867a47dd8cf98e8c8e27ee8cf638633f72c535372ce5e30ade80/copier-9.15.0.tar.gz", hash = "sha256:57b951d9f63b6b9d6ce3907fb1bd4672f60e2819a42393b971122d480059383f", size = 635532, upload-time = "2026-04-30T12:52:56.582Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/2e/4c/2e593d85827c49f478ea4925d1008a144bb8ff2189c6e9f405c37bb56edb/copier-9.17.2.tar.gz", hash = "sha256:02e9c0d05281603c06d52f48350e48ffca0b4283d9f025664fbce4befabaa555", size = 650501, upload-time = "2026-08-19T13:49:17.456Z" }

The size increase from 635,532 to 650,501 bytes in the source distribution reflects the security hardening added in the patch releases.

Real-World Impact

For development teams using Copier in CI/CD pipelines or automated onboarding workflows, this vulnerability creates a supply chain attack vector:

  • CI/CD compromise: Automated template updates could trigger malicious task execution
  • Developer workstation takeover: Local template generation with seemingly trusted sources executes hidden payloads
  • Lateral movement: Compromised templates could exfiltrate environment variables or SSH keys

The Fix

The remediation involved two coordinated changes to enforce the security boundary:

1. pyproject.toml — Version Constraint Update

 dependencies = [
-    "copier>=9.13.0",
+    "copier>=9.15.2",
 ]

This change sets the minimum secure version to 9.15.2, ensuring uv or pip will never resolve to the vulnerable 9.15.0 release.

2. uv.lock — Locked Artifact Update

 [[package]]
 name = "copier"
-version = "9.15.0"
+version = "9.17.2"

The lockfile update pins the exact verified artifact with new cryptographic hashes:

Attribute Old (Vulnerable) New (Fixed)
Version 9.15.0 9.17.2
sdist SHA256 57b951d9f63b6b9d6ce3907fb1bd4672f60e2819a42393b971122d480059383f 02e9c0d05281603c06d52f48350e48ffca0b4283d9f025664fbce4befabaa555
wheel SHA256 0f59c2ea36df42f3ded85c091c3f1e2c8d3814b537504f0abc8c2e508f7e013d 24757d8875cdf4e076ff338e5ea984e85f5d5da9afcecea8a9dde0f151310b19

Security Improvements in 9.15.2+

Copier 9.15.2 and subsequent releases implement:

  • Path canonicalization: All template paths are resolved to absolute, normalized forms before trust checks
  • Symlink traversal protection: Resolved paths are validated to prevent symlink-based escapes
  • Strict trust-prefix matching: The allowlist comparison uses pathlib.Path equality after resolution, not string prefix matching

The wheel size increase from 62,747 to 67,003 bytes reflects additional validation logic for secure path handling.

Key Takeaways

  • Never use string prefix matching for path trust decisions — the copier>=9.13.0 constraint in pyproject.toml allowed 9.15.0, which used insufficient path validation
  • Lockfiles require active maintenance — uv.lock pinned the vulnerable 9.15.0 with hash 57b951d9f63b6b9d6ce3907fb1bd4672f60e2819a42393b971122d480059383f; automated updates are essential
  • Task execution frameworks need strict path sandboxing — Copier's lifecycle tasks run shell commands, making path traversal a code execution vector
  • Supply chain security extends to development tools — Template generators, linters, and build tools are high-value attack targets

How Orbis AppSec Detected This

Component Details
Source pyproject.toml dependency declaration with unconstrained copier>=9.13.0
Sink Locked artifact resolution in uv.lock line 25: version = "9.15.0"
Missing control No minimum secure version constraint; no automated lockfile vulnerability scanning
CWE CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Fix Updated constraint to copier>=9.15.2 and regenerated uv.lock with verified hashes

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2026-53951 demonstrates how path traversal vulnerabilities can undermine security architectures even when explicit trust mechanisms exist. The Copier trust-prefix bypass shows that path validation must happen after canonicalization, not before. For Python developers, this case reinforces the importance of:

  • Proactive dependency management with minimum secure versions
  • Automated vulnerability scanning of lockfiles
  • Understanding that >= constraints without upper bounds can silently admit vulnerable releases

The 9.15.2 upgrade path is straightforward and preserves all valid functionality while eliminating this attack vector. Teams using Copier should audit their lockfiles and CI/CD configurations for this vulnerability pattern.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #402

Related Articles

critical

path.resolve() Path Traversal in Node CLI's Dynamic import()

A Node.js CLI script for validating expression definitions took a file path from `process.argv[2]`, resolved it with `path.resolve()`, and passed the result straight into a dynamic `import()` — with no check that the resolved path stayed inside the working directory. An attacker (or a malicious skill/plugin invocation) could supply traversal sequences to load and execute arbitrary `.js` files from anywhere on disk. The fix adds a boundary check with `path.relative()` and an extension allowlist b

high

bookDir() Path Traversal via Unsanitized bookId Parameter

The `bookDir()` function accepted unsanitized `bookId` values derived from user-created book titles, enabling path traversal attacks through `../` sequences. A fix was applied that validates the identifier using `path.basename()` and throws on mismatch, ensuring all resolved paths remain within `LIBRARY_DIR`.

high

Express `app.get('*')` Wildcard Handler Path Traversal in watch.js

A first-party Express server's wildcard route handler used `req.url.indexOf('font.woff2')` to gate access to a font file, allowing attackers to bypass the substring check with crafted paths. The fix replaces the catch-all handler with explicit route registration.

high

updateCardBg() Follows Unvalidated 302 Location Headers

A background-image updater fetched a configured image URL with manual redirect handling and then re-issued the request to whatever `Location` header came back, with no scheme or host checks. A redirect to `http://169.254.169.254/` or `http://127.0.0.1:<port>/` would have been followed with the original fetch options attached, and the response body written to disk as an image asset. The fix resolves the redirect target against `imgDownloadUrl` and rejects anything that is not HTTPS on the same ho

high

markitdown_bridge.py Path Traversal: Arbitrary File Read via sys.argv

The markitdown_bridge.py script, used by MDView for DOCX-to-Markdown conversion, accepted file paths directly from command-line arguments without validating they stayed within intended directories. An attacker could exploit this to read arbitrary files from the filesystem by passing path traversal sequences in the source_path parameter.

high

SHACL Viewer Path Traversal in graph3d(): Unvalidated `path`

The `graph3d()` and `graph2d()` request handlers in SHACL Viewer directly concatenated user-supplied `path` parameters into filesystem paths, enabling directory traversal outside the intended `/shapes/` directory. The fix introduces `_resolve_shapes_path()` with `os.path.realpath()` validation to enforce containment within the shapes directory.