Skip to content

fix: resolve 38 CodeQL alerts and exclude obj/ from analysis - #413

Merged
laurentiu021 merged 1 commit into
mainfrom
fix/codeql-alerts-batch
May 18, 2026
Merged

laurentiu021 merged 1 commit into
mainfrom
fix/codeql-alerts-batch

Conversation

@laurentiu021

@laurentiu021 laurentiu021 commented May 18, 2026 •

Copy link
Copy Markdown
Owner

Summary

Resolves 38 open CodeQL code scanning alerts across 16 source files and configures CodeQL to exclude auto-generated obj/ and �in/ directories (36 additional alerts in generated code).

Changes

Code fixes (38 alerts resolved)

  • Path safety — replaced Path.Combine with Path.Join in 8 locations (UpdateService, SpeedTestService, SpeedTestHistoryService, AboutViewModel, UninstallerService, TuneUpService)
  • Empty catch blocks — added descriptive comments to 6 intentionally-empty catches in SystemInfoService and TuneUpService
  • Generic exception — replaced catch (Exception) with specific types in TrayIconService.OnTimerTick
  • LINQ missed-where — converted implicit foreach filters to explicit .Where() in AppAlertService, NetworkSharedState, WindowsFeaturesService, SpeedTestService, TrayIconService
  • Complex conditions — extracted into helper methods: UninstallerService.IsUnderTrustedDirectory, ProcessManagerViewModel.MatchesFilter
  • Nested if — flattened in UninstallerViewModel.SelectAll
  • Missed ternary — HealthScoreService weighted average
  • Missed select — HealthScoreService.ComputeDiskScore converted to LINQ
  • Missed using — DashboardViewModel opLock converted to using declaration
  • Useless cast — removed redundant (SolidColorBrush) cast in OutputKindToBrushConverter

Configuration

  • Added .github/codeql-config.yml with paths-ignore for obj/ and bin/
  • Updated .github/workflows/codeql.yml to reference the config file

Remaining alerts (not fixable)

  • 3 P/Invoke alerts (cs/call-to-unmanaged-code, cs/unmanaged-code) — acceptable for Win32 interop
  • 2 obsolete method alerts (CreateFromSignedFile) — no modern replacement for Authenticode verification
  • 1 missed-using-statement (CancellationTokenSource field) — required for cross-method cancellation pattern

Testing

  • Build: 0 errors (main + tests)
  • No behavioral changes — all fixes are structural/stylistic

@coderabbitai

coderabbitai Bot commented May 18, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR is a comprehensive CodeQL alert remediation that introduces a dedicated analysis configuration, adds explicit exception handling across services, standardizes path construction to use Path.Join, refactors control flow to idiomatic LINQ patterns, and improves resource lifecycle management through helper extraction and using statements.

Changes

CodeQL Alert Remediation

