Skip to content

GitHub Actions

Gate release artifacts (Linux runner + Docker)

.github/workflows/pe-gate.yml
name: pe-gate
on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v4
      - run: msbuild MyApp.sln /p:Configuration=Release   # your build
      - uses: actions/upload-artifact@v4
        with: { name: dist, path: dist/ }

  inspect:
    needs: build
    runs-on: ubuntu-latest
    env:
      PPEE_IMAGE: ghcr.io/${{ github.repository_owner }}/ppee-cli:2.0.0
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with: { name: dist, path: dist }

      - name: Pull ppee-cli
        run: docker pull "$PPEE_IMAGE"

      - name: Policy gate
        run: |
          export PPEE_CLI="docker run --rm -v $PWD:$PWD:ro -w $PWD $PPEE_IMAGE"
          curl -fsSLo ppee-policy.sh https://mzrst.com/puppy/docs/assets/ppee-policy.sh   # or commit it to your repo
          chmod +x ppee-policy.sh
          ./ppee-policy.sh --require-hardening --forbid-wx --forbid-admin \
            --report ppee-report.jsonl dist/*.exe dist/*.dll | tee gate.txt

      - name: Job summary
        if: always()
        run: |
          {
            echo "## PE inspection"
            echo '| File | Signed | ASLR | DEP | CFG | W+X |'
            echo '|---|---|---|---|---|---|'
            jq -r '"| \(.path|split("/")|last) | \(.signed) | \(.aslr) | \(.dep) | \(.cfg) | \(.wxSections) |"' ppee-report.jsonl
          } >> "$GITHUB_STEP_SUMMARY"

      - uses: actions/upload-artifact@v4
        if: always()
        with: { name: ppee-report, path: ppee-report.jsonl }

The gate step fails the job (exit 1) on any violation. The summary and report are uploaded either way.

Verify signatures on a Windows runner

Only the Windows build reports WinVerifyTrust trust (chain and revocation):

.github/workflows/verify-signature.yml
jobs:
  verify:
    runs-on: windows-latest
    steps:
      - uses: actions/download-artifact@v4
        with: { name: dist, path: dist }
      - name: Get ppee-cli
        shell: pwsh
        run: |
          # Download or restore ppee-cli.exe into .\tools (e.g. from your release assets or a cache)
          echo "$PWD\tools" | Out-File -Append $env:GITHUB_PATH
      - name: Verify
        shell: pwsh
        run: |
          $bad = 0
          Get-ChildItem dist -Include *.exe,*.dll,*.sys -Recurse | ForEach-Object {
            $j = ppee-cli.exe --no-update-check --no-similarity --json --security $_.FullName | ConvertFrom-Json
            $r = $j.security.validity.result
            if ($r -ne 'SIGNED & VERIFIED') { Write-Output "::error file=$($_.Name)::$r"; $bad++ }
            else { Write-Output "OK  $($_.Name)  signer: $($j.security.signatures[0].signerCertificate.subjectName)" }
          }
          if ($bad) { exit 1 }

Build and publish the image

.github/workflows/ppee-image.yml
name: ppee-image
on:
  push:
    tags: ["v*"]
permissions:
  contents: read
  packages: write
jobs:
  image:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4   # a repo holding the Dockerfile and the release files
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          platforms: linux/amd64
          tags: ghcr.io/${{ github.repository_owner }}/ppee-cli:${{ github.ref_name }}
      - name: Smoke test
        run: |
          docker run --rm ghcr.io/${{ github.repository_owner }}/ppee-cli:${{ github.ref_name }} --version

Annotations

Print ::error file=<path>::<message> lines (as in the Windows example) to put findings inline on the PR's Files changed tab.

References