Back to blog

Securing CI/CD Pipelines with Human Approval Gates

Alex Floyd||9 min read

Every team has a story about a deployment that shouldn’t have happened. A merge to main that bypassed review. A config change pushed to production at 2 AM by an automated pipeline no one was watching. The fix is always the same: “we need an approval gate.”

But most approval gates are cosmetic. A GitHub PR requires one review? Developers approve their own PRs from a second browser. A deployment pipeline pauses for manual approval? Someone clicks “Continue” without verifying what they’re approving. The gate exists, but it provides no real proof that a qualified human made a deliberate decision.

SignedApproval integrates with GitHub, Bitbucket, and GitLab to add cryptographic approval gates to your CI/CD pipeline. When a PR is opened or a deployment is triggered, the developer receives a push notification, authenticates with their passkey or Face ID, and the pipeline gets a signed proof of authorization — not just a button click.

GitHub integration

The GitHub integration works through the SignedApproval GitHub App. Install it on your repository, configure which branches require approval, and every PR targeting those branches gets a commit status check that blocks merge until a human approves.

How it works

  1. Developer opens a PR targeting main (or any configured protected branch)
  2. SignedApproval receives a webhook from GitHub and creates a pending commit status: “SignedApproval: Awaiting human approval”
  3. The designated approver receives a push notification with the PR title, branch, and changed file count
  4. Approver authenticates with passkey, TOTP, or Face ID on their phone
  5. SignedApproval updates the commit status to “success” and stores the Ed25519 signature as proof
  6. The PR can now be merged (optionally auto-merged if configured)

Why commit status checks, not PR reviews?

GitHub’s built-in PR review system is designed for code review, not security gates. Reviews can be dismissed, bypassed by admins, or approved without authentication. Commit status checks are binary (pass/fail), can be enforced as required checks in branch protection rules, and cannot be overridden without changing the branch protection settings.

Setup