Layer / File(s) Summary
CodeQL Configuration & Workflow Integration
.github/codeql-config.yml, .github/workflows/codeql.yml, CHANGELOG.md
New CodeQL config file excludes obj/ and bin/ build directories from analysis; workflow references the config in the init step; changelog documents all 0.48.28 fixes across services and infrastructure.
Exception Handling Specificity
SysManager/SysManager/Services/SystemInfoService.cs, SysManager/SysManager/Services/TrayIconService.cs, SysManager/SysManager/Services/TuneUpService.cs
WMI date parsing in SystemInfoService now catches FormatException and InvalidCastException separately with clarifying comments; TrayIconService explicitly catches shutdown-related OperationCanceledException and ObjectDisposedException; TuneUpService directory cleanup catches IOException and UnauthorizedAccessException with no-op bodies.
Path Construction Standardization
SysManager/SysManager/Services/SpeedTestHistoryService.cs, SysManager/SysManager/Services/SpeedTestService.cs, SysManager/SysManager/Services/TuneUpService.cs, SysManager/SysManager/Services/UninstallerService.cs, SysManager/SysManager/Services/UpdateService.cs, SysManager/SysManager/ViewModels/AboutViewModel.cs
Path construction across services migrated from Path.Combine to Path.Join for history path, Ookla tool paths, temp directory, system executable resolution, updates folder, and build stamp/script paths.
LINQ Refactoring & Control Flow Simplification
SysManager/SysManager/Services/AppAlertService.cs, SysManager/SysManager/Services/HealthScoreService.cs, SysManager/SysManager/Services/SpeedTestService.cs, SysManager/SysManager/Services/WindowsFeaturesService.cs, SysManager/SysManager/Services/TrayIconService.cs, SysManager/SysManager/ViewModels/NetworkSharedState.cs, SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs, SysManager/SysManager/ViewModels/UninstallerViewModel.cs
Registry filtering uses .Where() predicate with TryAdd; disk score computation refactored to LINQ projection with Min(); ZIP extraction and feature list parsing use .Where() filters; disk notifications use FirstOrDefault instead of loop-and-break; buffer iteration filters touched hosts; process filtering and confirmation logic extracted/simplified.
Helper Extraction & Resource Management
SysManager/SysManager/Services/UninstallerService.cs, SysManager/SysManager/ViewModels/DashboardViewModel.cs, SysManager/SysManager/Helpers/OutputKindToBrushConverter.cs, SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs
UninstallerService centralizes trusted-directory validation in IsUnderTrustedDirectory helper; DashboardViewModel uses scoped using var for lock lifecycle; OutputKindToBrushConverter removes unnecessary SolidColorBrush cast; ProcessManagerViewModel extracts filter matching into MatchesFilter helper.

Possibly related PRs

Poem

🐰 With CodeQL's keen eye we scan and review,
Path.Join at the ready, exceptions now true,
LINQ dances where loops once did tread,
Helpers extracted, resources well-fed! ✨


🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.78% 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 accurately summarizes the main changes: resolving CodeQL alerts and configuring CodeQL to exclude auto-generated directories from analysis.
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/codeql-alerts-batch

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

@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: 3

🤖 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 356-360: The current Zip Slip check in the archive extraction loop
(archive.Entries, destinationPath, fullToolsDir, toolsDir) uses a raw prefix
check that can be bypassed; replace it by computing the canonical full path and
then a relative path (e.g., Path.GetRelativePath(fullToolsDir, destinationPath))
and reject any entry where the relative path starts with ".." or is empty, or
otherwise contains path traversal, before extracting; ensure you use
Path.GetFullPath and OrdinalIgnoreCase comparisons and skip/throw for invalid
entries so extraction cannot escape the intended toolsDir boundary.

In `@SysManager/SysManager/Services/UninstallerService.cs`:
- Around line 405-408: The trusted-directory check in UninstallerService
currently uses StartsWith on fullPath which allows prefix bypass (e.g.,
"C:\Program Files Evil"). Normalize both paths (use Path.GetFullPath) and
compare directories safely: for each trustedDirs entry, get its full path,
ensure it ends with a directory separator (or otherwise treat it as a
directory), then verify fullPath starts with that trustedDirFullPath + separator
OR compute Path.GetRelativePath(trustedDirFullPath, fullPath) and ensure the
relative path does not begin with ".." (use OrdinalIgnoreCase); update the loop
that iterates trustedDirs and the check that returns true to use this
directory-aware comparison instead of naive StartsWith.

In `@SysManager/SysManager/ViewModels/NetworkSharedState.cs`:
- Around line 353-356: The loop over touched uses Buffers.ContainsKey(host) then
indexing Buffers[host], creating a TOCTOU race; replace that pattern with a
single thread-safe retrieval using Buffers.TryGetValue(host, out var buffer) and
only call TrimBuffer(buffer) when TryGetValue returns true. Update the foreach
in the method that contains the current loop (the one that calls TrimBuffer and
iterates touched) to use TryGetValue to avoid the race and eliminate separate
ContainsKey + index access.
🪄 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: f45d368e-118c-478b-80f0-4b6bc6e742d6

📥 Commits

Reviewing files that changed from the base of the PR and between 751b69b and bc501c1.

