Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: QuickShell CodeQL configuration

queries:
- uses: security-and-quality

query-filters:
# Quality notes (not vulns). Quick Shell is a Windows shell host that must call
# Win32/COM for folder/file dialogs, clipboard, and dialog timeout recovery after
# dropping WinForms for MSIX trimming.
#
# cs/call-to-unmanaged-code: call sites of extern methods
# cs/unmanaged-code: DllImport/LibraryImport declarations themselves
- exclude:
id: cs/call-to-unmanaged-code
- exclude:
id: cs/unmanaged-code
5 changes: 4 additions & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ on:
- "**/*.sln"
- "QuickShell.Raycast/**"
- ".github/workflows/codeql.yml"
- ".github/codeql/**"
pull_request:
paths:
- "**/*.cs"
- "**/*.csproj"
- "**/*.sln"
- "QuickShell.Raycast/**"
- ".github/workflows/codeql.yml"
- ".github/codeql/**"
schedule:
- cron: "17 3 * * 0"
workflow_dispatch:
Expand Down Expand Up @@ -51,6 +53,7 @@ jobs:
- '**/*.csproj'
- '**/*.sln'
- '.github/workflows/codeql.yml'
- '.github/codeql/**'
raycast:
- 'QuickShell.Raycast/**'
- '.github/workflows/codeql.yml'
Expand Down Expand Up @@ -86,7 +89,7 @@ jobs:
uses: github/codeql-action/init@v4
with:
languages: csharp
queries: security-and-quality
config-file: ./.github/codeql/codeql-config.yml

- name: Restore solution
run: dotnet restore QuickShell.sln --verbosity minimal
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ jobs:

${{ github.event.inputs.release_notes }}

