diff --git a/django/core/management/templates.py b/django/core/management/templates.py index e06e6a6307..71ee0a6ce8 100644 --- a/django/core/management/templates.py +++ b/django/core/management/templates.py @@ -11,7 +11,11 @@ from urllib.request import build_opener import django from django.conf import settings from django.core.management.base import BaseCommand, CommandError -from django.core.management.utils import handle_extensions, run_formatters +from django.core.management.utils import ( + find_formatters, + handle_extensions, + run_formatters, +) from django.template import Context, Engine from django.utils import archive from django.utils.http import parse_header_parameters @@ -107,6 +111,10 @@ class TemplateCommand(BaseCommand): "exist, please create it first." % top_dir ) + # Find formatters, which are external executables, before input + # from the templates can sneak into the path. + formatter_paths = find_formatters() + extensions = tuple(handle_extensions(options["extensions"])) extra_files = [] excluded_directories = [".git", "__pycache__"] @@ -224,7 +232,7 @@ class TemplateCommand(BaseCommand): else: shutil.rmtree(path_to_remove) - run_formatters(self.written_files) + run_formatters(self.written_files, **formatter_paths) def handle_template(self, template, subdir): """ diff --git a/django/core/management/utils.py b/django/core/management/utils.py index e3e1122409..a308763d9d 100644 --- a/django/core/management/utils.py +++ b/django/core/management/utils.py @@ -157,11 +157,18 @@ def is_ignored_path(path, ignore_patterns): return any(ignore(pattern) for pattern in normalize_path_patterns(ignore_patterns)) -def run_formatters(written_files): +def find_formatters(): + return {"black_path": shutil.which("black")} + + +def run_formatters(written_files, black_path=(sentinel := object())): """ Run the black formatter on the specified files. """ - if black_path := shutil.which("black"): + # Use a sentinel rather than None, as which() returns None when not found. + if black_path is sentinel: + black_path = shutil.which("black") + if black_path: subprocess.run( [black_path, "--fast", "--", *written_files], capture_output=True, diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index e27ff96eda..af27e0afb5 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1362,6 +1362,19 @@ files is: byte-compile invalid ``*.py`` files, template files ending with ``.py-tpl`` will be renamed to ``.py``. +.. _trusted_code_warning: + +.. warning:: + + The contents of custom app (or project) templates should always be + audited before use: Such templates define code that will become + part of your project, and this means that such code will be trusted + as much as any app you install, or code you write yourself. + Further, even rendering the templates is, effectively, executing + code that was provided as input to the management command. The + Django template language may provide wide access into the system, + so make sure any custom template you use is worthy of your trust. + ``startproject`` ---------------- @@ -1418,8 +1431,9 @@ The :class:`template context <django.template.Context>` used is: - ``docs_version`` -- the version of the documentation: ``'dev'`` or ``'1.x'`` - ``django_version`` -- the version of Django, e.g. ``'2.0.3'`` -Please also see the :ref:`rendering warning <render_warning>` as mentioned -for :djadmin:`startapp`. +Please also see the :ref:`rendering warning <render_warning>` and +:ref:`trusted code warning <trusted_code_warning>` as mentioned for +:djadmin:`startapp`. ``test`` --------