📒 Files selected for processing (19)
  • .github/codeql-config.yml
  • .github/workflows/codeql.yml
  • CHANGELOG.md
  • SysManager/SysManager/Helpers/OutputKindToBrushConverter.cs
  • SysManager/SysManager/Services/AppAlertService.cs
  • SysManager/SysManager/Services/HealthScoreService.cs
  • SysManager/SysManager/Services/SpeedTestHistoryService.cs
  • SysManager/SysManager/Services/SpeedTestService.cs
  • SysManager/SysManager/Services/SystemInfoService.cs
  • SysManager/SysManager/Services/TrayIconService.cs
  • SysManager/SysManager/Services/TuneUpService.cs
  • SysManager/SysManager/Services/UninstallerService.cs
  • SysManager/SysManager/Services/UpdateService.cs
  • SysManager/SysManager/Services/WindowsFeaturesService.cs
  • SysManager/SysManager/ViewModels/AboutViewModel.cs
  • SysManager/SysManager/ViewModels/DashboardViewModel.cs
  • SysManager/SysManager/ViewModels/NetworkSharedState.cs
  • SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs
  • SysManager/SysManager/ViewModels/UninstallerViewModel.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 (16)
.github/codeql-config.yml (1)

1-5: LGTM!

.github/workflows/codeql.yml (1)

35-35: LGTM!

CHANGELOG.md (1)

9-36: LGTM!

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

68-70: LGTM!

Also applies to: 90-91

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

132-140: LGTM!

Also applies to: 204-209

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

161-161: LGTM!

Also applies to: 197-198

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

21-23: LGTM!

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

140-143: LGTM!

SysManager/SysManager/ViewModels/AboutViewModel.cs (1)

429-431: LGTM!

Also applies to: 517-522

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

184-186: LGTM!

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

70-76: LGTM!

Also applies to: 106-112

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

119-119: LGTM!

SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs (1)

146-146: LGTM!

Also applies to: 168-173

SysManager/SysManager/ViewModels/UninstallerViewModel.cs (1)

160-166: LGTM!

SysManager/SysManager/ViewModels/DashboardViewModel.cs (1)

148-148: LGTM!

SysManager/SysManager/Helpers/OutputKindToBrushConverter.cs (1)

107-107: LGTM!

