Skip to content

fix: PATH hijacking, orphan process kill, log privacy sanitization (SEC-007, SEC-008, PRIV-001)#376

Merged
laurentiu021 merged 1 commit into
mainfrom
fix/security-priority-1
May 15, 2026
Merged

fix: PATH hijacking, orphan process kill, log privacy sanitization (SEC-007, SEC-008, PRIV-001)#376
laurentiu021 merged 1 commit into
mainfrom
fix/security-priority-1

Conversation

@laurentiu021

@laurentiu021 laurentiu021 commented May 15, 2026

Copy link
Copy Markdown
Owner

Summary

PRIORITY 1 security/reliability fixes from QA audit.

Changes

SEC-007: PATH hijacking prevention (CRITICAL)

  • MsiExec.exe and rundll32.exe now resolved to absolute System32 path
  • Prevents attacker from placing malicious binary in PATH ahead of System32
  • Uses Environment.GetFolderPath(SpecialFolder.System) for reliable resolution

SEC-008: Orphan process kill (CRITICAL)

  • WaitForExitAsync now wrapped in try/catch(OperationCanceledException)
  • On timeout/cancellation, proc.Kill(entireProcessTree: true) is called
  • Prevents Ookla CLI from running indefinitely after user cancels or timeout fires

PRIV-001: Log privacy sanitization (MEDIUM)

  • All ex.Message values in SpeedTestService Log.Debug calls now pass through LogService.SanitizePath
  • Prevents username leakage via %LOCALAPPDATA% paths in exception messages

Build

  • 0 errors, 0 new warnings

Summary by CodeRabbit

Release Notes v0.48.13

Bug Fixes

  • Improved trusted system utility execution in installer service by resolving to absolute paths
  • Enhanced speed test service to clean up processes when timeout or cancellation occurs
  • Increased log security by sanitizing sensitive file paths from debug messages

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR hardens security and stability across two Windows service executables. UninstallerService prevents PATH hijacking by resolving trusted uninstall binaries to absolute System32 paths. SpeedTestService prevents orphaned Ookla processes during cancellation and removes sensitive paths from debug logs. Changes are documented in the 0.48.13 release notes.

Changes

Security and Stability Hardening

Layer / File(s) Summary
System32 path resolution for trusted uninstallers
SysManager/SysManager/Services/UninstallerService.cs
When uninstall target matches the trusted binary allowlist (MsiExec, rundll32), it is resolved to an absolute Windows System32 directory path with proper .exe extension appended, preventing PATH hijacking attacks while maintaining backward compatibility with non-trusted uninstall paths.
Ookla process cleanup on cancellation
SysManager/SysManager/Services/SpeedTestService.cs
RunOoklaAsync wraps proc.WaitForExitAsync in try/catch to detect timeout/cancellation via OperationCanceledException, then kills the entire Ookla process tree with Kill(entireProcessTree: true) before rethrowing, eliminating orphaned speedtest.exe processes left behind by timeouts.
Debug log exception message sanitization
SysManager/SysManager/Services/SpeedTestService.cs
Exception messages in File.Delete failure handlers across locked file cleanup, Authenticode subject mismatch, and signature verification paths now use LogService.SanitizePath(ex2.Message) instead of raw exception text, preventing username and directory paths from leaking into debug logs.
Release documentation
CHANGELOG.md
Version 0.48.13 release notes document the three security and stability fixes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • laurentiu021/SystemManager#361: Adds complementary hardening to UninstallerService.UninstallLocalAsync executable validation to prevent binary hijacking.
  • laurentiu021/SystemManager#370: Implements linked timeout handling in SpeedTestService's Ookla execution path, related to the timeout/cancellation behavior modified in this PR.
  • laurentiu021/SystemManager#364: Modifies the same UninstallerService and SpeedTestService methods for stricter validation and failure handling in trusted-executable execution flows.

Poem

