Skip to content

Build, Sign, and Verify the MIME Validation Plugin

This document covers building, signing, and verifying FsiAgentGovernance.Plugins.dll — the Dataverse pre-validation plugin shipped from src/ValidateMimeTypePlugin.cs.

For initial registration in Power Platform see docs/build-instructions.md (legacy Visual Studio walkthrough) and the Step 2: Register Dataverse Plugin section of flow-configuration.md.

What ships with each release

Starting with the version that resolves Issue #38, every tagged GitHub Release attaches:

Filename Purpose
FsiAgentGovernance.Plugins.dll The compiled plugin assembly
FsiAgentGovernance.Plugins.dll.sha256 SHA-256 hash of the DLL
FsiAgentGovernance.Plugins.dll.sig Sigstore signature (legacy split format)
FsiAgentGovernance.Plugins.dll.pem Sigstore signing certificate (legacy split format)
FsiAgentGovernance.Plugins.dll.bundle Sigstore signing bundle (preferred)

Plus the existing source tarball, SBOMs, root SHA256SUMS, and a build provenance attestation generated by GitHub Actions.

Strong-name signing. The published DLL is not strong-name signed. Dataverse sandbox isolation requires strong-name signed assemblies, so customers must merge transitive dependencies and apply their own .snk before registration. See Local production build below.

Build locally (CI parity)

Prerequisites:

  • .NET 8 SDK (build host requires dotnet; cross-targets net462)
  • Windows is recommended for full parity with CI; on Linux/macOS, building net462 requires Mono and is not officially supported
cd mime-type-restrictions\src
dotnet restore ValidateMimeTypePlugin.csproj
dotnet build ValidateMimeTypePlugin.csproj `
  --configuration Release `
  --nologo `
  /p:TreatWarningsAsErrors=true

Output: bin\Release\net462\FsiAgentGovernance.Plugins.dll.

Local production build (with strong-name + ILRepack merge)

Production builds for Dataverse sandbox isolation require:

  1. A strong-name key (.snk). Generate once per environment:
sn -k FsiAgentGovernance.Plugins.snk

Store this file outside the repo (it is .gitignored). Treat it as a long-lived signing secret — losing it means existing registered plugins must be re-deployed under a new public key.

  1. Build with strong-name signing enabled:
dotnet build ValidateMimeTypePlugin.csproj `
  --configuration Release `
  --nologo `
  /p:SignAssembly=true `
  /p:AssemblyOriginatorKeyFile=$env:USERPROFILE\keys\FsiAgentGovernance.Plugins.snk
  1. Merge transitive dependencies with ILRepack. Sandbox isolation will not load System.Text.Json or any other NuGet dependency from disk; they must be merged into the plugin DLL.

Install ILRepack (choco install ilrepack or download from https://github.com/gluck/il-repack/releases) and run:

$bin = "bin\Release\net462"
ilrepack /out:"$bin\FsiAgentGovernance.Plugins.merged.dll" `
  "$bin\FsiAgentGovernance.Plugins.dll" `
  "$bin\System.Text.Json.dll" `
  "$bin\System.Buffers.dll" `
  "$bin\System.Memory.dll" `
  "$bin\System.Numerics.Vectors.dll" `
  "$bin\System.Runtime.CompilerServices.Unsafe.dll" `
  "$bin\System.Text.Encodings.Web.dll" `
  "$bin\System.Threading.Tasks.Extensions.dll" `
  "$bin\System.ValueTuple.dll" `
  /keyfile:"$env:USERPROFILE\keys\FsiAgentGovernance.Plugins.snk"

The exact list of transitive DLLs may shift with System.Text.Json minor releases — include every System.*.dll that ends up in $bin.

  1. Verify the merged DLL:
sn -vf $bin\FsiAgentGovernance.Plugins.merged.dll

Then register FsiAgentGovernance.Plugins.merged.dll via the Plugin Registration Tool.

CI build (ci-dotnet.yml)

.github/workflows/ci-dotnet.yml runs on every push and PR that touches mime-type-restrictions/src/**. It performs dotnet restore + dotnet build for both Debug and Release configurations on windows-latest.

The Release artifact is uploaded for inspection and short-retention (14 days). It is not strong-name signed — that is the responsibility of the deployer.

CodeQL (codeql.yml)

The CodeQL workflow analyses both Python and C#. The C# analysis runs on windows-latest, builds the plugin via dotnet build, and uploads results to the GitHub Security tab.

Release signing (release.yml)

When a v*.*.* tag is pushed, the build-plugin job:

  1. Builds the Release configuration of ValidateMimeTypePlugin.csproj.
  2. Computes a SHA-256 hash of the output DLL.
  3. Signs the DLL with cosign keyless OIDC (Sigstore). No long-lived keys are stored in the repository or in repository secrets.
  4. Generates a build provenance attestation for the DLL.
  5. Uploads the DLL plus signing artifacts as a workflow artifact.

The downstream release job downloads those artifacts, includes them in the root SHA256SUMS, and attaches them to the GitHub Release.

Verifying a published DLL

Customers who deploy the published binary should verify it before registering it in Dataverse.

1. Verify the SHA-256 hash

$expected = (Get-Content FsiAgentGovernance.Plugins.dll.sha256).Split(' ')[0]
$actual = (Get-FileHash -Algorithm SHA256 FsiAgentGovernance.Plugins.dll).Hash.ToLower()
if ($expected -ne $actual) { throw "SHA-256 mismatch" } else { "OK: $actual" }

2. Verify the cosign signature (Sigstore bundle, preferred)

cosign verify-blob \
  --bundle FsiAgentGovernance.Plugins.dll.bundle \
  --certificate-identity-regexp '^https://github.com/judeper/FSI-AgentGov-Solutions/\.github/workflows/release\.yml@refs/tags/v.*$' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  FsiAgentGovernance.Plugins.dll

3. Verify the cosign signature (legacy split format)

cosign verify-blob \
  --signature FsiAgentGovernance.Plugins.dll.sig \
  --certificate FsiAgentGovernance.Plugins.dll.pem \
  --certificate-identity-regexp '^https://github.com/judeper/FSI-AgentGov-Solutions/\.github/workflows/release\.yml@refs/tags/v.*$' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  FsiAgentGovernance.Plugins.dll

4. Verify the GitHub build provenance attestation

gh attestation verify FsiAgentGovernance.Plugins.dll \
  --repo judeper/FSI-AgentGov-Solutions

If any of those checks fail, do not register the DLL. File a security issue per SECURITY.md.

Trust boundary notes

  • The published DLL is signed by GitHub Actions running on judeper/FSI-AgentGov-Solutions. Trust depends on the integrity of that repository's release workflow and the vX.Y.Z tag.
  • Strong-name signing is separate from cosign signing. cosign attests who built the DLL; strong-name attests which assembly identity it loads as in the Dataverse sandbox.
  • Customers who require an even stronger trust path may run the Local production build steps above and host their own signed DLL — the cosign-signed reference DLL is then used only as a build artifact for comparison, not as the deployed binary.

See THREAT-MODEL.md "Plugin trust boundary" for the full threat model.