Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0256824
azure artifact signing draft
zateutsch Jun 28, 2026
2157a7c
auth headache
zateutsch Jun 28, 2026
bf01691
Merge branch 'main' into zt/359-azure-signing
zateutsch Jun 28, 2026
1697d9d
local pr review findings
zateutsch Jun 30, 2026
bd324f2
Merge branch 'main' into zt/359-azure-signing
zateutsch Jun 30, 2026
49fa1b2
fix codeql warning
zateutsch Jun 30, 2026
ffb8cd3
Merge branch 'zt/359-azure-signing' of https://github.com/microsoft/w…
zateutsch Jun 30, 2026
f6ed393
Merge branch 'main' into zt/359-azure-signing
zateutsch Jul 5, 2026
04d5744
address copilot feedback and add profile type to profile options lsit
zateutsch Jul 6, 2026
01e3a54
Merge branch 'main' into zt/359-azure-signing
zateutsch Jul 8, 2026
9349af0
Address PR review findings for Azure Trusted Signing
zateutsch Jul 22, 2026
7ec59eb
Address second-pass PR review findings for Azure Trusted Signing
zateutsch Jul 22, 2026
97df5c3
Harden Azure signing: cwd-subtree az.cmd guard, pagination cycle guar…
zateutsch Jul 22, 2026
0cbe49c
Wrap az-login token retry, clean metadata temp on failure, add proces…
zateutsch Jul 22, 2026
4e7d493
Validate ARM pagination host, scan all where.exe hits, add coverage
zateutsch Jul 22, 2026
344bd59
Seam+test where.exe candidate selection, add az-sign to npm README
zateutsch Jul 22, 2026
16088f3
Address PR #595 review comments for Azure signing
zateutsch Jul 23, 2026
d7b0ee4
Merge remote-tracking branch 'origin/main' into zt/359-azure-signing
zateutsch Jul 23, 2026
ee3e36c
Address PR review comments: role names, CI path optimization, dispose…
zateutsch Jul 23, 2026
df0492c
Address PR review: metadata endpoint validation, profile checks, docs…
zateutsch Jul 23, 2026
f6c44c8
Address PR review: least-privilege CI guidance, portal wording, npm w…
zateutsch Jul 23, 2026
d2b289c
Use Path.Join instead of Path.Combine in remaining flagged locations
zateutsch Jul 23, 2026
7344daa
az-sign: direct-GET profile validation, validated-path CLI auth, cach…
invalid-email-address Jul 23, 2026
fa8a18b
az-sign: verify dlib integrity, login on metadata-file path, doc all …
invalid-email-address Jul 23, 2026
272fe59
Use filtered exception catches; document VC++ Redistributable prerequ…
invalid-email-address Jul 23, 2026
d8c7892
Use ternary assignment for choices and url branches
invalid-email-address Jul 23, 2026
53375b6
Potential fix for pull request finding 'Generic catch clause'
zateutsch Jul 23, 2026
8722991
Potential fix for pull request finding 'Generic catch clause'
zateutsch Jul 23, 2026
7cf646c
Honor existing Azure CLI session in non-interactive environments
invalid-email-address Jul 23, 2026
12e7017
Fix malformed XML doc comment on IsValidTenantId
invalid-email-address Jul 23, 2026
d3607c0
Harden signtool working directory, fix ARM pagination and deadlock test
invalid-email-address Jul 23, 2026
c48a280
Harden signtool dlib az resolution and add ProcessRunner .cmd regress…
invalid-email-address Jul 23, 2026
d824916
Clarify that az-sign metadata mode only skips discovery/selection, no…
invalid-email-address Jul 23, 2026
5a64f0c
Filter ProcessRunnerTests Cleanup catch to expected filesystem except…
invalid-email-address Jul 23, 2026
fa966c1
Propagate cancellation from the Trusted Signing dlib install
invalid-email-address Jul 23, 2026
f53af3f
Validate full metadata shape before any Azure work in az-sign
invalid-email-address Jul 23, 2026
6478fb0
Fix flaky update notice gating assertion
Copilot Jul 23, 2026
32a8d0b
Dispose ARM responses, drain reads on cancel, document timestamp URL
invalid-email-address Jul 23, 2026
7c58bf2
Potential fix for pull request finding
zateutsch Jul 23, 2026
2678976
Use model-based selection prompts and fix AzureAuthService formatting
invalid-email-address Jul 23, 2026
ddc39ac
address nikola PR review
zateutsch Jul 24, 2026
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
17 changes: 16 additions & 1 deletion .claude/agents/winapp.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ Does the project already have an appxmanifest.xml?
│ └─ Is the exe separate from your app code? (Electron, sparse package testing)
│ └─ winapp create-debug-identity <exe-path> (registers sparse package)
├─ Need to sign an existing MSIX or exe?
│ └─ winapp sign <file> <cert>
│ ├─ With a local dev/CA certificate (PFX)?
│ │ └─ winapp sign <file> <cert>
│ └─ With Azure Trusted Signing (cloud-managed identity, no local PFX)?
│ └─ winapp az-sign <file>
└─ Need to run a Windows SDK tool directly (makeappx, signtool, makepri)?
└─ winapp tool <toolname> <args>

