When Your CI Pipeline Ignores Artifact Signatures: How to Enforce GPG Checks Before Deploying to Production

Why a Missing GPG Check is a Silent Vulnerability

When a CI pipeline builds a Docker image, compiles a binary, or packages a Helm chart, it usually writes the result to an artifact store. If the pipeline then pushes that artifact straight to a production registry or a Kubernetes cluster, the chain of trust is broken. An attacker who can tamper with the build environment, inject a malicious dependency, or simply mis‑configure a build step can get their payload into production without any alert.

GPG signatures provide a cryptographic guarantee that the artifact was produced by a trusted key and that it has not been altered since signing. Enforcing GPG checks before deployment turns the artifact store into a verifiable ledger. This article walks through the practical steps to add that check to a typical GitHub Actions or GitLab CI pipeline, discusses trade‑offs, and shows how to troubleshoot common pitfalls.


1. The Threat Model

Actor Capability Impact
Build bot with elevated privileges Modify build scripts, inject dependencies Compromise of all downstream deployments
Insider with write access to repo Replace build artifacts with signed but malicious ones Silent rollout of malware
External attacker exploiting a CI secret leak Sign a forged artifact with a compromised key Bypass signature verification if key is compromised

The core problem is that the CI system trusts the build environment. If that trust is misplaced, the signature check is the only remaining gate. Without it, the pipeline can be a conduit for supply‑chain attacks.


2. Choosing a Signing Strategy

2.1. Key Management

Option Pros Cons
Hardware Security Module (HSM) Highest security, tamper‑resistant Expensive, requires integration
AWS KMS / Azure Key Vault Managed, scalable Cloud‑locked, potential latency
GPG key on a dedicated signing server Open source, flexible Requires secure hosting and rotation

For most teams, a dedicated signing server that holds a GPG private key protected by a passphrase and a hardware token (e.g., YubiKey) strikes a balance between security and cost.

2.2. Signing Formats

Artifact Preferred Signature Tool
Docker image docker trust (Notary) Docker Content Trust
Binary .asc detached signature gpg --detach-sign
Helm chart helm package + helm repo index Helm
JAR / WAR jarsigner or gpg Java tools

The rest of the article focuses on the most common scenario: signing a Docker image and a binary.


3. Implementing GPG Checks in GitHub Actions

3.1. Signing the Artifact

# .github/workflows/build.yml
name: Build & Sign

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build binary
        run: |
          make build
          gpg --batch --yes --detach-sign -a target/app
          mv target/app target/app.asc          

      - name: Build Docker image
        run: |
          docker build -t myapp:${{ github.sha }} .
          docker tag myapp:${{ github.sha }} registry.example.com/myapp:${{ github.sha }}
          docker push registry.example.com/myapp:${{ github.sha }}          

      - name: Sign Docker image
        run: |
          docker trust key generate mykey
          docker trust signer add --key mykey registry.example.com/myapp:${{ github.sha }}          

The docker trust commands require Docker Content Trust to be enabled (DOCKER_CONTENT_TRUST=1).

3.2. Publishing the Signature

      - name: Upload signatures
        uses: actions/upload-artifact@v4
        with:
          name: signatures
          path: |
            target/app.asc
            # Docker signatures are stored in the registry            

3.3. Verification Step

Add a separate job that runs before deployment:

  verify:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: signatures

      - name: Verify binary
        run: |
          gpg --verify target/app.asc target/app          

      - name: Verify Docker image
        run: |
          DOCKER_CONTENT_TRUST=1 docker pull registry.example.com/myapp:${{ github.sha }}
          docker trust inspect --pretty registry.example.com/myapp:${{ github.sha }}          

If any verification fails, the job exits non‑zero, stopping the pipeline. The deployment job depends on verify, so a failed check blocks production.


4. Implementing GPG Checks in GitLab CI

# .gitlab-ci.yml
stages:
  - build
  - verify
  - deploy

