Skip to content

fix(python): place Windows venv in configured temp directory - #696

Open
a-oren wants to merge 2 commits into
guacsec:mainfrom
a-oren:TC-7494
Open

a-oren wants to merge 2 commits into
guacsec:mainfrom
a-oren:TC-7494

Conversation

@a-oren

@a-oren a-oren commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Create the controller-managed Python venv under os.tmpdir() on Windows instead of a root-relative \tmp path on the current drive.

  • Build Scripts\python.exe and Scripts\pip.exe from the complete venv directory, so venv creation and the pip upgrade agree even when TEMP and the working directory are on different drives.

  • Keep Linux's fixed /tmp/trustify_da_env_js path, executable names, and reuse/uninstall behavior unchanged.

  • TC-7561: keep Windows command paths literal at the subprocess boundary (0724cb7), addressing review discussion 4553943998.

    • Invoke native executables without a shell or pre-quoting, preserving complete temp paths with spaces or & as both venv targets and interpreter paths.
    • Preserve .bat/.cmd support via explicit escaped cmd.exe invocation; resolve PATH/PATHEXT first, including explicit .cmd names, so npm .bin shims receive the required reparse escaping. Reject CR/LF in batch inputs before spawning.
    • Add 13 intercepted boundary regressions covering native execution, wrappers, expansion syntax, shim reparsing, exact-extension lookup, and unchanged POSIX behavior. The ampersand-path and explicit-shim regressions were observed failing before their fixes.
    • Verification: focused command 28 passing; lint 0 errors / 15 unchanged baseline warnings; full npm test 1,063 passing / 15 failing, compared with unmodified PR HEAD 1,050 passing / 15 failing. All 15 failure names, primary errors, and causes match: 3 OCI fixture assertions, 2 broken-local-pipdeptree cases, and 10 missing-Poetry cases. Proceeding with these matching failures was explicitly approved.
    • Native Windows execution remains unavailable/unverified; interception verifies emitted commands, not real cmd.exe/wrapper execution. Python placement, executable names, fixed venv name, and lifecycle remain unchanged.

Tests

  • Failing-first Windows placement test using an explicit path.win32 seam (TEMP on D:), asserting creation and pip upgrade use <TEMP>\trustify_da_env_js\Scripts\python.exe.
  • Existing Windows executable tests still cover TC-7486's python3 -> python.exe naming; POSIX tests now run with a non-default temp dir to prove /tmp remains fixed.
  • Focused: npm run tests -- --grep "Python_controller" - 13 passing.
  • Full: npm test - 1052 passing; the 15 failures are pre-existing environment issues that fail identically on unmodified main (missing poetry, broken pipdeptree install, OCI image fixture drift).
  • npm run lint - 0 errors, same 15 pre-existing warnings as main.

Native Windows execution was not available in this environment and remains unverified.

Implements TC-7494

Summary by Sourcery

Correct Windows Python environment placement and make cross-platform command invocation reliable and safe.

Bug Fixes:

  • Place Windows-managed Python virtual environments under the configured OS temporary directory and use that same complete path for venv executables and pip upgrades.
  • Harden Windows command invocation for native executables, batch files, and npm shims while preserving literal paths and safely escaping arguments.

Enhancements:

  • Preserve Linux virtual-environment placement and executable behavior while adding coverage for cross-drive Windows paths and command-boundary behavior.

Tests:

  • Add focused tests covering Windows venv placement, executable naming, command resolution, argument escaping, batch-wrapper safety, and unchanged POSIX behavior.

@sourcery-ai

sourcery-ai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Implementation

Place Windows-managed Python environments under the configured temp directory and build their executable paths from that directory, while preserving the fixed POSIX location.

  • src/providers/python_controller.js L104-113, L119 (removed lines), L124-125

Resolve Windows commands before dispatch and invoke native executables literally while escaping batch-wrapper inputs, including npm shim arguments.

  • src/tools.js L4, L241-256, L277-325

Tests

Verify Windows venv placement and executable paths, and confirm POSIX environments retain their fixed /tmp location.

  • test/providers/python_controller.test.js L18-27, L171-208 (+8 small edits)

