No dependencies. No config files. One workflow, one secret.
- Go to app.permissionprotocol.com
- Sign up / log in
- Go to Settings → API Keys → Create Key
- Name:
deploy-gate - Scopes:
receipts.verify,deployRequests.create - Copy the key (shown only once)
🔒 Security note: This key can create approval requests and verify receipts. It CANNOT approve requests or access other tenants. Least privilege by design.
If you want automatic deploy-request creation when no receipt exists, also create a request-create token in Permission Protocol and keep it for Step 2.
Option A: GitHub CLI
gh secret set PP_API_KEY -b "pp_live_your_key_here"
# Optional: enable auto-request creation on missing receipts
gh secret set PP_REQUEST_CREATE_TOKEN -b "pp_req_create_..."Option B: GitHub UI
- Go to your repo → Settings → Secrets and variables → Actions
- Click New repository secret
- Name:
PP_API_KEY - Value: paste your key
- Click Add secret
- Optional for auto-request creation: add
PP_REQUEST_CREATE_TOKEN
Create .github/workflows/deploy-gate.yml:
name: Deploy Gate
on:
pull_request:
branches: [main]
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: permission-protocol/deploy-gate@v2
with:
pp-api-key: ${{ secrets.PP_API_KEY }}Optional (auto-create request on missing receipt):
- uses: permission-protocol/deploy-gate@v2
with:
pp-api-key: ${{ secrets.PP_API_KEY }}
pp-request-create-token: ${{ secrets.PP_REQUEST_CREATE_TOKEN }}Commit and push.
git checkout -b test-gate
echo "# test" >> README.md
git add . && git commit -m "test: trigger deploy gate"
git push origin test-gateOpen a PR to main. Deploy Gate always creates/verifies a PP request and posts a PR comment with the review link.
That's it. You're protected.
═══════════════════════════════════════════════════════════
🔐 PERMISSION PROTOCOL - Deploy Authorization Required
═══════════════════════════════════════════════════════════
❌ NO RECEIPT - Human approval required
A human must approve before merge.
👉 APPROVE HERE: https://app.permissionprotocol.com/pp/deploy-requests/dr_abc123
After approval, re-run this workflow.
═══════════════════════════════════════════════════════════
PR comment (auto-approved / verified):
✅ **Permission Protocol:** Approved
[View receipt →](https://app.permissionprotocol.com/pp/deploy-requests/{requestId})PR comment (approval required):
⏳ **Permission Protocol:** Approval required
[Review & approve →](https://app.permissionprotocol.com/pp/deploy-requests/{requestId})If you use GitHub Repository Rulesets (recommended) to enforce this check, follow these rules to avoid merge loops:
- Create TWO rulesets instead of one.
- Ruleset 1: "Permission Protocol"
- Required status check:
Permission Protocol - Strict mode: OFF (
strict_required_status_checks_policy: false) - Why? Authorization doesn't go stale when
mainadvances. Strict mode creates phantom "Expected" errors that block merges unnecessarily.
- Required status check:
- Ruleset 2: "Build Protection"
- Required status check:
Build and test(or your CI check) - Strict mode: ON (
strict_required_status_checks_policy: true) - Why? This ensures your code actually compiles with the latest
mainbefore merging.
- Required status check:
Result: Your code stays fresh, but your human approvals don't get wiped out every time someone else merges to main.
Error: Request failed with status code 401
Fix: Your API key is invalid or expired.
- Check the key is correct (starts with
pp_live_orpp_test_) - Regenerate if needed
Error: Request failed with status code 403
Fix: Your API key is missing required scopes.
- Go to Settings → API Keys
- Ensure scopes include:
receipts.verify,deployRequests.create - Regenerate key with correct scopes
Error: Request failed with status code 404
Fix: Your repo isn't registered with Permission Protocol.
- Go to app.permissionprotocol.com
- Connect the repo to your Permission Protocol account
⚠️ PP unavailable - fail-open
What it means: Permission Protocol API did not respond before timeout (fail-open-timeout, default 30s).
- Deploy Gate sets commit status to success with
PP unavailable — fail-open - Merge is not blocked by PP downtime
- Retry later to restore normal gating behavior
Fix: Make sure the workflow triggers on pull_request to main:
on:
pull_request:
branches: [main]Default protected paths: deploy/ and .github/workflows/
To send different risk metadata signals:
- uses: permission-protocol/deploy-gate@v2
with:
pp-api-key: ${{ secrets.PP_API_KEY }}
protected-paths: '^(src/critical/|infra/|k8s/|terraform/)'These paths are sent to PP as metadata and do not decide whether the gate runs.
Total install time: ~3 minutes
Time to first blocked PR: ~1 minute after that