Install the GitHub App and configure it in your SignedApproval dashboard:

  1. Go to Dashboard → Integrations → GitHub
  2. Click “Install GitHub App” and authorize for your organization or repositories
  3. Configure branch patterns: which branches require approval (default: main, release/*)
  4. Set the required quorum (number of approvals needed — default is 1)
  5. Optional: enable auto-merge after approval
  6. Add “SignedApproval” as a required status check in GitHub’s branch protection settings

Bitbucket integration

The Bitbucket integration follows the same pattern using Bitbucket’s build status API. When a PR is opened against a protected branch, SignedApproval sets a “pending” build status on the head commit. After human approval, the status flips to “SUCCESSFUL” and the PR can be merged.

Configuration

Connect your Bitbucket workspace via OAuth in the dashboard:

# Dashboard configuration (per-repo settings)
{
  "workspace": "your-workspace",
  "repository": "your-repo",
  "branch_patterns": ["main", "release/*"],
  "auto_merge": false,
  "quorum": 1
}

Bitbucket’s merge checks will block the PR until SignedApproval’s build status passes. Unlike Bitbucket’s native approval buttons, which require no authentication beyond being logged into Bitbucket, SignedApproval requires passkey, TOTP, or biometric authentication — and produces a cryptographic proof.

GitLab integration

GitLab uses external status checks and pipeline statuses. The SignedApproval integration creates a commit status on the merge request’s head commit, blocking merge until the human approves. This works with GitLab.com and self-hosted GitLab instances.

# GitLab configuration
{
  "project_id": 12345,
  "branch_patterns": ["main", "production"],
  "required_approval_count": 1,
  "auto_merge": false
}

Policy configuration

Beyond branch patterns, you can configure granular policies that control when approvals are required and what authentication methods are acceptable:

// Example policy configuration
{
  "rules": [
    {
      "name": "Production deploys require passkey",
      "match": {
        "branch": "main",
        "paths": ["src/**", "deploy/**"]
      },
      "require": {
        "method": ["passkey", "biometric"],
        "quorum": 2,
        "ttl_max": 600
      }
    },
    {
      "name": "Config changes need single approval",
      "match": {
        "branch": "main",
        "paths": ["config/**", ".env*", "*.yml"]
      },
      "require": {
        "method": ["passkey", "totp", "biometric"],
        "quorum": 1,
        "ttl_max": 1800
      }
    },
    {
      "name": "Docs skip approval",
      "match": {
        "branch": "main",
        "paths": ["docs/**", "*.md"]
      },
      "action": "skip"
    }
  ]
}

Policies let you enforce that production deployments require passkey authentication from two approvers, while documentation changes can skip approval entirely. The authentication method constraint is important: for your highest-risk actions, you can require the strongest authentication method available.

The audit trail

Every CI/CD approval produces the same Ed25519 credential as any other SignedApproval action. This means your deployment audit trail isn’t just “PR #142 was merged by @developer on March 15” — it’s “PR #142 was approved by Alex Floyd via passkey authentication at 2026-03-15T14:23:07Z, signed with Ed25519 key abc123, signature verified.”

This level of specificity matters for SOC 2 audits, ISO 27001 compliance, and any regulatory framework that requires documented change management. The signature is independently verifiable — your auditor can confirm the approval without access to your internal systems.

Log sinks for external audit systems

SignedApproval supports log sinks — webhook endpoints that receive a copy of every approval event in real time. Point a log sink at your Datadog, Splunk, or SIEM instance and every CI/CD approval is automatically ingested into your security monitoring pipeline. Configure log sinks in Dashboard → Settings → Log Sinks.

Deployment pipeline integration

Beyond PR gates, you can add approval checks directly to your deployment pipeline. Here is an example GitHub Actions workflow that requires SignedApproval before deploying:

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

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

      - name: Build
        run: npm run build

      - name: Request deployment approval
        id: approval
        run: |
          RESPONSE=$(curl -s -X POST \
            https://signedapproval.net/api/v1/approvals/request \
            -H "Authorization: Bearer ${{ secrets.SA_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "action": "Deploy commit ${{ github.sha }} to production",
              "ttl_seconds": 600,
              "metadata": {
                "repo": "${{ github.repository }}",
                "commit": "${{ github.sha }}",
                "actor": "${{ github.actor }}"
              }
            }')
          echo "id=$(echo $RESPONSE | jq -r '.id')" >> $GITHUB_OUTPUT

      - name: Wait for approval
        run: |
          for i in $(seq 1 60); do
            STATUS=$(curl -s \
              https://signedapproval.net/api/v1/approvals/${{ steps.approval.outputs.id }} \
              -H "Authorization: Bearer ${{ secrets.SA_API_KEY }}" \
              | jq -r '.status')

            if [ "$STATUS" = "approved" ]; then
              echo "Approved!"
              exit 0
            elif [ "$STATUS" = "rejected" ]; then
              echo "Rejected by approver"
              exit 1
            fi
            sleep 10
          done
          echo "Approval timed out"
          exit 1

      - name: Deploy
        run: npm run deploy:production

Comparing to native platform approvals

FeatureNative (GitHub/GL/BB)SignedApproval
AuthenticationSession cookie (already logged in)Passkey, TOTP, or Face ID per action
Proof typeDatabase recordEd25519 signature
Offline verificationNoYes, with public key
Admin overrideAdmins can bypassNo bypass without valid signature
Cross-platformPlatform-specificOne policy for GitHub + BB + GL
Audit exportAPI/CSV from each platformReal-time log sinks + verifiable signatures

Getting started

The fastest path to cryptographic CI/CD approval gates:

  1. Create a free account (200 approvals/month included)
  2. Install the SignedApproval GitHub App (or connect Bitbucket/GitLab in your dashboard)
  3. Configure branch patterns and approval policies
  4. Add “SignedApproval” as a required status check in your branch protection rules
  5. Install the iOS app for push notification approvals with Face ID

Your next production deployment will come with a cryptographic proof that a real human authorized it. Not a button click. Not a Slack reaction. A signature.

CI/CDGitHubDevOpsSecurity

Related posts

Ready to add cryptographic approvals to your agents?

200 free approvals per month. No credit card required.

Get your API key