Exercise Windows command resolution, native argument handling, batch escaping, and unchanged POSIX invocation at the subprocess boundary.

  • test/tools.test.js L4, L23-51, L223-348

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/providers/python_controller.js" line_range="112" />
<code_context>
+			// drive-aware even when the Windows branch runs on another host (tests).
+			const venvPath = isWindows ? path.win32 : path.posix
+			this.pythonEnvDir = isWindows
+				? venvPath.join(os.tmpdir(), "trustify_da_env_js")
+				: venvPath.join("/", "tmp", "trustify_da_env_js")
 			try {
</code_context>
<issue_to_address>
**Temp paths execute shell commands**

When the configured Windows temp path contains an attacker-controlled shell metacharacter such as `&` but no spaces, `invokeCommand` passes `pythonEnvDir` through `shell: true`, and `handleSpacesInPath` leaves the path unquoted, so `cmd.exe` treats `&` as a command separator and runs the injected command; venv creation or the pip upgrade fails and the analysis process runs attacker-controlled commands.

Quote or escape Windows paths containing shell metacharacters before passing them to the shell, or avoid invoking commands through a shell.
</issue_to_address>

### Comment 2
<location path="src/providers/python_controller.js" line_range="112" />
<code_context>
+			// drive-aware even when the Windows branch runs on another host (tests).
+			const venvPath = isWindows ? path.win32 : path.posix
+			this.pythonEnvDir = isWindows
+				? venvPath.join(os.tmpdir(), "trustify_da_env_js")
+				: venvPath.join("/", "tmp", "trustify_da_env_js")
 			try {
</code_context>
<issue_to_address>
**Shared temp venv can be preseeded**

When a Windows process uses a temp directory writable by an untrusted local user, who pre-creates the venv path with a malicious startup module, `pythonEnvDir` is a predictable child of the shared temp root, and `prepareEnvironment()` runs `python -m venv` there before executing the venv interpreter for the pip upgrade. Python reuses existing venv contents, so a planted `sitecustomize.py` can execute with the analysis process's privileges.

Create the venv in a securely created private directory and verify that it is owned by the process and is not a reparse point before invoking Python.
</issue_to_address>

### Comment 3
<location path="src/providers/python_controller.js" line_range="112" />
<code_context>
+			// drive-aware even when the Windows branch runs on another host (tests).
+			const venvPath = isWindows ? path.win32 : path.posix
+			this.pythonEnvDir = isWindows
+				? venvPath.join(os.tmpdir(), "trustify_da_env_js")
+				: venvPath.join("/", "tmp", "trustify_da_env_js")
 			try {
</code_context>
<issue_to_address>
**Overlapping scans corrupt shared environments**

When two Windows analyses using the same configured temp directory overlap, both controllers run `python -m venv` and install dependencies into the same `pythonEnvDir`; one setup can disrupt the other's environment, and `getDependencies()` can read the other scan's packages after yielding, so setup fails or the scan returns an incorrect SBOM.

Give each analysis its own isolated virtual-environment directory, or coordinate access so concurrent scans cannot modify or read the same environment.
</issue_to_address>

Sourcery assessment

Approval pending. 3 findings to address first.

Blocking findings: src/providers/python_controller.js:112, src/providers/python_controller.js:112, src/providers/python_controller.js:112


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/providers/python_controller.js Resolved
Comment thread src/providers/python_controller.js Resolved
Comment thread src/providers/python_controller.js Resolved
@codecov-commenter

codecov-commenter commented Nov 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.68%. Comparing base (ce9658d) to head (0724cb7).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #696      +/-   ##
==========================================
+ Coverage   91.60%   91.68%   +0.07%     
==========================================
  Files          44       44              
  Lines       11638    11700      +62     
  Branches     2346     2371      +25     
==========================================
+ Hits        10661    10727      +66     
+ Misses        977      973       -4     
Flag Coverage Δ
unit-tests 91.68% <100.00%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/providers/python_controller.js 89.67% <100.00%> (+0.06%) ⬆️
src/tools.js 94.53% <100.00%> (+2.22%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

prepareEnvironment() built the managed venv at
path.join(path.sep, "tmp", "trustify_da_env_js"), which becomes a
root-relative \tmp path on Windows, then re-anchored the interpreter and
pip paths to the current drive root from process.cwd().

Use os.tmpdir() on Windows so the venv lives in the OS-configured temp
directory, and join Scripts\python.exe and Scripts\pip.exe from that
complete venv directory. The venv creation and pip upgrade now share one
directory even when TEMP and the working directory are on different
drives.

POSIX keeps the fixed /tmp/trustify_da_env_js path with the existing
reuse and pip uninstall behavior.

Adds one failing-first Windows placement test backed by an explicit
path.win32 seam; Linux path preservation is covered by the existing
POSIX tests running with a non-default temp dir. Native Windows execution
remains unverified on this POSIX host.

Implements TC-7494

Signed-off-by: Adva Oren <aoren@redhat.com>
Assisted-by: Claude Code
@a-oren

a-oren commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor Author

[sdlc-workflow/verify-pr] Re: @sourcery-ai[bot] review — all three items in review 5879037787 are classified as code change requests; their validity and scope are assessed separately from the language classification.

  1. review-body-5879037787-1 / inline 4553943998, Temp paths execute shell commands: confirmed newly exposed command-boundary defect. Tracked once in TC-7561, a standalone Task blocking TC-7494 and targeting this PR; no duplicate issue for the review-body repetition. Intercepted invocation evidence confirms unprotected metacharacter paths with shell: true; native Windows execution was not performed.
  2. review-body-5879037787-2 / inline 4553944024, Shared temp venv can be preseeded: pre-existing conditional risk requiring attacker write access. No newly weakened ownership/ACL boundary was demonstrated; private-directory/lifecycle changes are explicitly outside TC-7494. No Task created.
  3. review-body-5879037787-3 / inline 4553944040, Overlapping scans corrupt shared environments: pre-existing shared-lifecycle concurrency risk. The requested isolation/coordination changes conflict with the task's preservation scope. No Task created.

Individual inline threads contain the corresponding reasoning. These dispositions do not assert that the broader lifecycle risks are fixed.

@a-oren

a-oren commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Verification Report for TC-7494 (commit b6d2f58)

Reviewed HEAD: b6d2f58a59fd67fe1d46e9e9aa52a4e51a37d2ea; base: ce9658daa444a1d0c4fbf9730560bc51ae38a6c5.

Check Result Details
Review Feedback WARN Three distinct requests plus three review-body duplicates assessed; one required fix tracked in TC-7561, blocking TC-7494. All items received classification/disposition replies.
Root-Cause Investigation DONE Reused existing preventive Task TC-5951, linked Related to TC-7494 and added supporting analysis. No duplicate root-cause issue created.
Scope Containment PASS Exactly the two authorized production/test files; no missing or extra files.
Diff Size PASS +62 / -37 = 99 changed lines across two files; proportionate to the task.
Commit Traceability PASS The sole commit references Implements TC-7494.
Sensitive Patterns PASS No secrets or credentials in the 62 added lines. This narrow scan does not negate the command-boundary defect below.
CI Status PASS All current checks green at this HEAD, including Node 22/24 lint/test, PR title, commit messages, dependabot configuration, and Sourcery check execution.
Acceptance Criteria FAIL 3 of 4 assessed as met, with provider non-regression assessed indirectly. AC1 fails for shell-active Windows temp paths; ordinary placement, cross-drive paths, and Linux preservation are verified.
Test Quality PASS No repetitive test sprawl; all test bodies documented. Eval Quality: N/A (no eval result reviews).
Test Change Classification MIXED Useful placement/Linux coverage added; Windows python3 no longer checks complete creation arguments or controller.pythonEnvDir. Informational, nonblocking; restore assertions in the existing loop, not new tests.
Verification Commands FAIL Focused: 13 passing. Lint: 0 errors / 15 existing warnings. Full local suite: 1050 passing / 15 failures, exit 15; historically matched failure titles, not a clean local pass.

Overall: FAIL

Required fix

src/providers/python_controller.js:112,115,125-128 → src/tools.js:114-120,243-246: configurable Windows temp paths can become shell syntax. The controller now incorporates os.tmpdir() into both the venv-target argument and its executable path. Windows invokeCommand() forces shell: true; handleSpacesInPath() only quotes strings containing spaces. An intercepted production-call-chain probe using C:\Temp&AuditMarker captured both paths without protection and shell: true.

This is a valid-path portability failure and a command-injection exposure when the configured path is attacker-influenced. The unsafe helper predates the PR, but exposing configurable TEMP content to it is new. No injected command was executed; native Windows/cmd.exe execution remains unverified. No remote exploitability or privilege escalation is claimed. See Node's shell-input warning.

TC-7561 tracks a narrow command-boundary fix on this existing PR, preserving supported batch-wrapper behavior and the fixed venv lifecycle. It is a standalone Task, as authorized, because TC-7494 is already a Sub-task and Jira does not nest sub-tasks. Inline 4553943998 and duplicate review-body-5879037787-1 are covered by the same issue.

Other review dispositions

  • Preseeding (4553944024, duplicate body item 2): conditional pre-existing risk requiring untrusted write access. The old implementation already reused a predictable directory without ownership/reparse checks. No newly weakened ACL boundary was demonstrated. No Task created for private-directory/lifecycle changes excluded by TC-7494.
  • Concurrency (4553944040, duplicate body item 3): pre-existing shared install/read/uninstall behavior is unchanged. Unique directories or new coordination semantics contradict this task's explicit lifecycle-preservation scope. No Task created here; the broader risk is not declared resolved.
  • All three inline requests and all three review-body subitems were classified as code change requests based on their language. Validity and scope were assessed separately; review-body duplicates did not create duplicate work.

Verification detail and limitations

  • Windows path seam: the checked-in test uses actual path.win32 semantics, not merely mocked os.platform(). A supplemental intercepted probe explicitly set cwd=C:\workspace, TEMP=D:\Temp: base created a root-relative \tmp target and used C:\tmp for upgrade; reviewed HEAD used D:\Temp\trustify_da_env_js consistently for creation, Python, and pip. This reconstructs red/green behavior, not the historical order of test development.
  • Permanent coverage: the existing placement test does not itself establish a C: cwd. The supplemental probe verifies the acceptance condition now; any permanent strengthening should modify that same test, not add another placement case.
  • Linux/lifecycle: existing POSIX tests with mocked /var/tmp retain /tmp/trustify_da_env_js, bin/python, and bin/pip. Reuse/uninstall logic is unchanged; no deletion, unique directories, or lifecycle redesign was introduced.
  • Local full suite: 3 OCI golden mismatches, 2 broken pipdeptree._runner cases, and 10 missing-Poetry cases. All 15 failure titles match historical baseline evidence, but a fresh full-suite run of this PR's exact base was not performed. The fresh pass count is 1050, versus the previously reported 1052; that difference is unexplained. Green CI and historical matches are supporting evidence, not proof of a clean local run.
  • Test reduction: the Windows source-python3 case retains exact executable checks but lost complete creation-target/controller-directory checks when those moved to the source-python placement case. Restore those two assertions in the existing parameterized loop if addressing this nonblocking coverage reduction. No additional test cases or suites are recommended. The removed duplicate python3.exe exclusion was redundant, not a loss.

Root cause

The supportable preventive gap is an implement-task analysis method: trace newly sourced input through unchanged downstream helpers and verify literal data remains literal at parsing/execution boundaries. The task's helper-mocked placement tests verify path values, not the actual subprocess boundary. No parent Feature or Feature-to-task omission was evidenced, so no define-feature/plan-feature attribution is asserted; historical skill invocation/responsibility is unproven.

Existing TC-5951 already covers this downstream re-parsing method, including shell and format-active input verification. It was reused, linked Related to TC-7494, and received supporting root-cause analysis. No duplicate preventive Task was created or branch retargeted. Node/Windows API facts stay out of general-purpose skill rules. TC-7504 remains separate relational-oracle work.

Evidence retained locally in /tmp/opencode/verify-tc7494/ (invocation/path probes, focused/full/lint logs, CI and baseline-comparison records). No Atlassian Artifacts upload requested.

Code unchanged; no merge or Jira status transition performed. TC-7494 remains in Review. Test Quality, Test Change Classification, and Root-Cause Investigation are informational and excluded from the Overall calculation.


This comment was AI-generated by sdlc-workflow/verify-pr v0.13.10.

Invoke native executables without a shell so temp paths cannot become
cmd syntax. Resolve PATH/PATHEXT wrappers before escaping batch inputs
and npm shim reparsing. Preserve POSIX invocation and Python lifecycle.

Implements TC-7561

Signed-off-by: Adva Oren <aoren@redhat.com>
Assisted-by: Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants