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
26 changes: 20 additions & 6 deletions .github/workflows/release-vsix-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -493,12 +493,20 @@ jobs:
<!-- hve-core:verification:start -->
## Verifying Downloads

All release assets include [Sigstore](https://www.sigstore.dev/) build-provenance
attestations generated by GitHub Actions. Verify any downloaded asset with the
GitHub CLI:
The VSIX and OpenVEX document are build-provenance subjects. The SBOMs are
predicate payloads, the `.sigstore.json` and `.intoto.jsonl` files carry the
VSIX verification material, and `dependency-diff.md` is informational.

Resolve the expected source from the exact Stable tag before verifying subjects:

```bash
gh attestation verify <file>.vsix --repo microsoft/hve-core
TAG='v<version>'
SOURCE_SHA=$(gh api "repos/microsoft/hve-core/commits/$TAG" --jq '.sha')

gh attestation verify <file>.vsix --repo microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/extension-provenance-signer.yml \
--signer-digest 3a09401536cef0c4559db1aa64b7d1010638fd67 \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG"
```

## VEX (Vulnerability Exploitability eXchange)
Expand All @@ -510,8 +518,14 @@ jobs:
populated, use it alongside the SBOM to filter false positives in vulnerability scanners:

```bash
# Verify VEX attestation
gh attestation verify hve-core.openvex.json --repo microsoft/hve-core
gh attestation verify hve-core.openvex.json --repo microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/vex-attest.yml \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG"

gh attestation verify dependencies.spdx.json --repo microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/vex-attest.yml \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG" \
--predicate-type https://openvex.dev/ns/v0.2.0
```

See [SECURITY.md](https://github.com/microsoft/hve-core/blob/main/SECURITY.md)
Expand Down
21 changes: 16 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,20 @@ HVE Core publishes cryptographically attested assets under exact channel tags:

```bash
# Stable VSIX
TAG='v<version>'
SOURCE_SHA=$(gh api "repos/microsoft/hve-core/commits/$TAG" --jq '.sha')
gh attestation verify hve-core-<version>.vsix -R microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/extension-provenance-signer.yml \
--signer-digest 3a09401536cef0c4559db1aa64b7d1010638fd67
--signer-workflow microsoft/hve-core/.github/workflows/extension-provenance-signer.yml \
--signer-digest 3a09401536cef0c4559db1aa64b7d1010638fd67 \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG"

# PreRelease VSIX
TAG='prerelease-v<version>'
SOURCE_SHA=$(gh api "repos/microsoft/hve-core/commits/$TAG" --jq '.sha')
gh attestation verify hve-core-<version>.vsix -R microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/extension-provenance-signer.yml \
--signer-digest 3a09401536cef0c4559db1aa64b7d1010638fd67
--signer-workflow microsoft/hve-core/.github/workflows/extension-provenance-signer.yml \
--signer-digest 3a09401536cef0c4559db1aa64b7d1010638fd67 \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG"
```

The GitHub Release is the canonical verification surface for SLSA and Sigstore
Expand Down Expand Up @@ -182,11 +188,16 @@ build provenance for the OpenVEX file as a subject, and an OpenVEX predicate
over `dependencies.spdx.json` as a subject.

```bash
TAG='v<version>'
SOURCE_SHA=$(gh api "repos/microsoft/hve-core/commits/$TAG" --jq '.sha')

gh attestation verify hve-core.openvex.json -R microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/vex-attest.yml
--signer-workflow microsoft/hve-core/.github/workflows/vex-attest.yml \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG"

gh attestation verify dependencies.spdx.json -R microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/vex-attest.yml \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG" \
--predicate-type https://openvex.dev/ns/v0.2.0
```

Expand Down
7 changes: 6 additions & 1 deletion docs/security/sbom-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,22 @@ Verify SPDX predicates through their primary artifact subjects:

```bash
# Stable VSIX
TAG='v<version>'
SOURCE_SHA=$(gh api "repos/microsoft/hve-core/commits/$TAG" --jq '.sha')
gh attestation verify hve-core-<version>.vsix -R microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/extension-provenance-signer.yml \
--signer-digest 3a09401536cef0c4559db1aa64b7d1010638fd67 \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG" \
--predicate-type https://spdx.dev/Document/v2.3

# PreRelease VSIX
TAG='prerelease-v<version>'
SOURCE_SHA=$(gh api "repos/microsoft/hve-core/commits/$TAG" --jq '.sha')
gh attestation verify hve-core-<version>.vsix -R microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/extension-provenance-signer.yml \
--signer-digest 3a09401536cef0c4559db1aa64b7d1010638fd67 \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG" \
--predicate-type https://spdx.dev/Document/v2.3

```

These commands can match both the per-artifact and dependency SBOM
Expand Down
11 changes: 9 additions & 2 deletions docs/security/vex-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: VEX Verification
description: Download, verify, and interpret the OpenVEX vulnerability exploitability document published with Stable HVE Core releases
sidebar_position: 4
author: Microsoft
ms.date: 2026-08-09
ms.date: 2026-09-04
ms.topic: how-to
keywords:
- VEX
Expand Down Expand Up @@ -63,18 +63,25 @@ gh release download v<version> -R microsoft/hve-core \

A Stable release publishes two Sigstore attestations for the VEX document:

```bash
TAG='v<version>'
SOURCE_SHA=$(gh api "repos/microsoft/hve-core/commits/$TAG" --jq '.sha')
```

1. **Provenance of the VEX document**, so you can confirm the file itself came from the official pipeline. Verify it against the dedicated reusable VEX attestation workflow:

```bash
gh attestation verify hve-core.openvex.json -R microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/vex-attest.yml
--signer-workflow microsoft/hve-core/.github/workflows/vex-attest.yml \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG"
```

2. **VEX bound to the dependency SBOM**, where the VEX document is the in-toto *predicate* over the SBOM *subject* (the OpenVEX "encapsulating format"). This lets VEX-aware tooling resolve the exploitability assessment for the product's component inventory:

```bash
gh attestation verify dependencies.spdx.json -R microsoft/hve-core \
--signer-workflow microsoft/hve-core/.github/workflows/vex-attest.yml \
--source-digest "$SOURCE_SHA" --source-ref "refs/tags/$TAG" \
--predicate-type https://openvex.dev/ns/v0.2.0
```

Expand Down
40 changes: 38 additions & 2 deletions scripts/release/Invoke-ProvenanceVerification.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,21 @@ function Invoke-ProvenanceVerification {
throw 'Provenance invariant failed: release artifacts must contain exactly one VSIX'
}

$IsStable = $ReleaseTag.StartsWith('v', [System.StringComparison]::Ordinal)
$VexArtifacts = @($artifacts | Where-Object { $_.Name -ieq 'hve-core.openvex.json' })
$DependencySbomArtifacts = @($artifacts | Where-Object { $_.Name -ieq 'dependencies.spdx.json' })
if ($IsStable) {
if ($VexArtifacts.Count -ne 1 -or $VexArtifacts[0].Name -cne 'hve-core.openvex.json') {
throw 'Provenance invariant failed: Stable release artifacts must contain exactly one OpenVEX document'
}
if ($DependencySbomArtifacts.Count -ne 1 -or $DependencySbomArtifacts[0].Name -cne 'dependencies.spdx.json') {
throw 'Provenance invariant failed: Stable release artifacts must contain exactly one dependency SBOM'
}
}
elseif ($VexArtifacts.Count -ne 0) {
throw 'Provenance invariant failed: PreRelease artifacts must not contain an OpenVEX document'
}

$VsixArtifact = $VsixArtifacts[0]
$SigstorePath = "$($VsixArtifact.FullName).sigstore.json"
$IntotoPath = "$($VsixArtifact.FullName).intoto.jsonl"
Expand Down Expand Up @@ -492,8 +507,29 @@ function Invoke-ProvenanceVerification {
elseif ($artifact.Name -like '*.zip') {
$null = Invoke-ExternalCommand -Command 'gh' -Arguments @('attestation', 'verify', $fullPath, '--repo', $Repository)
}
elseif ($artifact.Name -eq 'hve-core.openvex.json') {
$null = Invoke-ExternalCommand -Command 'gh' -Arguments @('attestation', 'verify', $fullPath, '--repo', $Repository, '--signer-workflow', "$Repository/.github/workflows/vex-attest.yml", '--predicate-type', 'https://openvex.dev/ns/v0.2.0')
elseif ($artifact.Name -ceq 'hve-core.openvex.json') {
$null = Invoke-ExternalCommand -Command 'gh' -Arguments @(
'attestation', 'verify', $fullPath,
'--repo', $Repository,
'--signer-workflow', "$Repository/.github/workflows/vex-attest.yml",
'--signer-digest', $ExpectedSourceSha,
'--source-digest', $ExpectedSourceSha,
'--source-ref', "refs/tags/$ReleaseTag",
'--predicate-type', 'https://slsa.dev/provenance/v1',
'--deny-self-hosted-runners'
)
}
elseif ($IsStable -and $artifact.Name -ceq 'dependencies.spdx.json') {
$null = Invoke-ExternalCommand -Command 'gh' -Arguments @(
'attestation', 'verify', $fullPath,
'--repo', $Repository,
'--signer-workflow', "$Repository/.github/workflows/vex-attest.yml",
'--signer-digest', $ExpectedSourceSha,
'--source-digest', $ExpectedSourceSha,
'--source-ref', "refs/tags/$ReleaseTag",
'--predicate-type', 'https://openvex.dev/ns/v0.2.0',
'--deny-self-hosted-runners'
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1537,11 +1537,19 @@ Describe 'Retained release reconciliation and OpenVEX' -Tag 'Unit', 'ReleaseReco
[string[]]@($document['jobs']['publish-release']['needs']) | Should -Contain 'vex-attest'
}

It 'Documents VSIX and VEX verification without a plugin ZIP instruction' {
It 'Documents source-bound subject-specific verification without a plugin ZIP instruction' {
$notes = [string](Get-NamedJobStep -Document (Get-WorkflowDocument -Name 'release-vsix-publish.yml') `
-JobName 'append-verification-notes' -StepName 'Append verification section to release notes')['run']
$notes | Should -Match 'gh attestation verify <file>\.vsix'
$notes | Should -Match 'gh attestation verify hve-core\.openvex\.json'
$notes | Should -Match 'gh attestation verify dependencies\.spdx\.json'
$notes | Should -Match 'SOURCE_SHA=\$\(gh api'
@([regex]::Matches($notes, '--source-digest')) | Should -HaveCount 3
@([regex]::Matches($notes, '--source-ref')) | Should -HaveCount 3
$notes | Should -Match 'extension-provenance-signer\.yml'
$notes | Should -Match '3a09401536cef0c4559db1aa64b7d1010638fd67'
$notes | Should -Match 'SBOMs are\s+predicate payloads'
$notes | Should -Not -Match 'All release assets include.*build-provenance'
$notes | Should -Not -Match '<file>\.zip'
}

Expand Down
69 changes: 64 additions & 5 deletions scripts/tests/release/Invoke-ProvenanceVerification.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ BeforeAll {
$script:Repository = 'microsoft/hve-core'
$script:SourceSha = '0123456789abcdef0123456789abcdef01234567'
$script:SignerSha = 'abcdef0123456789abcdef0123456789abcdef01'
$script:ReleaseTag = 'v3.4.0'
$script:ReleaseTag = 'prerelease-v3.3.0'

function New-VerifiedResultFixture {
<#
Expand Down Expand Up @@ -354,26 +354,85 @@ Describe 'Invoke-ProvenanceVerification' -Tag 'Unit' {
} | Should -Throw $Error
}

It 'Retains ZIP and OpenVEX cryptographic verification without parsing sidecars' {
It 'Retains ZIP verification without parsing unrelated metadata' {
$ZipPath = Join-Path $script:tempDir 'hve-core-plugin.zip'
$VexPath = Join-Path $script:tempDir 'hve-core.openvex.json'
$UnrelatedPath = Join-Path $script:tempDir 'unrelated.metadata.json'
Set-Content -LiteralPath $ZipPath -Value 'zip bytes'
Set-Content -LiteralPath $VexPath -Value '{"sanitized":true}'
Set-Content -LiteralPath $UnrelatedPath -Value '{not-trusted}'

{
Invoke-ProvenanceVerification -ArtifactDirectory $script:tempDir -Repository $script:Repository -ExpectedSourceSha $script:SourceSha -ExpectedSignerSha $script:SignerSha -ReleaseTag $script:ReleaseTag
} | Should -Not -Throw

Should -Invoke Invoke-ExternalCommand -Times 3 -Exactly
Should -Invoke Invoke-ExternalCommand -Times 2 -Exactly
Should -Invoke Invoke-ExternalCommand -Times 1 -Exactly -ParameterFilter {
$Command -eq 'gh' -and $Arguments[2] -eq [System.IO.Path]::GetFullPath($ZipPath) -and
($Arguments -join ' ') -eq "attestation verify $([System.IO.Path]::GetFullPath($ZipPath)) --repo $script:Repository"
}
}

It 'Rejects an OpenVEX document in a PreRelease artifact set' {
Set-Content -LiteralPath (Join-Path $script:tempDir 'hve-core.openvex.json') -Value '{"sanitized":true}'

{
Invoke-ProvenanceVerification -ArtifactDirectory $script:tempDir -Repository $script:Repository -ExpectedSourceSha $script:SourceSha -ExpectedSignerSha $script:SignerSha -ReleaseTag $script:ReleaseTag
} | Should -Throw '*PreRelease artifacts must not contain an OpenVEX document*'
Should -Invoke Invoke-ExternalCommand -Times 0 -Exactly
}

It 'Rejects a Stable artifact set missing <Missing>' -ForEach @(
@{ Missing = 'OpenVEX document'; Add = 'dependencies.spdx.json' }
@{ Missing = 'dependency SBOM'; Add = 'hve-core.openvex.json' }
) {
Set-Content -LiteralPath (Join-Path $script:tempDir $Add) -Value '{"sanitized":true}'

{
Invoke-ProvenanceVerification -ArtifactDirectory $script:tempDir -Repository $script:Repository -ExpectedSourceSha $script:SourceSha -ExpectedSignerSha $script:SignerSha -ReleaseTag 'v3.4.0'
} | Should -Throw "*$Missing*"
Should -Invoke Invoke-ExternalCommand -Times 0 -Exactly
}

It 'Rejects a Stable artifact set with a duplicate or noncanonical <Subject>' -ForEach @(
@{ Subject = 'OpenVEX document'; Canonical = 'hve-core.openvex.json'; Variant = 'HVE-Core.OpenVEX.json' }
@{ Subject = 'dependency SBOM'; Canonical = 'dependencies.spdx.json'; Variant = 'Dependencies.SPDX.json' }
) {
Set-Content -LiteralPath (Join-Path $script:tempDir 'hve-core.openvex.json') -Value '{"sanitized":true}'
Set-Content -LiteralPath (Join-Path $script:tempDir 'dependencies.spdx.json') -Value '{"spdxVersion":"SPDX-2.3"}'
Rename-Item -LiteralPath (Join-Path $script:tempDir $Canonical) -NewName $Variant

{
Invoke-ProvenanceVerification -ArtifactDirectory $script:tempDir -Repository $script:Repository -ExpectedSourceSha $script:SourceSha -ExpectedSignerSha $script:SignerSha -ReleaseTag 'v3.4.0'
} | Should -Throw "*exactly one $Subject*"
Should -Invoke Invoke-ExternalCommand -Times 0 -Exactly
}

It 'Verifies Stable VEX provenance and OpenVEX over the dependency SBOM separately' {
$VexPath = Join-Path $script:tempDir 'hve-core.openvex.json'
$DependencySbomPath = Join-Path $script:tempDir 'dependencies.spdx.json'
Set-Content -LiteralPath $VexPath -Value '{"sanitized":true}'
Set-Content -LiteralPath $DependencySbomPath -Value '{"spdxVersion":"SPDX-2.3"}'

$script:ReleaseTag = 'v3.4.0'
$script:Fixture = New-VerifiedResultFixture -VsixPath $script:vsixPath
$script:VerificationJson = ConvertTo-Json -InputObject @($script:Fixture) -Depth 30 -Compress
$script:Fixture.attestation | ConvertTo-Json -Depth 30 -Compress |
Set-Content -LiteralPath $script:SigstorePath -Encoding utf8NoBOM
$script:Fixture.attestation.dsseEnvelope | ConvertTo-Json -Depth 30 -Compress |
Set-Content -LiteralPath $script:IntotoPath -Encoding utf8NoBOM

Invoke-ProvenanceVerification -ArtifactDirectory $script:tempDir -Repository $script:Repository -ExpectedSourceSha $script:SourceSha -ExpectedSignerSha $script:SignerSha -ReleaseTag $script:ReleaseTag

Should -Invoke Invoke-ExternalCommand -Times 3 -Exactly
Should -Invoke Invoke-ExternalCommand -Times 1 -Exactly -ParameterFilter {
$Command -eq 'gh' -and $Arguments[2] -eq [System.IO.Path]::GetFullPath($VexPath) -and
$Arguments -contains "$script:Repository/.github/workflows/vex-attest.yml" -and
$Arguments -contains 'https://slsa.dev/provenance/v1' -and
$Arguments -contains $script:SourceSha -and
$Arguments -contains "refs/tags/$script:ReleaseTag"
}
Should -Invoke Invoke-ExternalCommand -Times 1 -Exactly -ParameterFilter {
$Command -eq 'gh' -and $Arguments[2] -eq [System.IO.Path]::GetFullPath($DependencySbomPath) -and
$Arguments -contains "$script:Repository/.github/workflows/vex-attest.yml" -and
$Arguments -contains 'https://openvex.dev/ns/v0.2.0'
}
}
Expand Down
Loading