Your Repository
After the initial review is approved you receive a link to your OSSign repository, usually github.com/OSSign/<owner>--<repo>. For acme/tool that is OSSign/acme--tool.
Your workflow will be located in .github/workflows/sign.yml within your OSSign repository.
We will generally create the first version of the workflow for you based on your existing project CI, but for complex build processes or special requirements, you may need to make adjustments to ensure it works correctly with OSSign.
| Marker | Meaning |
|---|---|
[DO NOT CHANGE] |
The server or the review depends on it. Keep it exactly as it is. |
[CHANGE-ME] |
Project specific. Replace it freely. |
[OPTIONAL] |
Can be removed without affecting the server. |
Below is a sample workflow for a simple project that builds and signs files outside of the build tool.
Inputs and Run Name
The server starts the workflow with four inputs and finds the run by its title. These inputs come from the trigger signal that you send to our server. Leave this block unchanged.
run-name: "Sign ${{ inputs.ref }} [${{ inputs.dispatch_id }}]"
on:
workflow_dispatch:
inputs:
ref: # branch or tag you asked for, e.g. v1.2.3
required: true
sha: # the commit the server pinned and reviewed
required: true
release_name: # name of the release to publish
required: true
dispatch_id: # id of the signing request
required: true
Repository Variables
The workflow reads three repository variables: SOURCE_REPO (your repository) and OSSIGN_SERVER are set when the repository is created, and OSSIGN_KEY is added by the OSSign team. You do not need to change anything here, but you can add variables here to use later on in your build process.
env:
SOURCE_REPO: ${{ vars.SOURCE_REPO }}
OSSIGN_SERVER: ${{ vars.OSSIGN_SERVER }}
OSSIGN_KEY: ${{ vars.OSSIGN_KEY }}
permissions:
contents: write # publishing the release
id-token: write # the OIDC token used for signing
The Sign Job
Everything normally happens in one job, sign. It runs in the signing environment, which is protected by a deployment protection rule: the job waits there until the review of your change is approved.
jobs:
sign:
runs-on: ubuntu-latest # [CHANGE-ME] windows-latest and macos-latest work too
environment: signing # [DO NOT CHANGE]
Pinned Checkout
The first step checks out the exact commit that was reviewed and fails if it gets anything else. Leave it unchanged. Whatever gets signed must be built from this commit.
- name: Check out the pinned source commit
run: |
git init src
cd src
git remote add origin "$SOURCE_REPO"
git fetch --depth 1 origin "${{ inputs.sha }}"
git checkout --detach FETCH_HEAD
test "$(git rev-parse HEAD)" = "${{ inputs.sha }}"
If your build needs submodules or more history, add those commands after the check, inside src.
Your Build
This is the project-specific part. Set up your toolchain and build in src, and put everything that should be signed in dist/. Example for a Go project:
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: Build executables
working-directory: src
run: |
mkdir -p ../dist
GOOS=windows GOARCH=amd64 go build -o ../dist/tool.exe ./cmd/tool
Keep the build to what your own release pipeline does. Use actions from well-known publishers, pinned by tag or commit.
Signing
For signing that happens outside of the build tools, the ossign/ossign action is used to sign files in-place. You can use one step per file type (pecoff for Windows Executables, msi for installers, etc.) or you can set fileType to "auto" to automatically set the right type based on the file extension.
- name: Sign executables
uses: ossign/ossign@1.3.1
with:
fileType: pecoff
inputFiles: dist/*.exe
config: |
{ "tokenType": "ossignServer",
"ossignServer": { "url": "${{ env.OSSIGN_SERVER }}", "key": "${{ env.OSSIGN_KEY }}",
"metadata": { "dispatch": "${{ inputs.dispatch_id }}", "sha": "${{ inputs.sha }}" } } }
Change fileType and inputFile/inputFiles. Keep the config block as it is. If you build an installer from your executables, sign the executables first, build the installer from the signed files, then sign the installer in a second step.
Verify and Publish
The template ends with three steps you should normally keep:
- Verify signatures runs
ossign verifyon every signed file, so nothing signed with a test certificate or by the wrong signer is published. - Fetch the review report downloads the report of the review that released this job. See Reviews and Reports.
- Publish release creates a release in your OSSign repository with the signed files and the report attached. Adjust the
files:list to match what you build.
Electron and Sign Hooks
Builders such as electron-builder sign every file they produce themselves. The installOnly command for the ossign action installs the ossign CLI and writes a small sign hook that electron-builder can call for each file:
- name: Install ossign
uses: ossign/ossign@1.3.1
with:
installOnly: true
config: |
{ "tokenType": "ossignServer", ... }
The hook is passed on the command line, so your own electron-builder configuration cannot override it. Set CSC_IDENTITY_AUTO_DISCOVERY=false so electron-builder does not look for a local certificate.
You can also implement the command directly in your electron-builder configuration if needed, just make it conditional on the presence of the OSSIGN_KEY environment variable.
- name: Build and sign with electron-builder
working-directory: src
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
GH_TOKEN: "" # keep electron-builder from publishing on its own
run: |
npx electron-builder --win --x64 --publish never \
-c.win.signtoolOptions.sign="${GITHUB_WORKSPACE//\\//}/ossign-sign.js" \
-c.win.signAndEditExecutable=true
electron-builder 26 and later use win.signtoolOptions.sign; earlier versions use win.sign. The template contains the full hook.
Testing Your Changes
Fork your OSSign repository and run the workflow there with workflow_dispatch, filling in the four inputs by hand (sha must be a real commit of your project). The fork cannot sign with the OSSign key, so for a test run replace the signing config with a local certificate you generate yourself:
openssl req -x509 -newkey rsa:2048 -nodes -days 30 -subj "/CN=Test Certificate" \
-keyout key.pem -out cert.pem
{ "tokenType": "certificate",
"certificate": { "certificate": "<contents of cert.pem>", "privateKey": "<contents of key.pem>" },
"timestampUrl": "http://timestamp.globalsign.com/tsa/advanced" }
Store it as a secret in your fork only, and drop the review report step, which needs the OSSign server. Revert both before you open the pull request.
Made changes?
Open a pull request against your OSSign repository with your workflow and let us know through your application or a new ticket that you are finished. We will review it, merge it, and run a test build to ensure everything works as expected.