Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 787 – Safer subprocess usage using t-strings

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

Table of Contents

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 subprocess module 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:

  1. A new sh() renderer function in the shlex module for safe shell command construction
  2. Adding t-string support to the subprocess module’s core functions,
    particularly subprocess.Popen, subprocess.run(), and other related functions that accept a command argument

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