Adding a Manual Approval Gate Before Code Signing
You will learn how to configure a manual approval gate in your release pipeline that pauses automated builds, alerts an engineering lead, and only invokes OneSigner to sign your binaries after an explicit human click.
Understanding the Architecture for Gated Signing
Automating your code signing pipeline with OneSigner and its OneSignTool client accelerates releases, but fully automated pipelines introduce risk if an unreviewed or broken build reaches production. Since your production signing keys reside on a physical USB hardware token plugged into a dedicated Windows machine running the OneSigner service, any request that reaches its signing endpoint with a valid token is signed immediately — OneSigner itself has no approval step. To prevent unvetted binaries from receiving a valid cryptographic signature, you must separate the build stage from the signing stage and enforce a manual gate between them.
This pattern requires three distinct phases in your CI/CD orchestrator, such as GitHub Actions, GitLab CI, or Jenkins. First, your build job compiles the application, runs tests, and uploads the unsigned binaries as pipeline artifacts. Second, the pipeline pauses execution and triggers an interactive review request assigned to a designated security or release manager. Third, once approved, a downstream job downloads the unsigned artifacts, runs OneSignTool against your OneSigner host, and publishes the signed executables. This ensures that the private key on your hardware token is never exposed to unreviewed code.
Keep in mind the operational requirement of your signing infrastructure: OneSigner relies on a single Windows machine that must stay powered on with the YubiKey, SafeNet, or ePass2003 token plugged into a USB port. Your CI/CD runner communicates with this machine either over your local network or securely via a custom public HTTPS domain routed through a Cloudflare Tunnel. If the target machine goes offline or the token is dislodged, the approval gate will succeed, but the subsequent signing call will fail until the hardware is restored.
Configuring the Build and Artifact Upload Stage
Your first pipeline stage must compile the software and package all target executables, DLLs, or MSI installers into a stable archive before any signing discussion begins. You should configure your build runner to output exact file hashes and store the build artifacts with a retention period that accommodates your review window. If you are using GitHub Actions, your initial workflow file will compile the application and publish the unsigned deliverables using the standard artifact upload action.
name: Release Pipeline
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Build Release Binaries
run: |
msbuild MyProduct.sln /p:Configuration=Release /p:Platform=x64
- name: Upload Unsigned Artifacts
uses: actions/upload-artifact@v4
with:
name: unsigned-payload
path: bin/Release/*.exe
At this point, no signing has occurred. The compiled binaries sit securely in your CI/CD provider's isolated storage bucket. If the build fails unit tests or static analysis, the pipeline halts immediately, protecting your downstream workflow from waste. To learn more about structuring automated signing workflows without relying on external cloud key storage, review how to automate code signing in CI/CD without cloud HSM.
Setting Up the Manual Approval Environment
Most modern CI/CD orchestrators provide native mechanisms for human intervention. In GitHub Actions, you can accomplish this by leveraging environments with required reviewers. When a workflow targets an environment configured with approval rules, the runner pauses execution at the boundary of that job and sends an email or dashboard notification to the authorized reviewers you specified.
To configure this in your repository settings, navigate to your repository settings, select Environments, and create a new environment named production-signing. Under protection rules, check the box for required reviewers and add the GitHub usernames or team aliases of your release managers. Once configured, you define a secondary job in your YAML workflow that declares environment: production-signing. The pipeline will refuse to proceed to the signing step until at least one of those designated users explicitly clicks the review button in the GitHub web interface.
During this pause, the reviewer can download the unsigned artifacts from the build job, inspect file properties, run local virus scans, or verify code changes against pull request logs. This human-in-the-loop verification satisfies internal compliance policies that forbid fully headless automated deployments of signed production binaries. For teams managing multiple signing tokens or scaling their release operations across different environments, consulting strategies on how to manage multiple signing workflows on a single OneSigner host helps structure your access tiers effectively.
Executing the Signing Call via OneSignTool
Once the reviewer approves the deployment in your CI/CD dashboard, the pipeline releases the waiting job. This downstream job runs on a runner that has network visibility to your OneSigner host machine or tunnel endpoint. It downloads the unsigned artifacts and invokes OneSignTool, OneSigner's AzureSignTool-compatible command-line client, which talks to the Azure Key Vault Proxy endpoint of your OneSigner service.
OneSignTool uses AzureSignTool's flags: -kvu is the URL of your OneSigner host (for example its Cloudflare Tunnel domain), -kva is the OneSigner admin token, and -kvc is the certificate name configured in the Azure Key Vault Proxy mapping. OneSignTool.exe must be available on the runner (for example, kept in your repository's tools folder). Here is how the execution step looks in your workflow configuration:
sign:
needs: build
runs-on: windows-latest
environment: production-signing
steps:
- name: Download Unsigned Artifacts
uses: actions/download-artifact@v4
with:
name: unsigned-payload
path: ./binaries
- name: Run OneSignTool to Sign Binaries
env:
ONESIGNER_TOKEN: ${{ secrets.ONESIGNER_ADMIN_TOKEN }}
ONESIGNER_URL: "https://sign.example.com"
run: |
.\tools\OneSignTool.exe sign -kvu "$env:ONESIGNER_URL" -kva "$env:ONESIGNER_TOKEN" -kvc your-certificate-name `
-fd sha256 -tr http://timestamp.digicert.com -td sha256 ./binaries/MyProduct.exe
When this step runs, OneSignTool computes the file digest on the runner and sends only that hash to your OneSigner service, which signs it with the private key on the attached YubiKey, SafeNet, or ePass2003 token. OneSignTool then writes the Authenticode signature and timestamp into the file on the runner — the binary itself never leaves your build machine.
If you encounter issues with signing tool parameters, timestamp server timeouts, or certificate selection on your hardware token, consult the OneSignTool CLI guide for the full flag reference.
Verifying the Signed Output and Uploading Final Assets
Never assume a signing operation succeeded without programmatic verification. Your pipeline must include a validation step immediately following the OneSignTool invocation to inspect the cryptographic signature, verify the signing certificate chain, and confirm that the timestamp authority responded correctly. On a Windows runner, you can use the built-in PowerShell Get-AuthenticodeSignature cmdlet to inspect the resulting file before publishing it to your distribution channels.
Add the following validation steps to the end of your signing job:
- name: Verify Signature
run: |
$sig = Get-AuthenticodeSignature -FilePath "./binaries/MyProduct.exe"
Write-Host "Signature Status: $($sig.Status)"
Write-Host "Signer Certificate: $($sig.SignerCertificate.Subject)"
if ($sig.Status -ne 'Valid') {
Write-Error "Signature verification failed with status: $($sig.Status)"
exit 1
}
- name: Publish Final Signed Release
uses: softprops/action-gh-release@v1
with:
files: ./binaries/MyProduct.exe
token: ${{ secrets.GITHUB_TOKEN }}
By enforcing this verification check, your pipeline catches common failures—such as an expired certificate, an unresponsive timestamp server, or an accidental PIN lock on the hardware token—before defective binaries reach your customers. If the verification script detects an invalid status, it exits with a non-zero code, failing the pipeline and alerting your engineering team instantly.
Next Steps
To implement this workflow in your own infrastructure, ensure your OneSigner service is running on its dedicated Windows machine with your hardware token connected, and set the admin token and the Azure Key Vault Proxy certificate mapping in the OneSigner config app. Explore the OneSigner license plans to select the tier that matches your team's deployment scale, or start a 30-day free trial to test OneSignTool in your staging pipeline today.