fix(python): avoid FileAlreadyExistsException in venv cleanup#373
fix(python): avoid FileAlreadyExistsException in venv cleanup#373
Conversation
Remove redundant Files.createFile() before Files.write() in PythonControllerVirtualEnv. Files.write() already creates the file if it doesn't exist. The createFile() call caused FileAlreadyExistsException when a previous analysis failed mid-way and left the requirements.txt behind in /tmp/trustify_da_env/. Implements TC-3894 Assisted-by: Claude Code
ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan Review Summary by QodoRemove redundant file creation in Python venv cleanup
WalkthroughsDescription• Remove redundant Files.createFile() call before Files.write() • Files.write() already creates file if missing • Fixes FileAlreadyExistsException when retrying failed analyses • Prevents stale requirements.txt from causing race conditions Diagramflowchart LR
A["Previous: createFile + write"] -->|"FileAlreadyExistsException"| B["Failed on retry"]
C["Fixed: write only"] -->|"Auto-creates file"| D["Works on retry"]
File Changes1. src/main/java/io/github/guacsec/trustifyda/utils/PythonControllerVirtualEnv.java
|
Code Review by Qodo
1. TOCTOU write in /tmp
|
Test Results379 tests 379 ✅ 1m 46s ⏱️ Results for commit 420c7e3. |
| Files.deleteIfExists(envRequirements); | ||
| String freezeOutput = | ||
| Operations.runProcessGetOutput(pythonEnvironmentDir, pipBinaryLocation, "freeze"); | ||
| Files.createFile(envRequirements); | ||
| Files.write(envRequirements, freezeOutput.getBytes()); | ||
| Operations.runProcessGetOutput( | ||
| pythonEnvironmentDir, pipBinaryLocation, "uninstall", "-y", "-r", "requirements.txt"); |
There was a problem hiding this comment.
1. Toctou write in /tmp 🐞 Bug ⛨ Security
PythonControllerVirtualEnv.cleanEnvironment() deletes requirements.txt, runs a non-trivial external command (pip freeze), then writes requirements.txt; after removing Files.createFile(), a file recreated during that window will now be overwritten instead of failing fast. Because the venv directory is a fixed /tmp path and prepareEnvironment() trusts any pre-existing path, this enables symlink/race-style clobbering and can corrupt cleanup behavior.
Agent Prompt
### Issue description
`cleanEnvironment(false)` deletes `/tmp/trustify_da_env/requirements.txt`, runs `pip freeze`, then writes `requirements.txt`. With the removal of `Files.createFile()`, a path recreated between delete and write will be overwritten, creating a TOCTOU window on a `/tmp`-based directory.
### Issue Context
- The environment directory is hard-coded under `/tmp` and `prepareEnvironment()` trusts any existing path at that location.
- `pip freeze` introduces a time gap between delete and write.
### Fix Focus Areas
- src/main/java/io/github/guacsec/trustifyda/utils/PythonControllerVirtualEnv.java[39-43]
- src/main/java/io/github/guacsec/trustifyda/utils/PythonControllerVirtualEnv.java[89-96]
### Proposed fix
1. Write freeze output to a temp file in `pythonEnvironmentDir` (e.g., `Files.createTempFile(pythonEnvironmentDir, "requirements", ".txt")`).
2. Atomically replace the final `requirements.txt` using `Files.move(temp, envRequirements, ATOMIC_MOVE, REPLACE_EXISTING)` (fallback to non-atomic move if ATOMIC_MOVE unsupported).
3. (Optional hardening) Validate `pythonEnvironmentDir` is a real directory (not a symlink) before using it, and consider creating a per-run temp venv directory instead of a fixed `/tmp/trustify_da_env` path.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
Summary
Files.createFile()beforeFiles.write()inPythonControllerVirtualEnvFiles.write()already creates the file if it doesn't existcreateFile()call causedFileAlreadyExistsExceptionwhen a previous analysis failed mid-way and left therequirements.txtbehind in/tmp/trustify_da_env/Test plan
FileAlreadyExistsExceptionImplements TC-3894
🤖 Generated with Claude Code