Comment on lines +356 to 360
foreach (var entry in archive.Entries.Where(e => !string.IsNullOrEmpty(e.Name)))
{
// Skip directory entries
if (string.IsNullOrEmpty(entry.Name)) continue;

var destinationPath = Path.GetFullPath(Path.Combine(toolsDir, entry.FullName));
var destinationPath = Path.GetFullPath(Path.Join(toolsDir, entry.FullName));
if (!destinationPath.StartsWith(fullToolsDir, StringComparison.OrdinalIgnoreCase))
{

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 | 🔴 Critical | ⚡ Quick win

Harden Zip Slip boundary check to enforce a path-segment boundary.

Line 359 uses a raw prefix check, which can be bypassed by sibling paths like ...\tools_evil\... that still start with ...\tools. This allows writes outside the intended extraction root.

🔒 Proposed fix
-            var fullToolsDir = Path.GetFullPath(toolsDir);
+            var fullToolsDir = Path.GetFullPath(toolsDir)
+                .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+                + Path.DirectorySeparatorChar;
             foreach (var entry in archive.Entries.Where(e => !string.IsNullOrEmpty(e.Name)))
             {
                 var destinationPath = Path.GetFullPath(Path.Join(toolsDir, entry.FullName));
-                if (!destinationPath.StartsWith(fullToolsDir, StringComparison.OrdinalIgnoreCase))
+                if (!destinationPath.StartsWith(fullToolsDir, StringComparison.OrdinalIgnoreCase))
                 {
                     Log.Warning("Zip Slip attempt blocked: {Entry} resolves outside target dir", entry.FullName);
                     continue;
                 }
🤖 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 356 - 360,
The current Zip Slip check in the archive extraction loop (archive.Entries,
destinationPath, fullToolsDir, toolsDir) uses a raw prefix check that can be
bypassed; replace it by computing the canonical full path and then a relative
path (e.g., Path.GetRelativePath(fullToolsDir, destinationPath)) and reject any
entry where the relative path starts with ".." or is empty, or otherwise
contains path traversal, before extracting; ensure you use Path.GetFullPath and
OrdinalIgnoreCase comparisons and skip/throw for invalid entries so extraction
cannot escape the intended toolsDir boundary.

Comment on lines +405 to +408
foreach (var dir in trustedDirs)
{
if (!string.IsNullOrEmpty(dir) && fullPath.StartsWith(dir, StringComparison.OrdinalIgnoreCase))
return true;

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 | 🔴 Critical | ⚡ Quick win

Fix trusted-directory validation to avoid prefix bypass.

Line 407 accepts any path starting with the trusted string, so paths like C:\Program Files Evil\malware.exe can pass. This weakens the uninstall execution trust boundary.

🛡️ Proposed fix
     foreach (var dir in trustedDirs)
     {
-        if (!string.IsNullOrEmpty(dir) && fullPath.StartsWith(dir, StringComparison.OrdinalIgnoreCase))
-            return true;
+        if (string.IsNullOrEmpty(dir)) continue;
+        var trusted = System.IO.Path.GetFullPath(dir)
+            .TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
+        var candidate = System.IO.Path.GetFullPath(fullPath)
+            .TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
+
+        if (candidate.Equals(trusted, StringComparison.OrdinalIgnoreCase) ||
+            candidate.StartsWith(trusted + System.IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) ||
+            candidate.StartsWith(trusted + System.IO.Path.AltDirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
+            return true;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
foreach (var dir in trustedDirs)
{
if (!string.IsNullOrEmpty(dir) && fullPath.StartsWith(dir, StringComparison.OrdinalIgnoreCase))
return true;
foreach (var dir in trustedDirs)
{
if (string.IsNullOrEmpty(dir)) continue;
var trusted = System.IO.Path.GetFullPath(dir)
.TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
var candidate = System.IO.Path.GetFullPath(fullPath)
.TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
if (candidate.Equals(trusted, StringComparison.OrdinalIgnoreCase) ||
candidate.StartsWith(trusted + System.IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) ||
candidate.StartsWith(trusted + System.IO.Path.AltDirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
return true;
}
🤖 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/UninstallerService.cs` around lines 405 - 408,
The trusted-directory check in UninstallerService currently uses StartsWith on
fullPath which allows prefix bypass (e.g., "C:\Program Files Evil"). Normalize
both paths (use Path.GetFullPath) and compare directories safely: for each
trustedDirs entry, get its full path, ensure it ends with a directory separator
(or otherwise treat it as a directory), then verify fullPath starts with that
trustedDirFullPath + separator OR compute
Path.GetRelativePath(trustedDirFullPath, fullPath) and ensure the relative path
does not begin with ".." (use OrdinalIgnoreCase); update the loop that iterates
trustedDirs and the check that returns true to use this directory-aware
comparison instead of naive StartsWith.

Comment on lines +353 to 356
foreach (var host in touched.Where(h => Buffers.ContainsKey(h)))
{
if (!Buffers.TryGetValue(host, out var buffer)) continue;
var buffer = Buffers[host];
TrimBuffer(buffer);

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

Use TryGetValue to avoid a race between key check and index access.

Line 353 + Line 355 introduce a TOCTOU window on Buffers: another thread can remove host after ContainsKey and before Buffers[host], which can throw. Use a single TryGetValue read instead.

Suggested fix
-        foreach (var host in touched.Where(h => Buffers.ContainsKey(h)))
-        {
-            var buffer = Buffers[host];
+        foreach (var host in touched)
+        {
+            if (!Buffers.TryGetValue(host, out var buffer)) continue;
             TrimBuffer(buffer);
             var target = Targets.FirstOrDefault(t => t.Host == host);
             if (target == null) continue;
             RecomputeStats(target, buffer);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
foreach (var host in touched.Where(h => Buffers.ContainsKey(h)))
{
if (!Buffers.TryGetValue(host, out var buffer)) continue;
var buffer = Buffers[host];
TrimBuffer(buffer);
foreach (var host in touched)
{
if (!Buffers.TryGetValue(host, out var buffer)) continue;
TrimBuffer(buffer);
🤖 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/ViewModels/NetworkSharedState.cs` around lines 353 -
356, The loop over touched uses Buffers.ContainsKey(host) then indexing
Buffers[host], creating a TOCTOU race; replace that pattern with a single
thread-safe retrieval using Buffers.TryGetValue(host, out var buffer) and only
call TrimBuffer(buffer) when TryGetValue returns true. Update the foreach in the
method that contains the current loop (the one that calls TrimBuffer and
iterates touched) to use TryGetValue to avoid the race and eliminate separate
ContainsKey + index access.

Comment on lines +405 to +409
foreach (var dir in trustedDirs)
{
if (!string.IsNullOrEmpty(dir) && fullPath.StartsWith(dir, StringComparison.OrdinalIgnoreCase))
return true;
}
Comment on lines +169 to +173
p.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
(p.Description?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) ||
(p.PlainDescription?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) ||
(p.Category?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) ||
p.Pid.ToString().Contains(filter);
@laurentiu021
laurentiu021 merged commit 1366ac5 into main May 18, 2026
5 checks passed
@laurentiu021
laurentiu021 deleted the fix/codeql-alerts-batch branch May 18, 2026 07:44
laurentiu021 added a commit that referenced this pull request May 22, 2026
…vice (#419)

## Summary

**Batch 2** of the QA bug fix series.

### Issue #396 — CancellationTokenSource not disposed
Added Dispose(bool) override to **8 ViewModels** that had CTS fields but
no cleanup on dispose:
- AppUpdatesViewModel
- DiskAnalyzerViewModel
- DriversViewModel
- DuplicateFileViewModel
- LogsViewModel
- SpeedTestViewModel
- TracerouteViewModel
- UninstallerViewModel

Each override disposes the CTS field and calls �ase.Dispose(disposing).
The existing VMs that already had Dispose (CleanupVM, DeepCleanupVM,
NetworkVM, SystemHealthVM, WindowsUpdateVM) were verified and left
unchanged.

### Issue #413 — Bare catch in UpdateService
Replaced bare \catch\ blocks with specific exception types + Serilog
logging:
- **GetRecentAsync**: catches \OperationCanceledException\,
\HttpRequestException\, \JsonException\
- **DownloadAsync**: catches \OperationCanceledException\,
\HttpRequestException\, \IOException\
- Cleanup catch blocks in DownloadAsync now catch \IOException\ and
\UnauthorizedAccessException\ instead of bare catch

### Testing
- Main project builds with 0 errors
- No behavior changes — only proper resource cleanup and specific
exception handling
- CI will validate full test suite

Closes #396, Closes #413

Co-authored-by: laurentiu021 <laurentiu021@users.noreply.github.com>
laurentiu021 added a commit that referenced this pull request May 22, 2026
Adds CHANGELOG entries for all 9 releases from the QA bug fix session:

- **v0.28.16** — Dispose lifecycle (#395, #410)
- **v0.28.17** — CTS disposal + bare catch (#396, #413)
- **v0.28.18** — Input validation + null checks (#397, #398)
- **v0.28.19** — JSON error handling (#400)
- **v0.28.20** — Drive scanning + cache eviction + ConfigureAwait (#401,
#402, #403)
- **v0.28.21** — Audit logging + error messages (#405, #407)
- **v0.28.22** — SHA256 verification (#408, #409)
- **v0.28.23** — Service timeout + snapshot persist + traceroute DNS
(#414, #415, #416)
- **v0.28.24** — Accessibility (#411)

18 bugs fixed in total.

Co-authored-by: laurentiu021 <laurentiu021@users.noreply.github.com>
laurentiu021 added a commit that referenced this pull request May 22, 2026
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