DocsFor Maintainers

Your Signing Workflow

Your mirror repository will be created from one of our templates and adapted to fit your project. You are always welcome to create pull requests to improve it, implement changes due to a changed build process on your end, change delivery methods to upload files to somewhere else, or make any other necessary adjustments to fit your workflow.

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 verify on 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.

Edit This Page on GitHub Maintained by the OSSign team, Uppsala, Sweden