fix(connection): bind WSL relay trust to installed package - #1201
fix(connection): bind WSL relay trust to installed package#1201shanselman wants to merge 2 commits into
Conversation
…heck fails Modern WSL distributes as an MSIX package (Program Files\WSL / WindowsApps), which doesn't embed per-file Authenticode/catalog signatures on its EXEs -- trust is established at the package level instead. This caused genuine Microsoft wslrelay.exe binaries to be rejected during setup's loopback listener provenance check with "WSL relay Authenticode verification failed (Unsigned)". Add a fallback in WindowsAuthenticodeVerifier: when the classic Authenticode check fails and the file is wslrelay.exe, corroborate trust via the installed AppX package instead. Requires an exact match on the known WSL package family name (MicrosoftCorporationII.WindowsSubsystemForLinux_8wekyb3d8bbwe, whose suffix is derived from the publisher's signing cert), a real SignatureKind, and a Microsoft publisher (reusing the existing HasMicrosoftPublisherIdentity check). Looked up via a PowerShell Get-AppxPackage shell-out, matching this file's existing pattern for invoking schtasks.exe/wsl.exe. The primary Authenticode/catalog check is unchanged and always consulted first; the fallback narrows to wslrelay.exe specifically and never weakens the trust bar for any other binary.
Require package-owned WindowsApps paths or a protected external relay whose ACLs and version bind it to the installed Microsoft WSL package. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 76619eef-66f0-45be-ad09-b753347eea6b
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs maintainer review before merge. Reviewed August 21, 2026, 7:15 PM ET / 23:15 UTC. ClawSweeper reviewWhat this changesThe PR adds a package-bound fallback for an unsigned WSL relay so credential forwarding trusts it only when the executable can be tied to an installed Microsoft WSL package. Merge readinessKeep this maintainer-authored draft open. The source hardening is coherent, but its author has paused landing until an official installation naturally exhibiting the unsigned relay condition distinguishes a verifier defect from a WSL signing or installation problem. Priority: P0 Review scores
Verification
Live VerificationCommand: Result: FAIL (failed) — execution before step 1 Assertions:
How this fits togetherThe connection layer proves the process behind a loopback gateway listener before shared or bootstrap credentials can cross that boundary. This change adds a fallback path from file-signature verification to package, filesystem, ACL, and version provenance checks. flowchart LR
A[Loopback listener] --> B[Relay process path]
B --> C[File signature check]
C -->|trusted| D[Authorize credential forwarding]
C -->|unsigned relay| E[Package and path provenance]
E -->|all checks match| D
E -->|missing or inconsistent| F[Reject listener]
Decision needed
Why: The author explicitly paused landing because the current proof injects the first failure at a credential-protection boundary, leaving the production cause unresolved. Before merge
Agent review detailsSecurityNone. Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Diagnose an affected official installation first, then land either a narrow signature-verifier repair or this guarded provenance fallback with proof that the natural failure path requires it. Do we have a high-confidence way to reproduce the issue? No. The submitted real-host proof injects the primary failure, and the maintainer reports that the available official relay succeeds at normal Authenticode verification. Is this the best way to solve the issue? Unclear. The provenance design is fail-closed and source-consistent, but the maintainer correctly requires diagnosis of the real unsigned condition before selecting it over a narrower verifier fix. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against ccd64bbb7d68. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (1 earlier review cycle)
|
|
We are pausing landing while we identify why the external relay appeared unsigned. This is not a rejection of the compatibility report. The upstream WSL release pipeline is designed to:
The current maintainer host has WSL 2.9.4, and its external
@larroy, could you please run the following read-only PowerShell and paste the output? It contains no credentials. You may redact usernames if one unexpectedly appears. $relay = 'C:\Program Files\WSL\wslrelay.exe'
$package = Get-AppxPackage -Name MicrosoftCorporationII.WindowsSubsystemForLinux |
Sort-Object Version -Descending |
Select-Object -First 1
Write-Output '=== WSL version ==='
wsl.exe --version
Write-Output '=== WSL package ==='
$package |
Select-Object Name, PackageFullName, PackageFamilyName, Version,
SignatureKind, Publisher, InstallLocation |
Format-List
Write-Output '=== Relay file ==='
Get-Item -LiteralPath $relay |
Select-Object FullName, Length, CreationTimeUtc, LastWriteTimeUtc,
Attributes, LinkType,
@{Name='FileVersion'; Expression={$_.VersionInfo.FileVersion}},
@{Name='ProductVersion'; Expression={$_.VersionInfo.ProductVersion}} |
Format-List
Write-Output '=== Relay hash ==='
Get-FileHash -LiteralPath $relay -Algorithm SHA256 | Format-List
Write-Output '=== PowerShell Authenticode ==='
$signature = Get-AuthenticodeSignature -LiteralPath $relay
$signature |
Select-Object Status, StatusMessage, SignatureType,
@{Name='SignerSubject'; Expression={$_.SignerCertificate.Subject}},
@{Name='SignerThumbprint'; Expression={$_.SignerCertificate.Thumbprint}} |
Format-List
Write-Output '=== Optional signtool verification ==='
$signtool = Get-Command signtool.exe -ErrorAction SilentlyContinue
if ($signtool) {
& $signtool.Source verify /pa /all /v $relay
} else {
Write-Output 'signtool.exe is not on PATH; skipped.'
}Please also tell us how this WSL build was installed or updated:
The key comparison is the existing OpenClaw error ( We will keep this PR as a draft investigation until that distinction is clear. If the exact Microsoft file is valid and only our API path disagrees, we will fix the verifier narrowly. If the file itself is unsigned, we should not transfer package trust to unrelated external bytes; we will document the repair/update path and report an upstream WSL signing defect when appropriate. |
Summary
This is a maintainer follow-up to #1177. It preserves Pedro's original compatibility fix as the first commit, then completes the package-to-file provenance checks requested in the security review.
The compatibility problem in #1177 is real. Some Store/MSIX WSL installations can report a genuine
wslrelay.exeas unsigned when inspected as an individual PE file, even though Windows trusted the MSIX package that installed it. The original PR correctly recognized that package trust can be relevant when per-file Authenticode is unavailable.The missing piece was proving that the exact relay process owning the local listener came from that trusted package. It is not sufficient to prove these two facts independently:
wslrelay.exeexists somewhere under a broadly accepted path.The trusted package must own, or must be authoritatively bound to, the exact relay bytes being trusted.
Credit
Pedro identified the Store/MSIX compatibility failure and implemented the initial package-level fallback in #1177. His original commit is preserved on this branch, including its authorship.
This follow-up does not replace that contribution. It builds on it by tightening the provenance contract at the credential-protection boundary.
Background: why Windows signing has two relevant layers
Traditional Windows desktop binaries are often authenticated at the file layer:
FileSignatureInfogives us a useful file-level answer for those cases.MSIX adds a package-level trust model:
That means a failed per-file Authenticode result is not always proof that the WSL installation is malicious. However, package trust is not automatically transferable to an arbitrary file with the right name. Package trust answers "Windows trusts this package." It does not, by itself, answer "this exact process image came from that package."
Why this is a security boundary
ManagedLocalGatewayPortProvenanceServiceis not merely displaying diagnostics. Its result helps decide whether OpenClaw may send shared or bootstrap credentials to a loopback listener.Loopback is a network location, not an identity. A different local process can bind the expected port. If we trust a relay only because:
then an unrelated relay can inherit the package's trust without being owned by it. That creates a path for local credential disclosure.
The rule for this boundary is therefore:
Trust decision after this change
1. Keep file-level Authenticode first
The normal
FileSignatureInfopath remains the primary check.If the relay is signed and trusted by Microsoft at the file or catalog layer, the result returns immediately. Package lookup and filesystem fallback logic are not consulted.
Tests now inject this primary result explicitly. This matters because the relay on the maintainer test host is already validly Microsoft-signed. Without injection, fallback tests silently exercise the fast path and can appear to pass without testing package fallback at all.
2. Limit fallback to
wslrelay.exeA failed signature on any other filename returns the original failure. Installing WSL does not widen trust for arbitrary executables.
3. Validate the installed package identity
The fallback queries
Get-AppxPackagefor the active WSL package and reads:The package must:
Nonesignature kindO=Microsoft Corporationpublisher identityThe PowerShell projection converts enum and version values to strings before JSON serialization. This was verified against the live
Get-AppxPackageobject becauseSignatureKindotherwise serializes in a shape the parser does not accept.4. Bind WindowsApps relays to the exact package location
For a WindowsApps layout:
Program Files\WindowsAppsrootWindowsAppsandProgram Filesis checked for reparse pointsThis closes the original disconnect between "the WSL package is trusted" and "this relay path belongs to that package."
5. Bind the external Program Files relay
Current WSL packages can also use:
C:\Program Files\WSL\wslrelay.exeThat file is outside the package's immutable WindowsApps install directory, so package-location containment cannot prove ownership. The external path receives a stricter composite proof:
Program Files\WSL\wslrelay.exeProgram Filesmust contain no reparse pointThe exact version check binds the protected external relay to the installed package generation. A mismatch or an unreadable version fails closed.
Windows ACL details worth preserving
Windows ACLs have several details that are easy to miss in security-sensitive code.
Ownership is not the same as access
A SYSTEM-owned file can still be writable by another principal if its DACL grants that access. The implementation checks both owner and effective allow rules.
Read access is expected
Users and application packages normally have read and execute access to files under
Program Files. That is safe for this decision. The policy rejects modification rights, not ordinary read and execute rights.Inherit-only ACEs do not apply to the current object
A standard
Program FilesACL can contain aCREATOR OWNERACE markedInheritOnly. It describes permissions to materialize on descendants. It does not grant the creator rights on the current directory.The policy ignores inherit-only ACEs while evaluating the current object. Descendants are each inspected separately, so effective inherited permissions are still checked where they apply.
A null DACL is not an empty DACL
This distinction is especially important:
High-level access-rule enumeration can look empty in both cases. The implementation reads the raw security descriptor and requires the
DiscretionaryAclPresentcontrol flag plus a non-null DACL before evaluating rules.Tests added or corrected
The focused tests now cover:
wslrelay.exefailures never invoke fallbackCREATOR OWNERACE does not create a false rejectionValidation
Executed from the isolated
C:\p1177maintainer checkout withOPENCLAW_REPO_ROOT=C:\p1177:One unrelated telemetry timing test failed once during closeout:
McpHttpServerTelemetryTests.ShutdownWhileWaitingForHandler_RecordsShutdownNotBusyOrTimeoutThe exact test passed immediately on rerun. The complete required sequence then passed.
Real behavior proof
The maintainer host has WSL 2.9.4 and a validly Microsoft-signed external relay, so normal execution returns through the primary Authenticode path. To exercise the fallback itself, the current-head assembly was invoked with an injected primary failure while retaining the production package lookup and production filesystem/ACL inspector.
This proves that the current-head package query, JSON projection, canonical external path, reparse walk, raw DACL handling, ownership/write policy, and version binding compose successfully on a real WSL installation.
Proof limitation
A host where the genuine relay naturally reports an unsigned file-level result was not available for this maintainer pass. The fallback was therefore forced only at the injected primary-result seam. All downstream evidence came from the real installed package and real relay filesystem state.
Review
The security-focused rubber-duck pass found no trust bypass. Its compatibility and path-ancestor suggestions were incorporated.
The structured ship review then found two Windows ACL edge cases:
CREATOR OWNERACEs must not be treated as effective rights on the current object.Both findings were fixed and covered by tests. The final structured review result was:
Relationship to #1177
This PR supersedes #1177 for landing purposes while preserving and crediting its original compatibility contribution. The main change is not the goal of the fallback, which remains correct. The change is the proof required before package trust can be transferred to the exact listener-owning relay at a credential boundary.