Pushgate.devDocs Open Pushgate.dev

Threat model

Pushgate decides whether a push is accepted. This page names who can try to change that decision, what stops them, and what Pushgate alone does not stop.

On this page

The verdict is the asset

This page describes pushgate.dev with the hosted TestifySec platform.

For each branch or tag that a push updates, Pushgate asks the platform for a verdict on the commit that the ref will point to. Your tools run the checks. CI/lock signs what they produced. The platform checks that signed evidence for that exact commit against the policy assigned to the repository, and signs its answer.

When the policy requires evidence, Pushgate acts only on that signed verdict, and only after it checks the verdict against this push. Edge verification lists each check.

Commits between the old tip and the new tip of a ref get no verdict of their own. The account that connected the repository can override a verdict, as the out-of-scope table shows.

To get a bad commit accepted, an attacker must change the verdict, override it or send the push around it. This page names each attacker, what they control and what stops them.

What a Pushgate verdict needs Each step’s own signed evidence for the pushed commit and the policy assigned to the repository go into the signed verdict. Pushgate checks that verdict against the push before GitHub delivery. Evidence for another commit or another tenant is never a step’s evidence. Anyone who can use a step’s signing identity can sign evidence for the pushed commit, and the account that connected the repository can override the verdict at the edge. Both are out of scope. OUT OF SCOPE anyone who can sign for a step can sign EACH STEP’S OWN EVIDENCEFor the pushed commit ASSIGNEDRepository policy THE ASSETSigned verdict checked OUT OF SCOPE account that connected the repo can override EDGEPushgate.dev UPSTREAMGitHub NOT A STEP’S EVIDENCE another commit · another tenant not counted
Each step needs its own signed evidence for the pushed commit, stored in your own tenant. Whoever can use a step’s signing identity can still sign that evidence, so separation of duties is the defense.
Mermaid source
flowchart LR
    X[Anyone who can use a step’s signing identity] -. out of scope: can sign .-> E
    E[Each step’s own signed evidence for the pushed commit] --> V[Signed verdict]
    P[Policy assigned to the repository] --> V
    O[Evidence for another commit or another tenant] -. not a step’s evidence .-x V
    V -->|checked against this push| G[Pushgate edge] --> H[GitHub]
    A[Account that connected the repository] -. out of scope: can override .-> G

In scope

In each of these cases, the verdict stays correct. The linked pages explain each mechanism.

AdversaryWhat they controlWhat stops them
Honest pipelines
no adversary
Merge commits, matrix builds and re-runs. These leave evidence for many nearby commits, such as pull request heads and earlier commits of the branch.Every step needs its own evidence for the exact commit that is pushed. Evidence for another commit does not meet that need. A step with no evidence for the pushed commit fails.
Code in the change
pull request author or coding agent
The files, scripts, PATH and tool output of each job that runs the change.Only signed evidence counts. A record changed after signing fails verification, and an unsigned result, such as a log or a status check, never counts. Policy checks the signed content. The record shows what ran, not that the inputs were honest. This holds only where the change’s code cannot use a signing identity. See evidence acceptance.
Co-tenant
another platform customer
Their own evidence, policy releases, repository connections and requests for verdicts.Each of these belongs to one tenant. The platform reads evidence and releases only in the tenant that the gate’s credential names, and it refuses a release from another tenant. The signed verdict names the tenant, repository and repository ID, and Pushgate checks each one against the push. See tenant binding.
Replay
old, genuine evidence
Real signed evidence and verdicts from earlier commits and earlier pushes.Evidence made for one commit cannot be a step’s evidence for another commit. Each verdict carries a fresh nonce that Pushgate issued for this push, so an earlier verdict cannot accept a new push. Evidence for a commit still counts when that commit is pushed again. For built-in checks, that includes a push of the commit to another repository in your tenant. A later failed run for that commit does not cancel an earlier pass.

Out of scope

Pushgate alone does not stop these threats. Each row names what does.

ThreatWhy Pushgate alone does not stop itWhat does
A signing identity
in the wrong hands
Code that runs where a signing identity is available can use that identity, and its evidence passes every check. An enrolled agent holds its own identity. CI/lock keeps that credential on the agent’s machine and signs in a process that the agent starts. In CI, CI/lock runs the change’s own code in the job that signs, such as a GitHub Actions job with id-token: write. A compromised runner and a signer you approved who acts in bad faith also hold valid identities. Their evidence proves what they attested, not that it is true.The defense is separation of duties. For a step that must hold against the author or the agent, require a signer that the change’s code cannot use. ALPS 3 describes signing in a separate service. Have a person review the change.
The account that connected the repositoryThat account can turn the gate off. It can open a break-glass window of 5 to 240 minutes, in which Pushgate does not check the policy. It can also approve a refused push, move the repository from Block to Warn, or reduce what pushes must prove.Protect that account. Each push accepted in a break-glass window, and each refused push that it approves, names that account on its receipt.
Stolen keys or rootsA signature from a stolen key, or from a root that an attacker controls, is valid. Built-in checks always trust the platform’s own roots, and a tenant cannot remove them.Protect the keys that sign your evidence. Remove a root or key that you added when it is lost. The platform’s own roots are TestifySec’s to protect. See trust roots and time.
Compromised dependenciesEvidence records what the build used, not whether it was safe.Dependency scanning and vendor review. A custom command policy can require a scanner that fails on the findings you want to block.
Bugs and weak testsEvidence proves which command ran on the commit and what it returned. It does not prove that the code is correct or that the tests are good enough.Code review, better tests and static analysis.
A route around the gatePushgate checks only the pushes sent through it.GitHub repository permissions that block direct pushes. See prevent a route around the gate.

The CI/lock trust model covers what CI/lock evidence proves and does not prove, including compromised runners and upstream dependencies.

What this means for your policy

  • Produce evidence for the commit you push. If your pipeline creates a merge commit, the merge commit needs its own evidence. Evidence for the pull request head does not count.
  • Sign what must hold against the agent outside the agent. Evidence that an agent signs shows what it attested, not that it is true. For those steps, require a signer that the agent’s code cannot use.
  • Name the signer for each step. Built-in checks accept evidence from any signer that verifies against the platform’s roots, or against roots and keys that you added. The platform issues certificates to each agent you enroll and to any GitHub Actions workflow that asks. A signed custom policy can name the signing identity that each step requires. Generated drafts accept every agent enrolled in your tenant as the signer.
  • Pin the command. A custom check requires the exact command line and a zero exit code. It compares the command line as text, so it does not show which program the PATH found.
  • Use Block for anything that must stop a push. Warn records a failure and still accepts the push.
  • Replace a retired built-in check. A retired check no longer runs, and each receipt says that it was skipped.