Expand Down Expand Up @@ -169,6 +172,18 @@ Want to inspect or interact with a running app's UI?
- `--password <pwd>` — certificate password
- `--timestamp <url>` — timestamp server URL (recommended for production to stay valid after cert expires)

### `winapp az-sign <file-path>`
**Purpose:** Code-sign an exe, MSIX, or MSIX bundle using Azure Trusted Signing (a cloud-managed signing identity — no local PFX).
**When to use:** For production signing when the certificate is managed in Azure rather than as a local PFX file. Works in CI/CD and interactively.
**Key options:**
- `--subscription <id>` (`-s`) — Azure subscription ID (prompts if omitted and multiple exist)
- `--resource-group <rg>` (`-r`) — resource group to narrow down signing accounts
- `--account <name>` — signing account name (requires `--resource-group`)
- `--profile <name>` (`-p`) — certificate profile name (requires `--account`)
- `--metadata-file <path>` (`-m`) — reuse an existing `metadata.json`, skipping resource discovery and identity selection (authentication may still prompt for a tenant or `az login`)
**Auth:** Uses `DefaultAzureCredential`. For CI/CD set `AZURE_TENANT_ID`/`AZURE_CLIENT_ID`/`AZURE_CLIENT_SECRET` (or OIDC/managed identity); interactively falls back to `az login`.
**Requires:** An Azure Code Signing account + certificate profile, and the Code Signing Certificate Profile Signer role. Also needs two machine-wide x64 runtimes that winapp does not auto-install (it downloads the raw NuGet package, not the client-tools installer): the x64 .NET 8+ runtime (the signing library is a managed assembly loaded by `signtool.exe`) and the x64 Visual C++ Redistributable. Plus SignTool 10.0.22621.755+. A dlib load failure (e.g. `0xc000007b`) usually means a missing runtime — most often the VC++ Redistributable.

### `winapp manifest generate [directory]`
**Purpose:** Create an `appxmanifest.xml` without full project setup.
**When to use:** When you only need a manifest and image assets, without SDK installation or config file creation.
Expand Down
44 changes: 44 additions & 0 deletions .claude/skills/winapp-signing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Use this skill when:
- **Generating a development certificate** for local MSIX signing and testing
- **Installing (trusting) a certificate** on a machine so MSIX packages can be installed
- **Signing an MSIX package or executable** for distribution
- **Signing with Azure Trusted Signing** (cloud-managed signing identity) via `winapp az-sign`

## Prerequisites

Expand Down Expand Up @@ -80,6 +81,27 @@ When packaging multiple architectures into an `.msixbundle`, only the bundle nee

Note: The `package` command can sign automatically when you pass `--cert`, so you often don't need `sign` separately.

### Sign with Azure Trusted Signing (cloud signing)

