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