🐰 Three holes in the garden we've now patched up tight,
No PATH-jumping mischief, no processes in flight,
And usernames hidden from logs' prying eyes—
Security hardens! A rabbit's delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the three main security/reliability fixes: PATH hijacking prevention (SEC-007), orphan process termination (SEC-008), and log privacy sanitization (PRIV-001). It is concise, specific, and accurately reflects the changeset's primary concerns.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/security-priority-1

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 0% with 13 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
SysManager/SysManager/Services/SpeedTestService.cs 0.00% 8 Missing ⚠️
...sManager/SysManager/Services/UninstallerService.cs 0.00% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SysManager/SysManager/Services/SpeedTestService.cs`:
- Around line 208-220: The Process stdout/stderr reads
(proc.StandardOutput.ReadToEndAsync(linked) and
proc.StandardError.ReadToEndAsync(linked)) can throw OperationCanceledException
before the existing WaitForExitAsync catch runs, so move the two ReadToEndAsync
calls inside the same try/catch that surrounds WaitForExitAsync (or create a
single try that calls both ReadToEndAsync and then WaitForExitAsync) and in the
catch for OperationCanceledException ensure you call
proc.Kill(entireProcessTree: true) if !proc.HasExited (handling
InvalidOperationException as currently done) before rethrowing; this guarantees
proc cleanup even when cancellation happens during stream reads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4b459ac9-3568-4ebc-bbff-9cfdce87bcbe

📥 Commits

Reviewing files that changed from the base of the PR and between 451e20b and 35a2247.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • SysManager/SysManager/Services/SpeedTestService.cs
  • SysManager/SysManager/Services/UninstallerService.cs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & unit tests
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (3)
CHANGELOG.md (1)

9-19: LGTM!

SysManager/SysManager/Services/UninstallerService.cs (1)

258-284: LGTM!

SysManager/SysManager/Services/SpeedTestService.cs (1)

228-229: LGTM!

Also applies to: 289-290, 357-358, 368-369

Comment on lines 208 to +220
var stdout = await proc.StandardOutput.ReadToEndAsync(linked);
var stderr = await proc.StandardError.ReadToEndAsync(linked);
await proc.WaitForExitAsync(linked);
try
{
await proc.WaitForExitAsync(linked);
}
catch (OperationCanceledException)
{
// Timeout or user cancellation — kill the orphan process
try { if (!proc.HasExited) proc.Kill(entireProcessTree: true); }
catch (InvalidOperationException) { /* already exited */ }
throw;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="$(fd -t f SpeedTestService.cs | head -n1)"
echo "Inspecting cancellation scope in: $file"
nl -ba "$file" | sed -n '200,222p'

Repository: laurentiu021/SystemManager

Length of output: 216


🏁 Script executed:

cat -n SysManager/SysManager/Services/SpeedTestService.cs | sed -n '200,230p'

Repository: laurentiu021/SystemManager

Length of output: 1738


🏁 Script executed:

# Search for other Process + StandardOutput/StandardError patterns
rg -n "StandardOutput\.ReadToEndAsync|StandardError\.ReadToEndAsync" SysManager --type cs -B 5 -A 5

Repository: laurentiu021/SystemManager

Length of output: 2373


🏁 Script executed:

# Check for other Process with cancellation token patterns
rg -n "WaitForExitAsync.*CancellationToken|ReadToEndAsync.*CancellationToken" SysManager --type cs -B 3 -A 3

Repository: laurentiu021/SystemManager

Length of output: 52


Protect process cleanup against cancellation during stream reads.

ReadToEndAsync(linked) on lines 208-209 can throw OperationCanceledException before reaching the try/catch block, bypassing the proc.Kill() cleanup and leaving an orphaned process. Both reads must be wrapped in the cancellation-guarded block.

Suggested fix
         using var proc = new Process();
         proc.StartInfo = psi;
         await Task.Run(() => proc.Start(), linked).ConfigureAwait(false);
-        var stdout = await proc.StandardOutput.ReadToEndAsync(linked);
-        var stderr = await proc.StandardError.ReadToEndAsync(linked);
+        string stdout;
+        string stderr;
         try
         {
+            var stdoutTask = proc.StandardOutput.ReadToEndAsync(linked);
+            var stderrTask = proc.StandardError.ReadToEndAsync(linked);
             await proc.WaitForExitAsync(linked);
+            stdout = await stdoutTask.ConfigureAwait(false);
+            stderr = await stderrTask.ConfigureAwait(false);
         }
         catch (OperationCanceledException)
         {
             // Timeout or user cancellation — kill the orphan process
             try { if (!proc.HasExited) proc.Kill(entireProcessTree: true); }
             catch (InvalidOperationException) { /* already exited */ }
             throw;
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SysManager/SysManager/Services/SpeedTestService.cs` around lines 208 - 220,
The Process stdout/stderr reads (proc.StandardOutput.ReadToEndAsync(linked) and
proc.StandardError.ReadToEndAsync(linked)) can throw OperationCanceledException
before the existing WaitForExitAsync catch runs, so move the two ReadToEndAsync
calls inside the same try/catch that surrounds WaitForExitAsync (or create a
single try that calls both ReadToEndAsync and then WaitForExitAsync) and in the
catch for OperationCanceledException ensure you call
proc.Kill(entireProcessTree: true) if !proc.HasExited (handling
InvalidOperationException as currently done) before rethrowing; this guarantees
proc cleanup even when cancellation happens during stream reads.

var systemDir = Environment.GetFolderPath(Environment.SpecialFolder.System);
var resolvedName = exeFileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)
? exeFileName : exeFileName + ".exe";
exe = System.IO.Path.Combine(systemDir, resolvedName);
@laurentiu021
laurentiu021 merged commit b35ef62 into main May 15, 2026
5 checks passed
@laurentiu021
laurentiu021 deleted the fix/security-priority-1 branch May 15, 2026 07:29
laurentiu021 added a commit that referenced this pull request May 22, 2026
…EC-007, SEC-008, PRIV-001) (#376)

Co-authored-by: laurentiu021 <laurentiu021@users.noreply.github.com>
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.

3 participants