Skip to content

widget-creator: resilient Copilot CLI build + refresh vendored MXC binaries - #906

Closed
Chris Anderson (codemonkeychris) wants to merge 2 commits into
microsoft:mainfrom
codemonkeychris:andersonch/widget-creator-copilot-cli-and-mxc
Closed

Chris Anderson (codemonkeychris) wants to merge 2 commits into
microsoft:mainfrom
codemonkeychris:andersonch/widget-creator-copilot-cli-and-mxc

Conversation

@codemonkeychris

Copy link
Copy Markdown
Collaborator

Summary

Makes the widget-creator sample's GitHub.Copilot.SDK build-time CLI acquisition resilient (it previously hard-failed dotnet build/dotnet run on restricted networks) and refreshes the stale vendored MXC sandbox binaries.

Copilot CLI acquisition

The SDK's GitHub.Copilot.SDK.targets download copilot.exe via an unauthenticated MSBuild DownloadFile hardcoded to registry.npmjs.org, then <Error> on failure (MSB3923). That breaks the build for anyone offline / behind a proxy / on a TLS-intercepted network, even though the CLI is a runtime-only artifact. CI already skips it (CopilotSkipCliDownload=true); local dev did not.

  • build/Reactor.CopilotCli.targets (new, imported from Directory.Build.targets) — takes over acquisition at evaluation time (the only lever that gates the SDK's own targets) and obtains the CLI best-effort, in order:
    1. binary cached by a previous build
    2. COPILOT_CLI_PATH
    3. public registry DownloadFile (external / OSS)
    4. npm pack honoring the ambient authenticated .npmrc — the supported path for the internal Azure Artifacts npm feed (which requires auth DownloadFile can't provide)
    5. else the build succeeds without a bundled CLI and emits a REACTORCLI001 warning
    • Inert whenever CopilotSkipCliDownload=true, so all CI/official/nightly pipelines are unchanged — only previously-broken local builds are fixed.
  • CopilotSdkClient.cs — resolves a CLI at run time (bundled → COPILOT_CLI_PATH → installed) and passes RuntimeConnection.ForStdio(path:), because the SDK's default stdio transport only looks for a bundled binary and ignores COPILOT_CLI_PATH.
  • reactor-build-steps.yml — internal npm feed modernized to the pkgs.dev.azure.com domain (matching the NuGet feed's org/project; the preferred/required endpoint). Equivalent to the legacy github-private.pkgs.visualstudio.com form.
  • README — documents the internal-vs-external CLI-at-build behavior.

MXC binary refresh

  • Refreshes the vendored wxc-exec + helper binaries under tools/mxc/win-arm64/ from the latest MXC build, and updates the C-3 SHA-256 integrity pins in MxcBinaryManifest.cs to match (required, or the sandbox refuses to launch). The previously vendored wxc-exec was stale.

Validation

  • Build succeeds against the stock GitHub.Copilot.SDK 1.0.7 on a restricted network (graceful REACTORCLI001 skip, ~9s, no hang); COPILOT_CLI_PATH correctly bundles the CLI into the output.
  • The refreshed wxc-exec enforces the network policy: a default block config denies egress (curl → HTTPCODE:000), an internet-allowed config permits it (200) — verified by running wxc-exec directly, outside the app.

Notes / out of scope

  • Only win-arm64 MXC binaries + pins were refreshed (this machine's arch); win-x64 is unchanged.
  • A separate SDK/CLI protocol-compatibility fix (numeric timestamp converter + accepting protocol v2) lives in the upstream github/copilot-sdk repo and is not part of this PR; a local VersionOverride bridge was intentionally left out.

…naries

Make the GitHub.Copilot.SDK build-time CLI acquisition resilient and
internal/external-aware, and refresh the vendored MXC sandbox binaries.

Copilot CLI acquisition (build/Reactor.CopilotCli.targets, imported from
Directory.Build.targets):
- The SDK's targets download copilot.exe with an unauthenticated MSBuild
  DownloadFile hardcoded to registry.npmjs.org and <Error> on failure
  (MSB3923), breaking `dotnet build`/`run` on offline/proxied/TLS-intercepted
  networks. The CLI is a runtime-only artifact. CI skips it; local dev did not.
- New shared target takes over acquisition (evaluation-time, so it gates the
  SDK's own targets) and obtains the CLI best-effort: cached -> COPILOT_CLI_PATH
  -> public DownloadFile (external/OSS) -> `npm pack` honoring an authenticated
  .npmrc (internal Azure Artifacts feed) -> else build succeeds WITHOUT a bundled
  CLI plus a REACTORCLI001 warning. Inert when CopilotSkipCliDownload=true.
- CopilotSdkClient resolves a CLI at run time (bundled -> COPILOT_CLI_PATH ->
  installed) and passes RuntimeConnection.ForStdio(path:), since the SDK default
  only looks for a bundled binary and ignores COPILOT_CLI_PATH.
- Internal npm feed modernized to the pkgs.dev.azure.com domain (matching the
  NuGet feed's org/project); README documents the internal/external behavior.

MXC binaries (samples/apps/widget-creator/tools/mxc/win-arm64/*):
- Refresh the vendored wxc-exec + helper binaries from the latest mxc build and
  update the C-3 SHA-256 integrity pins in MxcBinaryManifest.cs to match. The
  previously vendored wxc-exec was stale; the refreshed binary enforces the
  network-block policy correctly (verified: default-block config denies egress,
  internet-allowed config permits it).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37ad0486-7427-4b45-8216-b11a2d06d00a
Copilot AI review requested due to automatic review settings July 21, 2026 19:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the reliability of Copilot-powered samples/tools by preventing GitHub.Copilot.SDK’s build-time copilot.exe acquisition from hard-failing on restricted/offline networks, and refreshes the integrity pins for the vendored MXC sandbox binaries used by the widget-creator sample.

Changes:

  • Add a repo-wide MSBuild override (build/Reactor.CopilotCli.targets, imported from Directory.Build.targets) that best-effort acquires/bundles the Copilot CLI (public download or npm pack) and otherwise emits REACTORCLI001 instead of failing the build.
  • Update widget-creator’s runtime Copilot client to explicitly pass a resolved CLI path to the SDK stdio transport when no bundled CLI is present.
  • Refresh win-arm64 MXC SHA-256 pins and modernize the internal npm registry URL in pipeline templates; document internal vs external behavior in the sample README.
Show a summary per file
File Description
samples/apps/widget-creator/Services/MxcBinaryManifest.cs Updates win-arm64 SHA-256 pins for refreshed vendored MXC binaries.
samples/apps/widget-creator/Services/CopilotSdkClient.cs Adds runtime CLI-path resolution and explicit stdio connection wiring for Copilot SDK.
samples/apps/widget-creator/README.md Documents internal vs external/offline Copilot CLI acquisition behavior.
Directory.Build.targets Imports the new resilient Copilot CLI acquisition targets late in the build.
build/Reactor.CopilotCli.targets Implements best-effort CLI acquisition (cache/env/public download/npm pack) and graceful warning fallback.
build/pipelines/templates/reactor-build-steps.yml Updates default internal npm registry endpoint to pkgs.dev.azure.com.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

samples/apps/widget-creator/Services/CopilotSdkClient.cs:114

  • Enumerating and executing copilot.exe from every directory on PATH expands the attack surface: when the bundled CLI is absent (e.g. offline build), any unintended/malicious copilot.exe earlier on PATH would be launched. Prefer requiring an explicit COPILOT_CLI_PATH (or checking only well-known install locations) instead of probing PATH.
        // Anything named copilot.exe on PATH.
        var path = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
        foreach (var dir in path.Split(Path.PathSeparator))
        {
            if (string.IsNullOrWhiteSpace(dir))
                continue;
            string full;
            try { full = Path.Combine(dir.Trim(), "copilot.exe"); }
            catch { continue; }
            yield return full;
        }
  • Files reviewed: 6/12 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines +72 to +78
/// <summary>
/// Resolves a Copilot CLI to spawn when the SDK's bundled binary is absent.
/// Returns <c>null</c> when the bundled CLI is present (let the SDK use it) or
/// when nothing could be found (let the SDK throw its descriptive error).
/// Order: bundled next to the app → <c>COPILOT_CLI_PATH</c> → a locally
/// installed Copilot CLI (GitHub CLI install, then <c>PATH</c>).
/// </summary>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in 8704eb1 — removed the PATH probing entirely (resolution is now bundled → COPILOT_CLI_PATH → the well-known per-user GitHub CLI install location only) and updated the doc comment to match, calling out that PATH is intentionally not scanned.

Comment thread samples/apps/widget-creator/README.md Outdated
Comment on lines +87 to +89
- **Offline / no feed** — the build still succeeds *without* a bundled CLI (a
`REACTORCLI001` warning is emitted). Set `COPILOT_CLI_PATH` to a local
`copilot.exe` to run generation, or just rely on `gh auth`'s CLI at run time.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reworded in 8704eb1 — the offline bullet now says you need a Copilot CLI at run time (bundled or via COPILOT_CLI_PATH) and explicitly notes that gh auth only supplies credentials, not a runnable copilot.exe.

- CopilotSdkClient.ResolveCliPath no longer scans PATH for copilot.exe (a
  malicious/unrelated copilot.exe earlier on PATH could otherwise be launched).
  It now resolves only the bundled CLI, COPILOT_CLI_PATH, and the well-known
  per-user GitHub CLI install location; updated the doc comment to match.
- README: reword the offline bullet so it no longer reads as if `gh auth`
  itself provides a runnable copilot.exe (it only supplies credentials).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37ad0486-7427-4b45-8216-b11a2d06d00a
Copilot AI review requested due to automatic review settings July 21, 2026 21:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 6/12 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment on lines +128 to +134
<ContentWithTargetPath Include="$(_ReactorCopilotCacheBin)"
TargetPath="runtimes\$(_CopilotRid)\native\$(_CopilotBinary)"
CopyToOutputDirectory="PreserveNewest" />
<ContentWithTargetPath Include="$(_ReactorCopilotRuntimeNode)"
TargetPath="runtimes\$(_CopilotRid)\native\$(_CopilotRuntimeLib)"
CopyToOutputDirectory="PreserveNewest"
Condition="Exists('$(_ReactorCopilotRuntimeNode)')" />
Alexandre Zollinger Chohfi (azchohfi) added a commit that referenced this pull request Nov 6, 2026
- Drop build/Reactor.CopilotCli.targets (+ its Directory.Build.targets import
  and the npm-feed pipeline tweak) carried over from #906. It forced
  CopilotSkipCliDownload=true and replaced GitHub.Copilot.SDK 1.0.28's
  SHA256SUMS-verified download from github/copilot-cli releases with an
  unverified npm tarball, and ignored the SDK's CopilotCliReleaseBaseUrl /
  COPILOT_CLI_DOWNLOAD_BASE_URL / CopilotCliBinaryPath hooks. Keep
  CopilotSdkClient.ResolveCliPath as the sample's run-time fallback and
  document the SDK hooks in the README instead.
- Legacy policy migration fails closed: policies with settings MXC 1.0 can't
  express (e.g. network.blockedHosts, leastPrivilege: true) are reported as
  invalid and the widget falls back to the default policy instead of having
  those restrictions silently dropped.
- Do not relaunch a build whose self-test the sandbox could not run (incl. a
  failed containment check); save it and explain instead.
- Treat self-test output in the repair prompt as untrusted data
  (SanitizeUntrusted + WIDGET_OUTPUT markers), like crash output.
- Reject blank CIDRs and route SDK argument validation errors to the
  launch-failure path.
- Only offer "local network" together with internet access (MXC's
  AppContainer fallback rejects inbound-only private network access);
  "No network access" now also closes ingress.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Alexandre Zollinger Chohfi (azchohfi) added a commit that referenced this pull request Nov 7, 2026
) (#1469)

* widget-creator: resilient Copilot CLI build

Make the GitHub.Copilot.SDK build-time CLI acquisition resilient and
internal/external-aware.

Copilot CLI acquisition (build/Reactor.CopilotCli.targets, imported from
Directory.Build.targets):
- The SDK's targets download copilot.exe with an unauthenticated MSBuild
  DownloadFile hardcoded to registry.npmjs.org and <Error> on failure
  (MSB3923), breaking `dotnet build`/`run` on offline/proxied/TLS-intercepted
  networks. The CLI is a runtime-only artifact. CI skips it; local dev did not.
- New shared target takes over acquisition (evaluation-time, so it gates the
  SDK's own targets) and obtains the CLI best-effort: cached -> COPILOT_CLI_PATH
  -> public DownloadFile (external/OSS) -> `npm pack` honoring an authenticated
  .npmrc (internal Azure Artifacts feed) -> else build succeeds WITHOUT a bundled
  CLI plus a REACTORCLI001 warning. Inert when CopilotSkipCliDownload=true.
- CopilotSdkClient resolves a CLI at run time (bundled -> COPILOT_CLI_PATH ->
  installed) and passes RuntimeConnection.ForStdio(path:), since the SDK default
  only looks for a bundled binary and ignores COPILOT_CLI_PATH.
- Internal npm feed modernized to the pkgs.dev.azure.com domain (matching the
  NuGet feed's org/project); README documents the internal/external behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37ad0486-7427-4b45-8216-b11a2d06d00a

* Address CR: drop PATH probing for copilot.exe; clarify README

- CopilotSdkClient.ResolveCliPath no longer scans PATH for copilot.exe (a
  malicious/unrelated copilot.exe earlier on PATH could otherwise be launched).
  It now resolves only the bundled CLI, COPILOT_CLI_PATH, and the well-known
  per-user GitHub CLI install location; updated the doc comment to match.
- README: reword the offline bullet so it no longer reads as if `gh auth`
  itself provides a runnable copilot.exe (it only supplies credentials).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37ad0486-7427-4b45-8216-b11a2d06d00a

* widget-creator: run widgets on the stable MXC SDK 1.0.1

Replace the vendored prerelease wxc-exec binaries (MXC v0.7.0-rc1) with the
stable, Microsoft-signed Microsoft.Mxc.Sdk 1.0.1 NuGet package and its
in-process Microsoft.Mxc.Sdk.V1 API.

- MxcSandbox launches widgets with MxcContainer.SpawnAsync on the
  ProcessContainer backend, streams stdout/stderr, maps MXC timeouts and
  cancellation to WIDGET_SANDBOX_TIMEOUT, and reports MxcException as an
  explicit launch error (never a widget crash).
- MxcPolicy: per-widget policy JSON now mirrors MXC 1.0 field names
  (filesystem / network.egress+ingress / ui / processContainer.capabilities),
  is validated as a closed document, migrates legacy 0.6.0-alpha policies on
  read, and maps to the typed ContainerRequest.
- MxcContainmentVerifier: strict BaseContainer mode uses MxcContainer.Probe;
  the out-of-band token check inspects the SDK-reported contained PID.
- Permissions dialog: drop the least-privilege toggle (not exposed by MXC
  1.0), map "Allow local network" to ingress allow, add write/all clipboard
  levels, surface advanced egress rules as "Custom".
- Self-test runs that fail to launch in the sandbox are no longer sent to
  Copilot for repair.
- Delete tools/mxc/** (x64 + arm64 binaries), MxcBinaryManifest hash pins,
  the csproj copy item, and the WIDGET_CREATOR_WXC_EXEC / _MXC_BIN /
  _USE_LOCAL_MXC / _MXC_ROOT overrides; update the README.

Supersedes #906 (its Copilot CLI build-resilience commits are kept).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* widget-creator: address code-quality review comments

- Narrow generic catch clauses in MxcSandbox: native-runtime probes catch
  only native load/MXC failures, verifier awaits catch only
  OperationCanceledException, and stream-closer cleanup catches only
  MxcException/IOException/ObjectDisposedException.
- Use Path.Join (never drops earlier segments) and FirstOrDefault in
  CopilotSdkClient.ResolveCliPath; use Where in MxcPolicy legacy migration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* widget-creator: address PR review findings

- Drop build/Reactor.CopilotCli.targets (+ its Directory.Build.targets import
  and the npm-feed pipeline tweak) carried over from #906. It forced
  CopilotSkipCliDownload=true and replaced GitHub.Copilot.SDK 1.0.28's
  SHA256SUMS-verified download from github/copilot-cli releases with an
  unverified npm tarball, and ignored the SDK's CopilotCliReleaseBaseUrl /
  COPILOT_CLI_DOWNLOAD_BASE_URL / CopilotCliBinaryPath hooks. Keep
  CopilotSdkClient.ResolveCliPath as the sample's run-time fallback and
  document the SDK hooks in the README instead.
- Legacy policy migration fails closed: policies with settings MXC 1.0 can't
  express (e.g. network.blockedHosts, leastPrivilege: true) are reported as
  invalid and the widget falls back to the default policy instead of having
  those restrictions silently dropped.
- Do not relaunch a build whose self-test the sandbox could not run (incl. a
  failed containment check); save it and explain instead.
- Treat self-test output in the repair prompt as untrusted data
  (SanitizeUntrusted + WIDGET_OUTPUT markers), like crash output.
- Reject blank CIDRs and route SDK argument validation errors to the
  launch-failure path.
- Only offer "local network" together with internet access (MXC's
  AppContainer fallback rejects inbound-only private network access);
  "No network access" now also closes ingress.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 1

- Verify and bind the MXC native runtime before the first SDK call (C-3): claim the SDK's DllImport resolver, load only the app's packaged mxc_ffi.dll after pinning it and its companion natives and requiring a Microsoft Authenticode signature; fail closed otherwise.

- Report unexpected MXC output-stream failures as sandbox errors instead of silently truncating the log.

- Validate egress CIDRs (MXC 1.0 syntax, host bits, except containment) and port selectors (endPort needs port, ascending, no ports for ICMP).

- 'No network access' also clears egress.deny rules.

- Surface a visible run-log warning when a stored policy can't be applied and the default replaces it.

- Use Where in UnsupportedLegacyFields.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 2

- Log MXC warnings appended during wait/finalization, not only spawn warnings.

- Resolve the Copilot CLI from the WinGet GitHub.Copilot command links (user and machine scope) as fixed install locations; still no PATH scan.

- LINQ nits in MxcPolicy.IsValidPort and MxcNativeRuntime; ExactSpelling on the WinTrust imports.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 3

- Verify the MXC native runtime's signer chain with CERT_CHAIN_POLICY_MICROSOFT_ROOT (public-key check, 2011 application root, no test roots) on the chain WinVerifyTrust validated, instead of matching the root's display name.

- Reject explicit empty 'to' / 'ports' selector arrays, which MXC 1.0 refuses.

- Treat an MxcException from WaitAsync as a sandbox failure: kill, clean up the verifier and pumps, and return LaunchError so the widget is still saved.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 4

- Legacy policy migration fails closed on malformed known fields and non-object sections, so a denial (e.g. deniedPaths, ui.disable) can't be dropped while grants carry over.

- Filter the containment-verifier catch explicitly (best effort; cancellation propagates).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 5

- Require every packaged MXC companion native (fail closed if one is missing).

- Reject MXC natives reached through a symbolic link, junction or redirected path: the pinned file's canonical path must equal the path that will be loaded or launched.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 6

- Use TryGetPropertyValue instead of ContainsKey + indexer in the legacy-shape checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 7

- Reject explicit JSON nulls anywhere in a widget policy: an unset value maps to its default, so e.g. an allow rule's "to": null would match every destination and a null except/deniedPaths would drop a restriction.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 8

- On cancellation/timeout, await the containment verifier before classifying: a containment failure it confirmed is reported as a sandbox failure, never as a repairable timeout.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 9

- Document the C-3 trust boundary: the app's own folder must not be writable by untrusted users. The .NET runtime probes it for bare-named native libraries (verified: a planted crypt32/advapi32/kernel32 is loaded by the runtime itself) and such a writer could replace WidgetCreator.dll anyway, so the gate covers substitution from outside that folder and tampering/linking/removal of the shipped MXC files.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 10

- Reject duplicate keys when parsing a widget policy (AllowDuplicateProperties = false) and treat ArgumentException from the JSON DOM as an invalid policy, so a duplicate key shows the invalid-policy message instead of faulting rendering.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 11

- Dispose the MXC stream closers used during the drain grace period: each StandardOutputCloser/StandardErrorCloser access returns an independent native handle (per the SDK docs), so they were previously left for the finalizer.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address review round 12

- Parse IPv6 CIDRs as strictly as MXC's Rust parser: hex groups and ':' only, with an optional strict trailing dotted-quad. IPAddress.TryParse alone tolerated brackets, ports, zone IDs and short embedded IPv4, which MXC then rejected at launch. Cross-checked against real MXC 1.0.1 spawns.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Chris Anderson <andersonch@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 37ad0486-7427-4b45-8216-b11a2d06d00a
@azchohfi

Copy link
Copy Markdown
Collaborator

Superseded by #1469 (merged as af4ecc4), which carries this PR's commits forward onto current main and moves Widget Creator to the stable, public MXC release (Microsoft.Mxc.Sdk 1.0.1 from nuget.org) instead of the vendored prerelease wxc-exec binaries.

  • Kept: the run-time Copilot CLI fallback (CopilotSdkClient.ResolveCliPath, no PATH probing).
  • Dropped: the build/Reactor.CopilotCli.targets npm download. GitHub.Copilot.SDK 1.0.28 already downloads the CLI from github/copilot-cli releases and verifies it against SHA256SUMS.txt, and it supports mirrors.
  • Dropped: the arm64 binary refresh. The vendored binaries were removed entirely.

Closing in favor of #1469. Thanks, Chris!

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