build:
  stage: build
  script:
    - make build
    - gpg --batch --yes --detach-sign -a target/app
    - docker build -t registry.example.com/myapp:$CI_COMMIT_SHA .
    - docker push registry.example.com/myapp:$CI_COMMIT_SHA
  artifacts:
    paths:
      - target/app.asc
      - target/app
    expire_in: 1 hour

verify:
  stage: verify
  script:
    - gpg --verify target/app.asc target/app
    - DOCKER_CONTENT_TRUST=1 docker pull registry.example.com/myapp:$CI_COMMIT_SHA
    - docker trust inspect --pretty registry.example.com/myapp:$CI_COMMIT_SHA
  needs: [build]

deploy:
  stage: deploy
  script:
    - kubectl rollout restart deployment/myapp
  needs: [verify]

GitLab’s needs keyword ensures that deploy only runs if verify succeeds.


5. Trade‑offs and Practical Considerations

Decision Impact on Security Impact on DevOps
Strict signature enforcement Eliminates silent supply‑chain attacks Adds build time (signing, verification)
Using a shared signing key Easier rotation but higher risk if key leaks Fewer secrets to manage
Signing only binaries, not images Misses image tampering Faster pipeline
Automated key rotation Keeps trust strong Requires CI to fetch new public keys

5.1. Performance

Signing a Docker image with Notary can add ~30 s per build. For teams with high throughput, consider caching the Notary trust store or parallelizing the signing step with other non‑blocking jobs.

5.2. Key Rotation

Automate key rotation with a cron job that:

  1. Generates a new GPG key pair.
  2. Publishes the new public key to a key server or internal registry.
  3. Updates the CI secrets with the new private key (protected by a passphrase).
  4. Re‑signs all artifacts that will be deployed in the next window.

Failing to rotate keys leaves a single point of compromise.

5.3. Handling Multiple Signers

In large organizations, different teams may sign different components. Use a trust hierarchy:

  • Root key: signed by the security team.
  • Team keys: signed by the root key.
  • Artifact signatures: signed by team keys.

Docker Content Trust supports multiple signers; verify that the chain is intact before deployment.


6. Troubleshooting Common Issues

Symptom Likely Cause Fix
gpg: signature made ... but verification fails Wrong key used, key revoked, or passphrase mismatch Ensure the correct key is imported and the passphrase is supplied via CI secrets
Docker pull fails with notary: failed to get signature Docker Content Trust disabled or key not uploaded Set DOCKER_CONTENT_TRUST=1 and run docker trust key generate
Verification job times out Network latency to key server or registry Cache public keys locally or use a mirror
Binary signature missing after build Build script did not run gpg --detach-sign Add explicit step or use a wrapper script that always signs

7. Real‑World Example: A Supply‑Chain Breach Prevented

In early 2026, a mid‑size fintech company discovered that an attacker had inserted a backdoor into a third‑party library. Their CI pipeline built the application, signed the binary, and pushed a Docker image to production. Because the pipeline did not verify the signature before deployment, the malicious binary reached the staging environment unnoticed.

After implementing the GPG check described above, the verification job failed on the first sign of tampering. The deployment was blocked, and the incident response team could investigate without any customer impact. The company also rotated the signing key and added a key‑rotation policy, closing the attack surface.


8. Integrating with Existing Security Controls

  • OPA / Gatekeeper: Add a policy that checks the presence of a valid signature in the image metadata before allowing a Kubernetes admission.
  • GitHub Advanced Security: Use the CODEQL analysis to flag unsigned binaries in the repository.
  • CISA’s Supply Chain Protection Guidance: Align the signing workflow with the 2025 CISA framework for secure software supply chains.

9. Final Checklist

  • GPG key stored in a secure, isolated signing server.
  • CI pipeline signs all artifacts (binary, image, chart).
  • Verification job runs before any deployment job.
  • Key rotation policy in place (quarterly or after any compromise).
  • Public keys are distributed to all verification points (CI, registry, Kubernetes).
  • Logs of signing and verification are retained for audit.

See also