Package matrix: [docs/release-packages.md](https://github.com/tonythethompson/QuickShell/blob/main/docs/release-packages.md)
Package matrix: [docs/release-packages.md](https://github.com/tonythethompson/QuickShell/blob/master/docs/release-packages.md)

## Install

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-run-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
Restart PowerToys after copying files.

- Activate with the **qs** action keyword, or type **Quick Shell** / **quickshell** in global Run.
- See [docs/powertoys-run-plugin.md](https://github.com/tonythethompson/QuickShell/blob/main/docs/powertoys-run-plugin.md).
- See [docs/powertoys-run-plugin.md](https://github.com/tonythethompson/QuickShell/blob/master/docs/powertoys-run-plugin.md).
files: |
QuickShell.Run/bin/x64/Release/QuickShell.Run-x64.zip
QuickShell.Run/bin/ARM64/Release/QuickShell.Run-ARM64.zip
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.2.0" />
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.8" />
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
Expand Down
72 changes: 72 additions & 0 deletions QuickShell.Core.Tests/WorkspaceSecurityPolicyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,78 @@ public void Open_directory_rejects_non_local_path_namespaces(string directory)
Assert.Equal(WorkspaceIssueCode.DirectoryOpenNotAllowed, result.PrimaryIssueCode);
}

[Fact]
public void ComputeDigest_matches_legacy_anonymous_Serialize_bytes()
{
// Pins trim-safe DTO serialization to the pre-refactor JsonSerializer.Serialize(anonymous)
// shape (compact, nulls included). Drift here silently invalidates WorkspaceReviewToken digests.
var workspace = new TerminalShortcut
{
Id = "digest-workspace",
Name = "Digest",
Directory = @"C:\repos\demo",
Command = null,
Terminal = "wt",
WtProfile = null,
RunAsAdmin = false,
Launches =
[
new WorkspaceEntry
{
Id = "launch-1",
Label = "Launch",
Terminal = "default",
WtProfile = null,
Command = "echo hi",
RunAsAdmin = false,
IsEnabled = true,
Order = 0,
TaskType = "none",
},
],
DevServerUrl = null,
OpenDevServerOnLaunch = false,
RepoUrl = null,
CompanionApps =
[
new CompanionAppEntry
{
Id = "companion-1",
Path = @"C:\Tools\editor.exe",
Arguments = null,
OpenOnLaunch = true,
Order = 0,
},
],
OpenCompanionAppOnLaunch = true,
CompanionAppPath = @"C:\Tools\editor.exe",
CompanionAppArguments = null,
};

var legacyPayload = System.Text.Json.JsonSerializer.Serialize(new
{
workspace.Id,
workspace.Name,
workspace.Directory,
workspace.Command,
workspace.Terminal,
workspace.WtProfile,
workspace.RunAsAdmin,
workspace.Launches,
workspace.DevServerUrl,
workspace.OpenDevServerOnLaunch,
workspace.RepoUrl,
workspace.CompanionApps,
workspace.OpenCompanionAppOnLaunch,
workspace.CompanionAppPath,
workspace.CompanionAppArguments,
});
var expected = Convert.ToHexString(
System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(legacyPayload)));

Assert.Equal(expected, WorkspaceSecurityPolicy.ComputeDigest(workspace));
}

[Fact]
public void Trust_review_token_is_invalidated_by_revision_change()
{
Expand Down
39 changes: 24 additions & 15 deletions QuickShell.Core/Classification/ProjectAnalysisService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using QuickShell.Abstractions.Classification;
using QuickShell.Models;
using QuickShell.Services;

namespace QuickShell.Classification;
Expand Down Expand Up @@ -78,21 +79,26 @@ public string GetTaskTypeChoiceTooltip(string? directory, string? taskType, Task
return $"Suggests: {first.Command} · also {alternates}";
}

/// <summary>
/// Builds a JSON payload containing the available task type choices.
/// </summary>
/// <param name="directory">The project directory used to determine available choices.</param>
/// <param name="pickContext">Context used to select task type suggestions.</param>
/// <param name="includePlaceholder">Whether to include the choice for adding a new command row.</param>
/// <returns>A JSON representation of the task type choices.</returns>
public string BuildTaskTypeChoicesJson(
string? directory = null,
TaskTypePickContext? pickContext = null,
bool includePlaceholder = true)
{
pickContext ??= TaskTypePickContext.Empty;
var choices = new List<object>();
var choices = new List<TaskTypeChoiceJson>();
if (includePlaceholder)
{
choices.Add(new
{
title = "Choose a command…",
value = TaskTypeCatalog.None,
tooltip = "Adds a new command row with a project-aware suggestion.",
});
choices.Add(new TaskTypeChoiceJson(
"Choose a command…",
TaskTypeCatalog.None,
"Adds a new command row with a project-aware suggestion."));
}

foreach (var def in TaskTypeCatalog.GetChoices())
Expand All @@ -102,18 +108,21 @@ public string BuildTaskTypeChoicesJson(
continue;
}

choices.Add(new
{
title = def.Title,
value = def.Id,
tooltip = GetTaskTypeChoiceTooltip(directory, def.Id, pickContext),
});
choices.Add(new TaskTypeChoiceJson(
def.Title,
def.Id,
GetTaskTypeChoiceTooltip(directory, def.Id, pickContext)));
}

return System.Text.Json.JsonSerializer.Serialize(choices);
return System.Text.Json.JsonSerializer.Serialize(choices, QuickShellJsonContext.Default.ListTaskTypeChoiceJson);
}

public CompanionAppSuggestion? TrySuggestCompanionApp(string directory) =>
/// <summary>
/// Suggests a companion app for the specified project directory.
/// </summary>
/// <param name="directory">The project directory to inspect.</param>
/// <returns>A companion app suggestion, or null when no suggestion is available.</returns>
public CompanionAppSuggestion? TrySuggestCompanionApp(string directory) =>
_companionAppDetector.TrySuggest(directory);

public string? TryDetectDevServerUrl(string directory) =>
Expand Down
28 changes: 28 additions & 0 deletions QuickShell.Core/Interop/NativeForegroundWindow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Runtime.InteropServices;

namespace QuickShell.Interop;

/// <summary>
/// Owner HWND for modal shell dialogs. Win32 is intentional: after dropping WinForms
/// there is no BCL/WinRT equivalent that works for both the packaged CmdPal host and
/// the PowerToys Run plugin.
/// </summary>
internal static partial class NativeForegroundWindow
{
/// <summary>
/// Gets the handle of the current foreground window.
/// </summary>
/// <returns>The handle of the current foreground window.</returns>
public static nint Get()
{
// codeql[cs/call-to-unmanaged-code] Required for IFileDialog owner hwnd; no managed API after WinForms removal.
return GetForegroundWindow();
}

/// <summary>
/// Retrieves the handle of the current foreground window.
/// </summary>
/// <returns>The handle of the foreground window, or zero if no foreground window exists.</returns>
[LibraryImport("user32.dll")]
private static partial nint GetForegroundWindow();
}
Loading
Loading