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.
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 .-> GIn scope
In each of these cases, the verdict stays correct. The linked pages explain each mechanism.
| Adversary | What they control | What 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.
| Threat | Why Pushgate alone does not stop it | What 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 repository | That 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 roots | A 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 dependencies | Evidence 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 tests | Evidence 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 gate | Pushgate 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.