Commit d19e5181c5

d19e5181c5fae0d0eae2d7d48971e938e5e287c4

parent: dc10160221

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 07:04 UTC

wiki: reserved ci/ statuses, trusted reuse, required contexts

Closes #258

Layout: unified · split

.gitbay/wiki/API.org +16 −2
@@ -112,8 +112,8 @@ CI reports results through the same command surface (over SSH or the
112JSON API with a full-scope token; reporting requires write access): 112JSON API with a full-scope token; reporting requires write access):
113 113
114#+begin_src sh 114#+begin_src sh
115gitbay status set <owner/name> <sha> --context build --state pending 115gitbay status set <owner/name> <sha> --context ext/build --state pending
116gitbay status set <owner/name> <sha> --context build --state success --url https://ci.example/run/1 116gitbay status set <owner/name> <sha> --context ext/build --state success --url https://ci.example/run/1
117gitbay status list <owner/name> <sha> --json # {"combined": "...", "statuses": [...]} 117gitbay status list <owner/name> <sha> --json # {"combined": "...", "statuses": [...]}
118#+end_src 118#+end_src
119 119
@@ -130,6 +130,20 @@ from outside through =status set= looks like. A repository where
130nothing has ever reported merges. Each 130nothing has ever reported merges. Each
131report also emits a =status= event to webhooks. 131report also emits a =status= event to webhooks.
132 132
133Contexts starting with =ci/= are the instance's own: its builds queue,
134reuse, skip and finish them, and =status set= refuses them with exit 4,
135so a writer cannot mark =ci/test= green on a head the build has not
136passed. Report under another prefix, such as =ext/=.
137
138=repo settings require-contexts <repo> ext/deploy ci/test= names
139statuses the checks gate waits for whether or not they have reported:
140one that has not is =pending=, and =mr show= lists it as
141=ext/deploy=missing=. Naming any context turns =require-checks= on;
142with no contexts the command clears the list and leaves
143=require-checks= as it was. =require-checks off= keeps the list, which
144waits for nothing until the gate is on again. =repo settings show=
145prints both.
146
133* Webhooks 147* Webhooks
134 148
135Per-repository outbound POSTs for repository events. Managed by repo 149Per-repository outbound POSTs for repository events. Managed by repo
.gitbay/wiki/Architecture/07-CI-and-Supply-Chain.org +2 −2
@@ -23,7 +23,7 @@ commit instead of failing silently.
231. *Queue.* The post-receive hook calls =queueJobs= 231. *Queue.* The post-receive hook calls =queueJobs=
24 (=internal/control/build.go=). Each job gets a =ci/<job>= status: 24 (=internal/control/build.go=). Each job gets a =ci/<job>= status:
25 =pending= when queued, =skipped= when path filters exclude it, or 25 =pending= when queued, =skipped= when path filters exclude it, or
26 =success= copied from an earlier build of the same tree (#177). 26 =success= copied from an earlier trusted build of the same tree on the same image (#177, #258).
27 Merge requests from forks are queued against the target repository 27 Merge requests from forks are queued against the target repository
28 with =trusted = false=. 28 with =trusted = false=.
292. *Claim.* A runner calls =runner next= over SSH 292. *Claim.* A runner calls =runner next= over SSH
@@ -55,7 +55,7 @@ Who may do what:
55| =repo secret set/remove/list= | admin on the repository | 55| =repo secret set/remove/list= | admin on the repository |
56| =repo runner add/remove= | admin on the repository | 56| =repo runner add/remove= | admin on the repository |
57| =runner next/log/done= | =runner= key attached to the repository, or admin | 57| =runner next/log/done= | =runner= key attached to the repository, or admin |
58| =status set= | write on the repository (any context name; #258) | 58| =status set= | write on the repository; =ci/*= contexts refused (=status.go=) |
59 59
60* Runner isolation 60* Runner isolation
61 61
.gitbay/wiki/Architecture/09-Controls.org +2 −2
@@ -36,7 +36,7 @@ chapter names of OWASP ASVS 4.0 where one fits.
36| Private resources indistinguishable from missing | in place | =resolveRepo= (=internal/control/repo.go=), =runGit=, smart HTTP | 36| Private resources indistinguishable from missing | in place | =resolveRepo= (=internal/control/repo.go=), =runGit=, smart HTTP |
37| Credential scopes narrow account rights | in place | key and token scopes (=control.go=, =policy/access.go=) | 37| Credential scopes narrow account rights | in place | key and token scopes (=control.go=, =policy/access.go=) |
38| Server-side write protections | in place | pre-receive =CheckPush=, signed commits (=internal/hookd/hookd.go=) | 38| Server-side write protections | in place | pre-receive =CheckPush=, signed commits (=internal/hookd/hookd.go=) |
39| Merge gates | partial | =MergeGates=; any writer can post a =ci/*= status (#258) | 39| Merge gates | in place | =MergeGates=; =ci/*= statuses written only by the build subsystem; required contexts |
40| Admin functions isolated | in place | =admin= noun gated in =Dispatch=; =audit= admin-only | 40| Admin functions isolated | in place | =admin= noun gated in =Dispatch=; =audit= admin-only |
41| CSRF protection | in place | SameSite=Lax plus =checkOrigin= (=accounts.go=) | 41| CSRF protection | in place | SameSite=Lax plus =checkOrigin= (=accounts.go=) |
42| Typed confirmation for destructive web actions | in place | =internal/httpd/confirm.go= | 42| Typed confirmation for destructive web actions | in place | =internal/httpd/confirm.go= |
@@ -89,7 +89,7 @@ chapter names of OWASP ASVS 4.0 where one fits.
89| Runner limited to attached repositories | in place | =runnerMayBuild= (=build.go=) | 89| Runner limited to attached repositories | in place | =runnerMayBuild= (=build.go=) |
90| Build images fixed by the operator | in place | =--pull=never= | 90| Build images fixed by the operator | in place | =--pull=never= |
91| Build network egress restricted | gap | #260 | 91| Build network egress restricted | gap | #260 |
92| Build results reused only across equal trust | gap | tree reuse ignores trust and image (#258) | 92| Build results reused only across equal trust | in place | =SuccessBuildForTree=, =SuccessBuildFor= (=internal/store/builds.go=) |
93 93
94** Availability and operations 94** Availability and operations
95 95
.gitbay/wiki/Architecture/10-Known-Gaps.org −1
@@ -10,7 +10,6 @@ what the 2026-09-27 review found; remove a row when its issue closes.
10 10
11| Issue | Area | Gap | Severity | 11| Issue | Area | Gap | Severity |
12|-------+------------------+-----------------------------------------------------------------------+----------| 12|-------+------------------+-----------------------------------------------------------------------+----------|
13| #258 | CI integrity | Any writer can post a =ci/*= status; tree reuse ignores trust and image | high |
14| #259 | Recovery | No restore has been exercised; verification does not check git connectivity | high | 13| #259 | Recovery | No restore has been exercised; verification does not check git connectivity | high |
15| #260 | CI network | Builds share the runner's source address; no egress policy | medium | 14| #260 | CI network | Builds share the runner's source address; no egress policy | medium |
16| #261 | Various | Migration foreign-key check after commit; three web writes bypass dispatch; documentation drift | medium | 15| #261 | Various | Migration foreign-key check after commit; three web writes bypass dispatch; documentation drift | medium |
.gitbay/wiki/CI.org +10 −1
@@ -5,7 +5,16 @@ Three mechanisms decide what a push does to CI, and they interact:
5- *Dedupe.* A job's result is a property of the commit's tree. A commit 5- *Dedupe.* A job's result is a property of the commit's tree. A commit
6 that already has a passed, queued or running build for a job is not 6 that already has a passed, queued or running build for a job is not
7 queued again; a commit whose tree already passed a job gets that 7 queued again; a commit whose tree already passed a job gets that
8 result as its status, naming the build it came from (#177). A failed, 8 result as its status, naming the build it came from (#177). Only a trusted build counts, and for tree reuse only one on the
9 image the job names: a fork's green build does not stand for the
10 repository's own, so its commit is built again when it lands on a
11 branch (#258). A job that names no =image:= is compared as naming
12 none: its reuse does not notice the runner's default image changing,
13 because reuse is decided when the push is queued, before any runner
14 claims the build, and runners can differ in their default. Name the
15 image in =ci.yml= to tie reuse to it; after an operator changes a
16 runner's =-image=, =build trigger= builds a job afresh, since a
17 triggered build is never reused. A failed,
9 cancelled or abandoned build does not count: that commit runs again. 18 cancelled or abandoned build does not count: that commit runs again.
10- *Path filters.* =paths= and =paths-ignore= on a job are evaluated 19- *Path filters.* =paths= and =paths-ignore= on a job are evaluated
11 against the files the push changed. The diff base is the old tip when 20 against the files the push changed. The diff base is the old tip when
.gitbay/wiki/Parity.org +1
@@ -197,6 +197,7 @@ rather than the one the web page shows.
197| merge requests only | yes | yes | yes | 197| merge requests only | yes | yes | yes |
198| protected tags | yes | yes | yes | 198| protected tags | yes | yes | yes |
199| require codeowners | yes | yes | yes | 199| require codeowners | yes | yes | yes |
200| require contexts | yes | yes | no |
200| access grants | yes | no | yes | 201| access grants | yes | no | yes |
201| effective access | yes | no | yes | 202| effective access | yes | no | yes |
202| webhooks | yes | no | yes | 203| webhooks | yes | no | yes |
.gitbay/wiki/Users.org +3 −1
@@ -553,7 +553,9 @@ queues nothing, so look there when a push builds nothing.
553 553
554Each job becomes a build (=build list=, =build log=, the builds tab on 554Each job becomes a build (=build list=, =build log=, the builds tab on
555the web) and a =ci/<job>= commit status, which =repo settings 555the web) and a =ci/<job>= commit status, which =repo settings
556require-checks= can gate merges on. =build list= takes =--ref=, 556require-checks= can gate merges on. =repo settings
557require-contexts= names statuses the gate waits for until they report,
558and turns the gate on. =build list= takes =--ref=,
557=--status= and =--job= to narrow the listing, combinable; the builds 559=--status= and =--job= to narrow the listing, combinable; the builds
558tab reads the same flags from its =?ref=, =?status= and =?job= query 560tab reads the same flags from its =?ref=, =?status= and =?job= query
559parameters and groups the result into one row per commit. Steps run 561parameters and groups the result into one row per commit. Steps run