PEP 787 – Safer subprocess usage using t-strings
- Author:
- Nick Humrich <nick at humrich.us>, Alyssa Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Deferred
- Type:
- Standards Track
- Requires:
- 750
- Created:
- 13-Apr-2025
- Python-Version:
- 3.15
- Post-History:
- 14-Apr-2025
Abstract
PEP 750 introduced template strings (t-strings) as a generalization of f-strings,
providing a way to safely handle string interpolation in various contexts. This PEP
proposes extending the subprocess and shlex modules to natively support t-strings, enabling
safer and more ergonomic shell command execution with interpolated values, as well as
serving as a reference implementation for the t-string feature to improve API ergonomics.
PEP Deferral
During the discussions of the initial draft of the PEP, it became clear that t-strings provide
a potential opportunity to offer shell=True levels of syntactic convenience for complex
subprocess invocations without all of the security and cross-platform compatibility concerns
that arise with giving user provided text access to a full system shell.
To that end, the PEP authors now plan to work on an experimental t-string based subprocess invocation library through the Python 3.14 beta period (and beyond), before preparing a revised draft of the proposal for Python 3.15.
Motivation
Despite the safety benefits and flexibility that template strings offer in PEP 750, they lack a concrete consumer implementation in the standard library that demonstrates their practical application. One of the most compelling use cases for t-strings is safer shell command execution, as noted in the withdrawn PEP 501:
# Unsafe with f-strings:
os.system(f"echo {message_from_user}")
# Also unsafe with f-strings
subprocess.run(f"echo {message_from_user}", shell=True)
# Fails with f-strings
subprocess.run(f"echo {message_from_user}")
# Safe with t-strings and POSIX-compliant shell quoting:
subprocess.run(t"echo {message_from_user}", shell=True)
# Safe on all platforms with t-strings:
subprocess.run(t"echo {message_from_user}")
# Safe on all platforms without t-strings:
subprocess.run(["echo", str(message_from_user)])
Currently, developers must choose between convenience (using f-strings with potential
security risks) and safety (using more verbose, list-based APIs). By adding native t-string
support to the subprocess module, we provide a consumer reference implementation that
demonstrates the value of t-strings while addressing a common security concern.
Rationale
The subprocess module is an ideal candidate for t-string support for several reasons:
- Command injection vulnerabilities in shell commands are a well-known security risk.
- The
subprocessmodule already supports both string and list-based command specifications. - There’s a natural mapping between t-strings and proper shell escaping that provides both convenience and safety.
- It serves as a practical showcase for t-strings that developers can immediately understand and appreciate.
By extending subprocess to handle t-strings natively, we make it easier to write secure code without sacrificing the convenience that led many developers to use potentially unsafe f-strings.
Specification
This PEP proposes two main additions to the standard library:
- A new
sh()renderer function in theshlexmodule for safe shell command construction - Adding t-string support to the
subprocessmodule’s core functions, - particularly
subprocess.Popen,subprocess.run(), and other related functions that accept a command argument
- Adding t-string support to the
Renderer for shell escaping added to shlex
As a reference implementation, a renderer for safe POSIX shell escaping will be added to
the shlex module. This renderer would be called sh and would be equivalent to
calling shlex.quote on each field value in the template literal.
Thus:
os.system(shlex.sh(t"cat {myfile}"))
would have the same behavior as:
os.system("cat " + shlex.quote(myfile)))
The addition of shlex.sh will NOT change the existing admonishments in the