T09 · Insecure Skill Coding Practices
- Location
scripts/doc_qa.py:12- Finding
Arbitrary Python Code Execution Through an Unsafely Interpolated PDF Path
- Content
View full analysis
Vulnerability Details
File Location:
scripts/doc_qa.py, lines 12-14
Vulnerability Type: Python interpreter argument injection
Risk Level: HighVulnerable Code:
python result = subprocess.run(["python3", "-c", f"import pdfplumber; print(pdfplumber.open('{path}').pages[0].extract_text())"], capture_output=True, text=True)Technical Analysis
The PDF path is inserted directly into Python source code passed to a child interpreter through the
python3 -coption. Althoughsubprocess.run()does not invoke a command shell here, the value is still interpreted as executable Python code.The
pathvalue can originate from the user-supplied--docsargument or from filenames returned byos.listdir()when a document directory is processed. A malicious filename containing a single quote, closing syntax, and additional Python statements can escape thepdfplumber.open()string literal.For example, a filename shaped like the following can alter the generated Python program:
text x'); __import__('os').system('id'); #.pdfWhen joined to the document directory and interpolated into the command, the resulting child-process source can execute the injected statement. The broad exception handler and captured output do not prevent execution; they may only conceal errors or command output after the payload runs.
Attack Path
- An attacker creates or supplies a file with a
.pdfextension and a filename containing valid Python injection syntax. - The attacker places that file in a directory that the victim will process, or convinces the victim to pass its path through
--docs. qa()enumerates the file and passes its path toread_file().read_file()interpolates the untrusted path into source code supplied topython3 -c.- The child Python interpreter parses and executes the attacker-controlled statements.
- The payload runs with the same operating-system ...[truncated 780 chars]
- An attacker creates or supplies a file with a
- Remediation
View remediation
Remediation Suggestions
Import and use
pdfplumberdirectly rather than constructing a second Python program:python elif ext == ".pdf": try: import pdfplumber with pdfplumber.open(path) as pdf: if not pdf.pages: return "" return pdf.pages[0].extract_text() or "" except Exception: return "[PDF parsing unavailable]"If a separate process is required for isolation, pass the document path as a distinct argument and retrieve it through
sys.argv; never interpolate it into executable source:python result = subprocess.run( [ "python3", "-c", ( "import sys, pdfplumber; " "p = pdfplumber.open(sys.argv[1]); " "print(p.pages[0].extract_text() or '')" ), path, ], capture_output=True, text=True, check=False, timeout=30, )Additional hardening should include:
- Resolve and validate document paths against an explicitly permitted root directory.
- Reject unsupported file types rather than attempting to read every unknown format as UTF-8 text.
- Apply subprocess timeouts and resource limits to untrusted document parsing.
- Catch specific parsing and I/O exceptions instead of using a bare
except. - Run document parsers in a sandbox or low-privilege worker when processing attacker-controlled files.
