Code, changes and reviews

Version 4 · Updated

The push guard

What every push is checked for, why it fails closed, and deny-all.

Every push and every merge in Branchscope passes the push guard before it lands, and so does every tag a release makes. It's a separate service (written in Rust), and git's pre-receive hook asks it about every ref update.

What it checks#

RuleApplies to
Deny-all, with a messageeveryone, when a site admin turns it on (maintenance, an incident)
An archived repository is read-onlyeveryone: no pushes, no merges
Each active ruleset that targets the branch or tageveryone not on that ruleset's bypass list

A repository's rulesets are its policy: which branches and tags are protected, and how. By default:

  • main (the Protected branches ruleset): nobody deletes it or force-pushes to it, and agents can't update it at all, by push or by merge, unless their own app is on the bypass list;
  • v* tags (Release tags): nobody moves or deletes them, and agents can't create, move or delete them.

Rulesets can also require changes (no direct pushes, for people too), required checks or deployments, linear history and signed commits, and restrict creations and updates. A person's bypass never covers an agent acting for them. Nothing bypasses deny-all or an archived repository.

For linear history and signed commits, the git service's hook looks at the commits each update adds and tells the guard whether any is a merge commit, and whether all are signed by a key registered to a Branchscope account (see Signing commits). A ruleset in evaluate mode is decided too, but never refuses: the guard says what it would have refused, and that's recorded.

The control plane keeps each repository's rulesets and gives them to the guard, within seconds of a change, and again within seconds of a guard restart. Required checks and deployments are decided by the control plane when a change merges or a release is published; the guard refuses a plain push to what they protect.

It fails closed#

  • Unreachable: if the guard can't be reached, or doesn't answer in time, the push is refused with "push guard unavailable, push rejected". The cause goes to the git service's log, not to the pusher.
  • Fresh guard: a guard that has just started refuses everything until it has its policy.
  • Same path everywhere: HTTP, SSH, merges and releases' tags are all asked about the same way.
  • Not told, not allowed: where a ruleset requires linear history or signed commits, an update the git service didn't report on (an older git service, or one that couldn't get the signing keys) is refused. A guard from before these rules is given such rulesets so that it refuses creating and updating what they target, except to their bypass list, until it's upgraded; so deploy the guard first, as always.

What's recorded#

Each decision becomes an event with:

  • the repository and the refs;
  • the pusher, and the agent when it was an agent;
  • whether the update was forced;
  • the decision and its message;
  • what rulesets in evaluate mode would have refused, if anything.

It's shown in Activity (per repository, and Push activity for everything you can read). Allowed pushes to protected branches are then indexed, and pushes to a change's branch re-run its review.

For site admins#

Push guard, under Admin in the sidebar, shows whether the guard is reachable and has its policy, and turns deny-all on and off, with the message pushers see. From the CLI:

bscope admin guard status
bscope admin guard deny-all on --message "Maintenance until 14:00"
bscope admin guard deny-all off

Site admins also run the hosted runners (Hosted runners, /hosted-runners), edit the docs in the CMS, get the messages people report in Chats, and administer every repository. See Access and identity.