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
5 changes: 5 additions & 0 deletions .github/workflows/gateway-msix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,11 @@ jobs:
-p:PublishAot=true `
-p:IncludePackagingContent=true `
-p:Platform='${{ matrix.architecture }}'
dotnet restore `
.\src\OpenClaw.SessionHost\OpenClaw.SessionHost.csproj `
--runtime 'win-${{ matrix.architecture }}' `
-p:PublishAot=true `
-p:Platform='${{ matrix.architecture }}'

- name: Compose unsigned MSIX
shell: pwsh
Expand Down
2 changes: 2 additions & 0 deletions OpenClaw.Gateway.MSIX.slnx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/OpenClaw.Launcher/OpenClaw.Launcher.csproj" />
<Project Path="src/OpenClaw.SessionHost/OpenClaw.SessionHost.csproj" />
<Project Path="src/OpenClaw.SessionProtocol/OpenClaw.SessionProtocol.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/OpenClaw.Launcher.AotSmoke/OpenClaw.Launcher.AotSmoke.csproj" />
Expand Down
9 changes: 9 additions & 0 deletions scripts/Build-LocalMSIX.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ try {
-p:IncludePackagingContent=true `
"-p:Platform=$Architecture"
}
Invoke-CheckedCommand `
-FailureMessage 'Session host dependency restore failed.' `
-Command {
& dotnet restore `
.\src\OpenClaw.SessionHost\OpenClaw.SessionHost.csproj `
--runtime "win-$Architecture" `
-p:PublishAot=true `
"-p:Platform=$Architecture"
}

$sourceCommit = (& git rev-parse HEAD) -join ''
if ($LASTEXITCODE -ne 0 -or
Expand Down
54 changes: 54 additions & 0 deletions scripts/Build-MSIX.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,51 @@ New-Item `

try {
Add-VswhereToPath
$sessionHostProject = Join-Path `
$repositoryRoot `
'src\OpenClaw.SessionHost\OpenClaw.SessionHost.csproj'
$sessionHostOutput = Join-Path `
$repositoryRoot `
"content\session-host\$Architecture"
Remove-DirectoryIfPresent -Path $sessionHostOutput
New-Item -Path $sessionHostOutput -ItemType Directory -Force | Out-Null
Write-Host "Publishing the NativeAOT win-$Architecture session host."
Invoke-CheckedCommand `
-FailureMessage 'NativeAOT session host publish failed.' `
-Command {
& dotnet publish $sessionHostProject `
--configuration Release `
--runtime "win-$Architecture" `
--self-contained `
--no-restore `
"-p:Platform=$Architecture" `
-p:PublishAot=true `
--output $sessionHostOutput `
--nologo
}
$sessionHostFiles = @(
Get-ChildItem -LiteralPath $sessionHostOutput -File -Force -Recurse |
Where-Object Extension -notin '.pdb', '.xml' |
ForEach-Object {
[ordered]@{
path = (
[IO.Path]::GetRelativePath(
$sessionHostOutput,
$_.FullName
)
).Replace('\', '/')
length = $_.Length
sha256 = (
Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256
).Hash.ToLowerInvariant()
}
} |
Sort-Object path
)
if (-not ($sessionHostFiles.path -contains 'openclaw-session-host.exe')) {
throw "The published $Architecture session host is incomplete."
}

$appxOutput = $msixBuildDirectory.TrimEnd('\') + '\'
Write-Host "Building unsigned NativeAOT win-$Architecture MSIX with MSBuild."
Invoke-CheckedCommand `
Expand Down Expand Up @@ -444,6 +489,14 @@ try {
}
)
}
foreach ($sessionHostFile in $sessionHostFiles) {
$expectedPackageFiles.Add(
"session-host/$Architecture/$($sessionHostFile.path)",
[pscustomobject]@{
Hash = $sessionHostFile.sha256
}
)
}
$packageEntries = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::OrdinalIgnoreCase
)
Expand Down Expand Up @@ -588,6 +641,7 @@ try {
nodeRuntimeSha256 = $nodeArchiveHash
mxcRuntimeVersion = [string]$mxcProvenance.version
mxcRuntimeFiles = $mxcRuntimeFiles
sessionHostFiles = $sessionHostFiles
architecture = $Architecture
archive = $msixName
sha256 = $msixHash
Expand Down
40 changes: 40 additions & 0 deletions scripts/Test-SigningInputs.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,31 @@ function New-TestArtifact {
Sort-Object path
)

$sessionHostDirectory = Join-Path $staging "session-host\$Architecture"
New-Item -Path $sessionHostDirectory -ItemType Directory -Force | Out-Null
[IO.File]::WriteAllText(
(Join-Path $sessionHostDirectory 'openclaw-session-host.exe'),
"session-host-$Architecture")
$sessionHostFiles = @(
Get-ChildItem -LiteralPath $sessionHostDirectory -File -Recurse |
ForEach-Object {
[ordered]@{
path = (
[IO.Path]::GetRelativePath(
$sessionHostDirectory,
$_.FullName
)
).Replace('\', '/')
length = $_.Length
sha256 = (
Get-FileHash `
-LiteralPath $_.FullName `
-Algorithm SHA256
).Hash.ToLowerInvariant()
}
}
)

$msixName = "OpenClawGateway-$Architecture.msix"
$msixPath = Join-Path $directory $msixName
[IO.Compression.ZipFile]::CreateFromDirectory($staging, $msixPath)
Expand All @@ -227,6 +252,7 @@ function New-TestArtifact {
nodeRuntimeSha256 = $nodeRuntimeHash
mxcRuntimeVersion = $mxcRuntimeVersion
mxcRuntimeFiles = $mxcRuntimeFiles
sessionHostFiles = $sessionHostFiles
architecture = $Architecture
archive = $msixName
sha256 = $msixHash
Expand Down Expand Up @@ -464,6 +490,20 @@ try {
-RequestedRef 'v2026.8.2'
}

Reset-TestArtifacts
Update-TestMsix -Root $testRoot -Architecture x64 -Mutator {
param($Expanded)
Set-Content `
-LiteralPath (
Join-Path $Expanded 'session-host\x64\openclaw-session-host.exe'
) `
-Value 'tampered' `
-Encoding utf8
}
Assert-Fails `
-MessagePattern 'session host file is invalid' `
-Action { Invoke-PolicyValidation -Root $testRoot }

$x64MetadataPath = Join-Path $testRoot 'x64\msix-metadata.json'
$x64Metadata = Get-Content -LiteralPath $x64MetadataPath -Raw |
ConvertFrom-Json
Expand Down
73 changes: 73 additions & 0 deletions scripts/Test-SigningInputs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ foreach ($architecture in @('x64', 'arm64')) {
$metadata.nodeRuntimeSha256 -notmatch '^[0-9a-fA-F]{64}$' -or
[string]::IsNullOrWhiteSpace([string]$metadata.mxcRuntimeVersion) -or
@($metadata.mxcRuntimeFiles).Count -eq 0 -or
@($metadata.sessionHostFiles).Count -eq 0 -or
$metadata.architecture -ne $architecture -or
$metadata.archive -ne $msix.Name -or
$metadata.sha256 -notmatch '^[0-9a-fA-F]{64}$' -or
Expand Down Expand Up @@ -378,6 +379,78 @@ foreach ($architecture in @('x64', 'arm64')) {
throw "The embedded $architecture MXC runtime file set is invalid."
}

$expectedSessionHostPaths =
[System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::OrdinalIgnoreCase
)
$hasSessionHost = $false
foreach ($file in @($metadata.sessionHostFiles)) {
$relativePath = [string]$file.path
$segments = @($relativePath.Split('/'))
if (
[string]::IsNullOrWhiteSpace($relativePath) -or
$relativePath.StartsWith('/') -or
[IO.Path]::IsPathRooted($relativePath) -or
$relativePath.Contains('\') -or
$relativePath.Contains(':') -or
$segments -contains '' -or
$segments -contains '.' -or
$segments -contains '..' -or
$file.length -isnot [int64] -or
$file.length -lt 0 -or
$file.sha256 -notmatch '^[0-9a-fA-F]{64}$'
) {
throw "The embedded $architecture session host inventory is invalid."
}

$packagePath = "session-host/$architecture/$relativePath"
if (-not $expectedSessionHostPaths.Add($packagePath)) {
throw (
"The embedded $architecture session host inventory has " +
'duplicate paths.'
)
}

$entry = Get-PackageEntry `
-EntriesByPath $entriesByPath `
-Path $packagePath
if (
$entry.Length -ne $file.length -or
(Get-PackageEntrySha256 -Entry $entry) -ine $file.sha256
) {
throw (
"The embedded $architecture session host file is invalid: " +
$relativePath
)
}

if ($relativePath -ieq 'openclaw-session-host.exe') {
$hasSessionHost = $true
}
}

if (-not $hasSessionHost) {
throw "The embedded $architecture session host is incomplete."
}

$actualSessionHostPaths = @(
$entriesByPath.Keys |
Where-Object {
$_.StartsWith(
"session-host/$architecture/",
[StringComparison]::OrdinalIgnoreCase
)
}
)
if (
$actualSessionHostPaths.Count -ne $expectedSessionHostPaths.Count -or
@($actualSessionHostPaths | Where-Object {
-not $expectedSessionHostPaths.Contains($_)
}).Count -ne 0
) {
throw "The embedded $architecture session host file set is invalid."
}

[xml]$manifest = Read-ZipEntryText `
-EntriesByPath $entriesByPath `
-Path 'AppxManifest.xml'
Expand Down
7 changes: 7 additions & 0 deletions src/OpenClaw.Launcher/OpenClaw.Launcher.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
<MxcRuntimeArchitecture Condition="'$(RuntimeIdentifier)' == 'win-x64'">x64</MxcRuntimeArchitecture>
<MxcRuntimeArchitecture Condition="'$(RuntimeIdentifier)' == 'win-arm64'">arm64</MxcRuntimeArchitecture>
<MxcRuntimeContentDirectory>$(MSBuildThisFileDirectory)..\..\content\mxc\$(MxcRuntimeArchitecture)\</MxcRuntimeContentDirectory>
<SessionHostContentDirectory>$(MSBuildThisFileDirectory)..\..\content\session-host\$(MxcRuntimeArchitecture)\</SessionHostContentDirectory>
</PropertyGroup>

<ItemGroup Condition="'$(IncludePackagingContent)' == 'true'">
Expand All @@ -99,6 +100,10 @@
<Content Include="$(MxcRuntimeContentDirectory)**\*"
Link="mxc\$(MxcRuntimeArchitecture)\%(RecursiveDir)%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest" />
<Content Include="$(SessionHostContentDirectory)**\*"
Exclude="$(SessionHostContentDirectory)**\*.pdb;$(SessionHostContentDirectory)**\*.xml"
Link="session-host\$(MxcRuntimeArchitecture)\%(RecursiveDir)%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<Target Name="ValidatePackagingContent"
Expand All @@ -116,6 +121,8 @@
Text="Missing the staged MXC runtime. Run scripts\Get-MxcRuntime.ps1 -Architecture $(MxcRuntimeArchitecture)." />
<Error Condition="!Exists('$(MxcRuntimeContentDirectory)mxc-runtime.json')"
Text="Missing MXC runtime provenance. Run scripts\Get-MxcRuntime.ps1 -Architecture $(MxcRuntimeArchitecture)." />
<Error Condition="!Exists('$(SessionHostContentDirectory)openclaw-session-host.exe')"
Text="Missing the published isolated-session guest helper. scripts\Build-MSIX.ps1 stages it for $(MxcRuntimeArchitecture)." />
</Target>

<Target Name="FinalizeNativeAotPackagePayload"
Expand Down
9 changes: 9 additions & 0 deletions src/OpenClaw.SessionHost/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Runtime.InteropServices;

// Every P/Invoke in this assembly targets kernel32.dll or iphlpapi.dll, both of
// which always live in System32. Restricting the search path to System32
// assembly-wide prevents DLL planting: without it the loader would also probe
// the application directory and the current directory. That matters more here
// than in the launcher, because this helper runs inside the isolated session
// alongside whatever the agent has written to its own workspace.
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
32 changes: 32 additions & 0 deletions src/OpenClaw.SessionHost/OpenClaw.SessionHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NuGetAuditMode>all</NuGetAuditMode>
<AssemblyName>openclaw-session-host</AssemblyName>
<RootNamespace>OpenClaw.SessionHost</RootNamespace>
<Platforms>x64;arm64</Platforms>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>false</TargetLatestRuntimePatch>
<PublishAot Condition="'$(RuntimeIdentifier)' != ''">true</PublishAot>
<UseAppHost Condition="'$(RuntimeIdentifier)' != ''">true</UseAppHost>

<!--
This helper runs inside the isolated session, never as a packaged app,
so it takes no part in MSIX tooling.
-->
<WindowsPackageType>None</WindowsPackageType>
<EnableMSIXTooling>false</EnableMSIXTooling>
<DefineConstants>$(DefineConstants);OPENCLAW_SESSION_HOST</DefineConstants>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\OpenClaw.SessionProtocol\OpenClaw.SessionProtocol.csproj" />
<InternalsVisibleTo Include="OpenClaw.Launcher.Tests" />
</ItemGroup>

</Project>
Loading
Loading