For production-grade signing without managing a PFX file, use `winapp az-sign` to sign with [Azure Trusted Signing](https://learn.microsoft.com/azure/trusted-signing/). The signing identity (certificate) is managed in Azure, so no private key ever lives on the machine.

```powershell
# Interactive: discover subscription, account, and profile (prompts for any not provided)
winapp az-sign ./app.msix

# Fully specified — no prompting (ideal for CI/CD)
winapp az-sign ./app.msix --subscription <sub-id> --resource-group <rg> --account <account> --profile <profile>

# Reuse an existing metadata.json (skips resource discovery and identity selection; authentication may still be interactive)
winapp az-sign ./app.msix --metadata-file ./metadata.json
```

**Authentication:** `az-sign` uses Azure's standard credential chain (`DefaultAzureCredential`). In CI/CD, set `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, and `AZURE_CLIENT_SECRET` (or GitHub Actions OIDC / managed identity). An existing Azure CLI session (`az login`, including the `azure/login` GitHub Action) is also honored in any environment. Only when no credentials are found *and* the session is interactive will `az-sign` launch `az login` for you.

**Prerequisites:** An Azure Code Signing account and certificate profile (created in the Azure portal after identity validation), plus a role assignment granting your identity the **Code Signing Certificate Profile Signer** role. Signing also requires two machine-wide x64 runtimes that winapp does **not** install for you (it downloads the raw NuGet signing package, not the official client-tools installer): the **x64 .NET 8+ runtime** (the signing library is a managed assembly loaded by `signtool.exe`; winapp's self-contained runtime does not satisfy it) and the **x64 Visual C++ Redistributable** (https://aka.ms/vs/17/release/vc_redist.x64.exe). Also requires **SignTool 10.0.22621.755 or later**. If signing fails while *loading* the dlib (e.g. `0xc000007b` or a missing-DLL error) rather than during authentication, install the missing runtime — most often the VC++ Redistributable.

> **Least-privilege CI:** Auto-discovery (listing subscriptions, resource groups, accounts, and profiles) needs read access at a parent scope. To avoid *every* collection-listing call, pass all four of `--subscription`, `--resource-group`, `--account`, and `--profile`: `az-sign` then validates the account and profile with direct resource reads (a GET on each named resource) instead of enumerating the parent collection, so a principal scoped to just that account and profile is sufficient. Omitting any one of them re-introduces a listing call — for example, leaving out `--subscription` makes `az-sign` list the subscriptions your identity can access — which a narrowly-scoped principal may not be permitted to do. A principal scoped only to a single certificate profile can skip validation entirely by passing a pre-generated `--metadata-file` (which specifies the account endpoint and profile directly).

## Recommended workflow

1. **Generate cert** — `winapp cert generate` (auto-infers publisher from manifest)
Expand Down Expand Up @@ -108,6 +130,8 @@ Note: The `package` command can sign automatically when you pass `--cert`, so yo
| "Certificate not trusted" | Cert not installed on machine | `winapp cert install ./devcert.pfx` (admin) |
| "Certificate file already exists" | `devcert.pfx` already present | Use `--if-exists overwrite` or `--if-exists skip` |
| Signature invalid after time passes | No timestamp used during signing | Re-sign with `--timestamp http://timestamp.digicert.com` |
| `az-sign` fails with "No credentials found" | No Azure auth in environment | Run `az login`, or set `AZURE_TENANT_ID`/`AZURE_CLIENT_ID`/`AZURE_CLIENT_SECRET` for CI/CD |
| `az-sign` "No Trusted Signing accounts found" | No account in the subscription/resource group | Create a Trusted Signing account and certificate profile in the Azure portal |


## Command Reference
Expand Down Expand Up @@ -181,3 +205,23 @@ Code-sign an MSIX package or executable. Example: winapp sign ./app.msix ./devce
|--------|-------------|---------|
| `--password` | Certificate password | `password` |
| `--timestamp` | Timestamp server URL | (none) |

### `winapp az-sign`

Code-sign a file using Azure Trusted Signing. Signs executables, MSIX packages, or MSIX bundles using a cloud-managed signing identity. Example: winapp az-sign ./app.msix

#### Arguments
<!-- auto-generated from cli-schema.json -->
| Argument | Required | Description |
|----------|----------|-------------|
| `<file-path>` | Yes | Path to the file to sign (exe, msix, or msixbundle) |

#### Options
<!-- auto-generated from cli-schema.json -->
| Option | Description | Default |
|--------|-------------|---------|
| `--account` | Signing account name. Must be used with --resource-group | (none) |
| `--metadata-file` | Path to an existing metadata.json file. Skips resource discovery and account/profile selection prompts and signs using this file directly. A non-interactive Azure credential should already be available; the CLI can otherwise fall back to an interactive tenant prompt or 'az login', but the npm programmatic API is always non-interactive and fails instead of prompting. | (none) |
| `--profile` | Certificate profile name. Must be used with --account | (none) |
| `--resource-group` | Resource group to narrow down signing accounts | (none) |
| `--subscription` | Azure subscription ID to use. If not provided and multiple subscriptions exist, you will be prompted. | (none) |
17 changes: 16 additions & 1 deletion .github/plugin/agents/winapp.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ Does the project already have an appxmanifest.xml?
│ └─ Is the exe separate from your app code? (Electron, sparse package testing)
│ └─ winapp create-debug-identity <exe-path> (registers sparse package)
├─ Need to sign an existing MSIX or exe?
│ └─ winapp sign <file> <cert>
│ ├─ With a local dev/CA certificate (PFX)?
│ │ └─ winapp sign <file> <cert>
│ └─ With Azure Trusted Signing (cloud-managed identity, no local PFX)?
│ └─ winapp az-sign <file>
└─ Need to run a Windows SDK tool directly (makeappx, signtool, makepri)?
└─ winapp tool <toolname> <args>

Expand Down Expand Up @@ -170,6 +173,18 @@ Want to inspect or interact with a running app's UI?
- `--password <pwd>` — certificate password
- `--timestamp <url>` — timestamp server URL (recommended for production to stay valid after cert expires)

### `winapp az-sign <file-path>`
**Purpose:** Code-sign an exe, MSIX, or MSIX bundle using Azure Trusted Signing (a cloud-managed signing identity — no local PFX).
**When to use:** For production signing when the certificate is managed in Azure rather than as a local PFX file. Works in CI/CD and interactively.
**Key options:**
- `--subscription <id>` (`-s`) — Azure subscription ID (prompts if omitted and multiple exist)
- `--resource-group <rg>` (`-r`) — resource group to narrow down signing accounts
- `--account <name>` — signing account name (requires `--resource-group`)
- `--profile <name>` (`-p`) — certificate profile name (requires `--account`)
- `--metadata-file <path>` (`-m`) — reuse an existing `metadata.json`, skipping resource discovery and identity selection (authentication may still prompt for a tenant or `az login`)
**Auth:** Uses `DefaultAzureCredential`. For CI/CD set `AZURE_TENANT_ID`/`AZURE_CLIENT_ID`/`AZURE_CLIENT_SECRET` (or OIDC/managed identity); interactively falls back to `az login`.
**Requires:** An Azure Code Signing account + certificate profile, and the Code Signing Certificate Profile Signer role. Also needs two machine-wide x64 runtimes that winapp does not auto-install (it downloads the raw NuGet package, not the client-tools installer): the x64 .NET 8+ runtime (the signing library is a managed assembly loaded by `signtool.exe`) and the x64 Visual C++ Redistributable. Plus SignTool 10.0.22621.755+. A dlib load failure (e.g. `0xc000007b`) usually means a missing runtime — most often the VC++ Redistributable.

### `winapp manifest generate [directory]`
**Purpose:** Create an `appxmanifest.xml` without full project setup.
**When to use:** When you only need a manifest and image assets, without SDK installation or config file creation.
Expand Down
44 changes: 44 additions & 0 deletions .github/plugin/skills/winapp-cli/signing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Use this skill when:
- **Generating a development certificate** for local MSIX signing and testing
- **Installing (trusting) a certificate** on a machine so MSIX packages can be installed
- **Signing an MSIX package or executable** for distribution
- **Signing with Azure Trusted Signing** (cloud-managed signing identity) via `winapp az-sign`

## Prerequisites

Expand Down Expand Up @@ -80,6 +81,27 @@ When packaging multiple architectures into an `.msixbundle`, only the bundle nee

Note: The `package` command can sign automatically when you pass `--cert`, so you often don't need `sign` separately.

### Sign with Azure Trusted Signing (cloud signing)

For production-grade signing without managing a PFX file, use `winapp az-sign` to sign with [Azure Trusted Signing](https://learn.microsoft.com/azure/trusted-signing/). The signing identity (certificate) is managed in Azure, so no private key ever lives on the machine.

```powershell
# Interactive: discover subscription, account, and profile (prompts for any not provided)
winapp az-sign ./app.msix

# Fully specified — no prompting (ideal for CI/CD)
winapp az-sign ./app.msix --subscription <sub-id> --resource-group <rg> --account <account> --profile <profile>

# Reuse an existing metadata.json (skips resource discovery and identity selection; authentication may still be interactive)
winapp az-sign ./app.msix --metadata-file ./metadata.json
```

**Authentication:** `az-sign` uses Azure's standard credential chain (`DefaultAzureCredential`). In CI/CD, set `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, and `AZURE_CLIENT_SECRET` (or GitHub Actions OIDC / managed identity). An existing Azure CLI session (`az login`, including the `azure/login` GitHub Action) is also honored in any environment. Only when no credentials are found *and* the session is interactive will `az-sign` launch `az login` for you.

**Prerequisites:** An Azure Code Signing account and certificate profile (created in the Azure portal after identity validation), plus a role assignment granting your identity the **Code Signing Certificate Profile Signer** role. Signing also requires two machine-wide x64 runtimes that winapp does **not** install for you (it downloads the raw NuGet signing package, not the official client-tools installer): the **x64 .NET 8+ runtime** (the signing library is a managed assembly loaded by `signtool.exe`; winapp's self-contained runtime does not satisfy it) and the **x64 Visual C++ Redistributable** (https://aka.ms/vs/17/release/vc_redist.x64.exe). Also requires **SignTool 10.0.22621.755 or later**. If signing fails while *loading* the dlib (e.g. `0xc000007b` or a missing-DLL error) rather than during authentication, install the missing runtime — most often the VC++ Redistributable.

> **Least-privilege CI:** Auto-discovery (listing subscriptions, resource groups, accounts, and profiles) needs read access at a parent scope. To avoid *every* collection-listing call, pass all four of `--subscription`, `--resource-group`, `--account`, and `--profile`: `az-sign` then validates the account and profile with direct resource reads (a GET on each named resource) instead of enumerating the parent collection, so a principal scoped to just that account and profile is sufficient. Omitting any one of them re-introduces a listing call — for example, leaving out `--subscription` makes `az-sign` list the subscriptions your identity can access — which a narrowly-scoped principal may not be permitted to do. A principal scoped only to a single certificate profile can skip validation entirely by passing a pre-generated `--metadata-file` (which specifies the account endpoint and profile directly).

## Recommended workflow

1. **Generate cert** — `winapp cert generate` (auto-infers publisher from manifest)
Expand Down Expand Up @@ -108,6 +130,8 @@ Note: The `package` command can sign automatically when you pass `--cert`, so yo
| "Certificate not trusted" | Cert not installed on machine | `winapp cert install ./devcert.pfx` (admin) |
| "Certificate file already exists" | `devcert.pfx` already present | Use `--if-exists overwrite` or `--if-exists skip` |
| Signature invalid after time passes | No timestamp used during signing | Re-sign with `--timestamp http://timestamp.digicert.com` |
| `az-sign` fails with "No credentials found" | No Azure auth in environment | Run `az login`, or set `AZURE_TENANT_ID`/`AZURE_CLIENT_ID`/`AZURE_CLIENT_SECRET` for CI/CD |
| `az-sign` "No Trusted Signing accounts found" | No account in the subscription/resource group | Create a Trusted Signing account and certificate profile in the Azure portal |


## Command Reference
Expand Down Expand Up @@ -181,3 +205,23 @@ Code-sign an MSIX package or executable. Example: winapp sign ./app.msix ./devce
|--------|-------------|---------|
| `--password` | Certificate password | `password` |
| `--timestamp` | Timestamp server URL | (none) |

### `winapp az-sign`

Code-sign a file using Azure Trusted Signing. Signs executables, MSIX packages, or MSIX bundles using a cloud-managed signing identity. Example: winapp az-sign ./app.msix

#### Arguments
<!-- auto-generated from cli-schema.json -->
| Argument | Required | Description |
|----------|----------|-------------|
| `<file-path>` | Yes | Path to the file to sign (exe, msix, or msixbundle) |

#### Options
<!-- auto-generated from cli-schema.json -->
| Option | Description | Default |
|--------|-------------|---------|
| `--account` | Signing account name. Must be used with --resource-group | (none) |
| `--metadata-file` | Path to an existing metadata.json file. Skips resource discovery and account/profile selection prompts and signs using this file directly. A non-interactive Azure credential should already be available; the CLI can otherwise fall back to an interactive tenant prompt or 'az login', but the npm programmatic API is always non-interactive and fails instead of prompting. | (none) |
| `--profile` | Certificate profile name. Must be used with --account | (none) |
| `--resource-group` | Resource group to narrow down signing accounts | (none) |
| `--subscription` | Azure subscription ID to use. If not provided and multiple subscriptions exist, you will be prompted. | (none) |
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ See also: [Debugging Guide](./docs/debugging.md) — choosing between `winapp ru

- [`cert`](./docs/usage.md#cert) - Generate and install development certificates
- [`sign`](./docs/usage.md#sign) - Sign MSIX packages and executables
- [`az-sign`](./docs/usage.md#az-sign) - Sign with Azure Trusted Signing (cloud-managed identity, no local PFX)
Comment thread
zateutsch marked this conversation as resolved.
Resolved
- [`create-external-catalog`](./docs/usage.md#create-external-catalog) - Generate CodeIntegrityExternal.cat for TrustedLaunch sparse packages

**Development Tools:**
Expand Down
Loading
Loading