PEP 787 – t-문자열을 사용한 더 안전한 서브프로세스 사용
- 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
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
PEP 750은 f-문자열을 일반화한 템플릿 문자열(t-문자열)을 도입하여, 다양한 컨텍스트에서 문자열 보간을 안전하게 처리하는 방법을 제공합니다. 이 PEP는 subprocess 및 shlex 모듈을 확장하여 t-문자열을 기본적으로 지원하고, 보간된 값을 사용한 셸 명령 실행을 더 안전하고 편리하게 하며, API 사용 편의성을 개선하기 위한 t-문자열 기능의 참조 구현 역할도 하도록 제안합니다.
PEP 연기
PEP 초안에 대한 초기 논의 과정에서, t-문자열은 복잡한 서브프로세스 호출에 shell=True 수준의 문법적 편의성을 제공할 수 있는 잠재적 기회를 제공하면서도, 사용자 제공 텍스트에 전체 시스템 셸에 대한 접근 권한을 부여할 때 발생하는 보안 및 플랫폼 간 호환성 문제를 피할 수 있다는 점이 분명해졌습니다.
이에 따라 PEP 작성자들은 Python 3.15를 위한 제안서 수정 초안을 준비하기 전에, Python 3.14 베타 기간과 그 이후까지 실험적인 t-string 기반 서브프로세스 호출 라이브러리를 개발할 계획입니다.
동기
PEP 750에서 템플릿 문자열이 제공하는 안전성 이점과 유연성에도 불구하고, 표준 라이브러리에는 그 실제 적용을 보여 주는 구체적인 사용자 구현이 없습니다. 철회된 PEP 501에서 언급했듯이, t-문자열의 가장 매력적인 사용 사례 중 하나는 더 안전한 셸 명령 실행입니다.
# 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)])
현재 개발자는 편의성(잠재적인 보안 위험이 있는 f-문자열 사용)과 안전성(더 장황한 리스트 기반 API 사용) 중 하나를 선택해야 합니다. 네이티브 t-문자열 지원을 subprocess 모듈에 추가함으로써, 일반적인 보안 문제를 해결하면서 t-문자열의 가치를 입증하는 소비자용 참조 구현을 제공합니다.
근거
다음과 같은 몇 가지 이유로 subprocess 모듈은 t-문자열을 지원하기에 이상적인 후보입니다.
- 셸 명령의 명령 주입 취약점은 잘 알려진 보안 위험입니다.
- 이미
subprocess모듈은 문자열 및 리스트 기반 명령 사양을 모두 지원합니다. - t-문자열과 적절한 셸 이스케이프 사이에는 편의성과 안전성을 모두 제공하는 자연스러운 매핑이 있습니다.
- 이는 개발자가 즉시 이해하고 가치를 인식할 수 있는 t-문자열의 실용적인 예시가 됩니다.
subprocess가 t-문자열을 기본적으로 처리하도록 확장하면, 많은 개발자가 잠재적으로 안전하지 않은 f-문자열을 사용하게 만든 편의성을 희생하지 않고도 안전한 코드를 더 쉽게 작성할 수 있습니다.
사양
이 PEP는 표준 라이브러리에 다음과 같은 두 가지 주요 추가 사항을 제안합니다.
- 안전한 셸 명령 구성을 위한
shlex모듈의 새로운sh()렌더러 함수 - t-문자열 지원을
subprocess모듈의 핵심 함수에 추가하면, - 특히 명령 인자를 받는
subprocess.Popen,subprocess.run()및 기타 관련 함수
- t-문자열 지원을
셸 이스케이프를 위한 렌더러를 shlex에 추가
참조 구현으로서 안전한 POSIX 셸 이스케이프를 위한 렌더러가 shlex 모듈에 추가됩니다. 이 렌더러의 이름은 sh가 되며, 템플릿 리터럴의 각 필드 값에 shlex.quote를 호출하는 것과 동일하게 동작합니다.
따라서:
os.system(shlex.sh(t"cat {myfile}"))
다음과 동일하게 동작합니다.:
os.system("cat " + shlex.quote(myfile)))
shlex.sh의 추가는 shell=True를 전달하는 것을 피하는 것이 가장 좋다는 subprocess 문서의 기존 경고나, os.system() 문서에서 더 높은 수준의 subprocess API를 참조하는 내용을 변경하지 않습니다.
t-문자열 프로세서 구현은 다음과 같습니다.:
from string.templatelib import Template, Interpolation
def sh(template: Template) -> str:
parts: list[str] = []
for item in template:
if isinstance(item, Interpolation):
# shlex.sh implementation, so shlex.quote can be used directly
parts.append(quote(str(item.value)))
else:
parts.append(item)
# shlex.sh implementation, so `join` references shlex.join
return join(parts)
이를 통해 셸 사용을 위해 t-문자열을 명시적으로 이스케이프할 수 있습니다.:
import shlex
# Safe POSIX-compliant shell command construction
command = shlex.sh(t"cat {filename}")
os.system(command)
subprocess 모듈 변경 사항
shlex 모듈에 추가 렌더러를 제공하고 템플릿 문자열을 추가하면, subprocess 모듈을 변경하여 템플릿 문자열을 Popen의 추가 입력 유형으로 받아들이도록 처리할 수 있습니다. 이미 시퀀스 또는 문자열을 받아들이며 각각 다르게 동작하기 때문입니다. 그 결과, subprocess.Popen의 모든 고수준 함수, 예를 들어 subprocess.run()은 안전한 방식으로 문자열을 받아들일 수 있습니다(shell=False인 모든 시스템에서, 그리고 shell=True인 POSIX 시스템에서).
예를 들어:
subprocess.run(t"cat {myfile}", shell=True)
위 코드는 이 PEP에서 제공하는 shlex.sh 렌더러를 자동으로 사용합니다. 따라서 다음과 같이 subprocess.run 호출 내부에서 shlex를 사용하는 것은:
subprocess.run(shlex.sh(t"cat {myfile}"), shell=True)
run이 모든 템플릿 리터럴을 shlex.sh를 통해 자동으로 렌더링하므로 중복됩니다.
subprocess.Popen을 shell=True없이 호출하는 경우에도 t-문자열 지원은 subprocess에 더욱 인체공학적인 구문을 제공합니다. 예를 들어:
subprocess.run(t"cat {myfile} --flag {value}")
다음과 동등합니다.:
subprocess.run(["cat", myfile, "--flag", value])
또는 더 정확하게는:
subprocess.run(shlex.split(f"cat {shlex.quote(myfile)} --flag {shlex.quote(value)}"))
먼저 위와 같이 shlex.sh 렌더러를 사용한 다음, 그 결과에 shlex.split을 적용합니다.
subprocess.Popen._execute_child 내부 구현은 t-문자열을 확인합니다.:
from string.templatelib import Template
if isinstance(args, Template):
import shlex
if shell:
args = shlex.sh(args)
else:
args = shlex.split(shlex.sh(args))
하위 호환성
이 변경은 기존 동작을 변경하지 않고 새로운 기능만 추가하므로 완전히 하위 호환됩니다. subprocess 모듈은 현재와 동일한 방식으로 문자열과 리스트를 계속 처리합니다.
보안 관련 사항
이 PEP는 셸 명령에 f-문자열을 사용하는 것보다 안전한 대안을 제공하여 보안을 향상하도록 명시적으로 설계되었습니다. 컨텍스트(셸 또는 비셸)에 따라 적절한 이스케이프를 자동으로 적용함으로써 명령 주입 취약점을 방지하는 데 도움이 됩니다.
그러나 shell=True를 사용하는 경우 안전성이 POSIX 호환 셸로 제한된다는 점에 유의해야 합니다. Windows 시스템에서 셸로 cmd.exe 또는 PowerShell을 사용할 수 있는 경우, shlex.quote()가 제공하는 이스케이프 메커니즘만으로는 모든 형태의 명령 주입을 방지하기에 충분하지 않습니다.
이 기능을 가르치는 방법
이 기능은 t-문자열의 실용적 가치를 보여 주는 자연스러운 확장으로 가르칠 수 있습니다.
- 명령 주입 문제와 셸 명령에서 f-문자열이 위험한 이유를 소개합니다.
- 전통적인 해결책(리스트 기반 명령, 수동 이스케이프)을 보여 줍니다.
- 명시적인 셸 이스케이프를 위한
shlex.sh렌더러를 소개합니다.:# Unsafe: os.system(f"cat {filename}") # Potential command injection! # Safe using shlex.sh: os.system(shlex.sh(t"cat {filename}")) # Explicitly escaping for shell
- subprocess 모듈의 기본 t-문자열 지원을 소개합니다.:
# Unsafe: subprocess.run(f"cat {filename}", shell=True) # Potential command injection! # Safe but verbose: subprocess.run(["cat", filename]) # Safe and readable with t-strings: subprocess.run(t"cat {filename}", shell=True) # Automatically escapes filename subprocess.run(t"cat {filename}") # Automatically converts to list form
명확한 예제와 보안 권고를 포함하여 shlex 및 subprocess 모듈 문서에 구현을 추가해야 합니다.
비-POSIX 셸에 대한 이스케이프된 렌더링 지원 연기
shlex.quote()는 정규식 문자 집합 [\w@%+=:,./-]을 안전한 것으로 분류하고, 그 밖의 모든 문자를 안전하지 않은 것으로 간주하여 해당 문자가 포함된 문자열을 인용해야 합니다. 이때 사용되는 인용 메커니즘은 POSIX 셸에서 문자열 인용이 작동하는 방식에 특화되어 있으므로, POSIX 셸 문자열 인용 규칙을 따르지 않는 셸을 실행할 때는 신뢰할 수 없습니다.
예를 들어 POSIX 인용 규칙을 따르는 셸을 사용할 때 subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True)를 실행하는 것은 안전합니다:
$ cat > run_quoted.py
import sys, shlex, subprocess
subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True)
$ python3 run_quoted.py pwd
pwd
$ python3 run_quoted.py '; pwd'
; pwd
$ python3 run_quoted.py "'pwd'"
'pwd'
그러나 Python에서 셸을 실행할 때 cmd.exe(또는 Powershell)를 호출하는 경우에는 여전히 안전하지 않습니다:
S:\> echo import sys, shlex, subprocess > run_quoted.py
S:\> echo subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True) >> run_quoted.py
S:\> type run_quoted.py
import sys, shlex, subprocess
subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True)
S:\> python3 run_quoted.py "echo OK"
'echo OK'
S:\> python3 run_quoted.py "'& echo Oh no!"
''"'"'
Oh no!'
이 표준 라이브러리 제한 사항을 해결하는 것은 이 PEP의 범위를 벗어납니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.