Commit 0787c7c07e

0787c7c07e515920c7178502c8168ae08de824a8

parent: ed682bb8e7

Verified · cmc ci/build: success ci/test: success

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

wiki: token delegation, read default; release note

Closes #257

Layout: unified · split

.gitbay/wiki/API.org +14 −6
@@ -14,18 +14,26 @@ enabled = true
14** Tokens 14** Tokens
15 15
16Tokens are minted wherever the registry is reached: over SSH, on the 16Tokens are minted wherever the registry is reached: over SSH, on the
17API, anywhere. A full-scope token can mint another, which is what full 17API, anywhere. =token create= makes a =read= token unless =--scope full=
18scope means; a read-scoped one cannot, because minting is a write. The 18is given; a read token runs only commands marked read-only. A full-scope
19controls here are scope, TTL and revocation, not which door a request 19token can mint another, but a token with a =--ttl= cannot run any
20arrived through (#234). Give a token the narrowest scope and shortest 20command that creates a credential — =token create=, =keys add=,
21=repo deploy-key add=, =repo runner add=, =web login=, =admin invite=,
22=admin user create=, =email verify=, =admin email verify= — since what
23it made would outlive it. Give a token the narrowest scope and shortest
21TTL that does its job, and revoke it when the job is over. 24TTL that does its job, and revoke it when the job is over.
22 25
23#+begin_src sh 26#+begin_src sh
24gitbay auth token create --name ci [--scope full|read] [--ttl 30d] 27gitbay auth token create --name ci [--scope read|full] [--ttl 30d]
25gitbay auth token list 28gitbay auth token list
26gitbay auth token revoke ci 29gitbay auth token revoke ci [--created]
27#+end_src 30#+end_src
28 31
32Tokens and keys record the token they were created through. =token
33revoke= prints what the token created, at any depth; with =--created=
34it revokes those too, and their SSH connections close. Without it they
35stay and the link is dropped.
36
29The token (prefix =gb_=, shown exactly once) is presented as 37The token (prefix =gb_=, shown exactly once) is presented as
30=Authorization: Bearer gb_...=. Only a hash is stored server-side. 38=Authorization: Bearer gb_...=. Only a hash is stored server-side.
31Scope =read= permits list/show/log/diff-style commands and refuses 39Scope =read= permits list/show/log/diff-style commands and refuses
.gitbay/wiki/Architecture/05-Identity-and-Access.org +1 −1
@@ -17,7 +17,7 @@
17|--------------------+-------------------------------------------------+----------------------------------+--------------------------------------------+-------------------------------+-------------------------------------| 17|--------------------+-------------------------------------------------+----------------------------------+--------------------------------------------+-------------------------------+-------------------------------------|
18| SSH user key | user's public key | fingerprint and public blob | =full=, =git=, or =runner= | none | =keys remove= (own keys); closes its connections | 18| SSH user key | user's public key | fingerprint and public blob | =full=, =git=, or =runner= | none | =keys remove= (own keys); closes its connections |
19| Deploy key | public key | same table, scope =deploy:<repo>:ro/rw= | one repository, read or read-write | none | =repo deploy-key remove= (repo admin); closes its connections | 19| Deploy key | public key | same table, scope =deploy:<repo>:ro/rw= | one repository, read or read-write | none | =repo deploy-key remove= (repo admin); closes its connections |
20| API token | =gb_= + 32 random bytes hex | SHA-256 hash | =full= or =read= | optional =--ttl= | =token revoke= | 20| API token | =gb_= + 32 random bytes hex | SHA-256 hash | =read= (default) or =full=; with an expiry, no credential-minting command | optional =--ttl= | =token revoke [--created]= |
21| Web session | 32 random bytes hex, cookie =gitbay_session= | SHA-256 hash | full account | 7 days, no sliding renewal | logout, =web sessions revoke= | 21| Web session | 32 random bytes hex, cookie =gitbay_session= | SHA-256 hash | full account | 7 days, no sliding renewal | logout, =web sessions revoke= |
22| Login link | 32 random bytes hex in a URL | SHA-256 hash, single use | creates a web session | 15 min (mail), 5 min (SSH) | consumed on use | 22| Login link | 32 random bytes hex in a URL | SHA-256 hash, single use | creates a web session | 15 min (mail), 5 min (SSH) | consumed on use |
23| Email verification | 32 random bytes hex | SHA-256 hash, single use | verifies one address for one account | 24 h | consumed on use | 23| Email verification | 32 random bytes hex | SHA-256 hash, single use | verifies one address for one account | 24 h | consumed on use |
.gitbay/wiki/Architecture/09-Controls.org +1 −1
@@ -26,7 +26,7 @@ chapter names of OWASP ASVS 4.0 where one fits.
26| Session lifetime | partial | 7 days absolute, no idle timeout (#276) | 26| Session lifetime | partial | 7 days absolute, no idle timeout (#276) |
27| Credential expiry | partial | API tokens optional; SSH and deploy keys none (#277) | 27| Credential expiry | partial | API tokens optional; SSH and deploy keys none (#277) |
28| Revocation takes effect immediately | in place | removing a key or disabling an account closes its connections; every exec re-reads its key (=internal/sshd/sshd.go=) | 28| Revocation takes effect immediately | in place | removing a key or disabling an account closes its connections; every exec re-reads its key (=internal/sshd/sshd.go=) |
29| Delegation bounded by the delegating credential | gap | expiring tokens can mint lasting credentials (#257) | 29| Delegation bounded by the delegating credential | in place | expiring tokens refused on =MintsCredential= commands; credentials record their creating token (=internal/control/control.go=) |
30 30
31** Access control (V4) 31** Access control (V4)
32 32
.gitbay/wiki/Architecture/10-Known-Gaps.org −4
@@ -11,7 +11,6 @@ what the 2026-09-27 review found; remove a row when its issue closes.
11| Issue | Area | Gap | Severity | 11| Issue | Area | Gap | Severity |
12|-------+------------------+-----------------------------------------------------------------------+----------| 12|-------+------------------+-----------------------------------------------------------------------+----------|
13| #255 | CI isolation | Untrusted and trusted builds of a repository share a writable build home | high | 13| #255 | CI isolation | Untrusted and trusted builds of a repository share a writable build home | high |
14| #257 | Credentials | An expiring token can create credentials that outlive it; tokens default to full scope | high |
15| #258 | CI integrity | Any writer can post a =ci/*= status; tree reuse ignores trust and image | high | 14| #258 | CI integrity | Any writer can post a =ci/*= status; tree reuse ignores trust and image | high |
16| #259 | Recovery | No restore has been exercised; verification does not check git connectivity | high | 15| #259 | Recovery | No restore has been exercised; verification does not check git connectivity | high |
17| #260 | CI network | Builds share the runner's source address; no egress policy | medium | 16| #260 | CI network | Builds share the runner's source address; no egress policy | medium |
@@ -28,9 +27,6 @@ what the 2026-09-27 review found; remove a row when its issue closes.
28| #281 | TLS | No explicit minimum TLS version | low | 27| #281 | TLS | No explicit minimum TLS version | low |
29| #282 | Hook socket | Anything that can open =hook.sock= can act as any user | medium | 28| #282 | Hook socket | Anything that can open =hook.sock= can act as any user | medium |
30 29
31Decisions already taken on these: #257 refuses credential
32creation from expiring tokens, records which token created each
33credential, and makes =read= the default scope.
34 30
35* Questions an auditor will ask that have no answer yet 31* Questions an auditor will ask that have no answer yet
36 32
.gitbay/wiki/Parity.org +1
@@ -356,6 +356,7 @@ client has no use for one (krz/gitbay#57).
356| activity push on, off | yes | yes | yes | 356| activity push on, off | yes | yes | yes |
357| web colour scheme | yes | yes | n/a | 357| web colour scheme | yes | yes | n/a |
358| API token mint | yes | no | no | 358| API token mint | yes | no | no |
359| API token revoke with what it created | yes | no | no |
359| account export bundle | yes | yes | n/a | 360| account export bundle | yes | yes | n/a |
360| profile set | yes | yes | yes | 361| profile set | yes | yes | yes |
361| write the profile about | yes | yes | yes | 362| write the profile about | yes | yes | yes |
.gitbay/wiki/Threat-Model.org +2 −1
@@ -52,7 +52,8 @@ matrix and the open gaps are in the [[file:Architecture/00-Overview.org][Archite
52 OpenSSH and fronted unchanged by the JSON API and the web. No command 52 OpenSSH and fronted unchanged by the JSON API and the web. No command
53 belongs to one surface (#234): what a caller may do is the account's 53 belongs to one surface (#234): what a caller may do is the account's
54 rights narrowed by its credential's scope, decided in one place, so a 54 rights narrowed by its credential's scope, decided in one place, so a
55 bearer token is worth exactly its scope and no more. Git transport 55 bearer token is worth exactly its scope and no more, and a credential
56 with an expiry cannot create one that outlives it. Git transport
56 never runs over the API. 57 never runs over the API.
57- *Anonymous surfaces* — HTTPS clone of public repos, =git://= where 58- *Anonymous surfaces* — HTTPS clone of public repos, =git://= where
58 enabled, the read-only web UI — carry no credentials and expose only 59 enabled, the read-only web UI — carry no credentials and expose only
CHANGELOG.org +14 −11
@@ -4,17 +4,20 @@ Versioning follows semver from v0.1.0. Database migrations run
4automatically on daemon start; upgrade notes appear per release when 4automatically on daemon start; upgrade notes appear per release when
5anything beyond "replace the binary and restart" is needed. 5anything beyond "replace the binary and restart" is needed.
6 6
7* unreleased 7* Unreleased
8 8
9- Removing an SSH key, removing a deploy key, or disabling or deleting an 9*Upgrade note.* =token create= makes a =read= token unless given
10 account closes every open connection using an affected key, git 10=--scope full=. A script that mints a token and then writes with it
11 transports included; every command re-reads its key (#256). 11must add =--scope full=. Existing tokens keep their scope.
12- A request whose credential has an expiry cannot run a command that 12
13 mints another one — tokens, keys, login links, invites, accounts, 13- A token with a =--ttl= is refused on every command that creates a
14 verified addresses (#257). 14 credential: tokens, keys, deploy keys, runner keys, login links,
15- Tokens and SSH keys record the token that created them; `token create` 15 invites, accounts and verified addresses (#257).
16 defaults to =--scope read=; `token revoke --created` also revokes what 16- Tokens and SSH keys record the token they were created through.
17 a token made, at any depth (#257). 17 =token revoke <name>= lists what it created; =--created= revokes
18 those too.
19- Removing an SSH key, a deploy key, or disabling an account closes the
20 connections the key opened, a push in flight included (#256).
18 21
19* v1.36.0 — 2026-09-23 22* v1.36.0 — 2026-09-23
20 23
internal/web/templates/account.html +1 −1
@@ -191,7 +191,7 @@ never included; a replayed bundle's emails arrive unverified.</p>
191<section id="cli"><h2>On the command line</h2> 191<section id="cli"><h2>On the command line</h2>
192<p class="meta">No page here yet, and nothing refusing one: a credential is 192<p class="meta">No page here yet, and nothing refusing one: a credential is
193easier to pipe than to paste, and a minted token is shown once.</p> 193easier to pipe than to paste, and a minted token is shown once.</p>
194<pre class="message" tabindex="0">gitbay auth token create --name laptop # API tokens 194<pre class="message" tabindex="0">gitbay auth token create --name laptop # API tokens, read-only unless --scope full
195gitbay web sessions list # browser sessions 195gitbay web sessions list # browser sessions
196gitbay admin ... # instance administration</pre> 196gitbay admin ... # instance administration</pre>
197<p class="meta">All of it works from stock OpenSSH too: 197<p class="meta">All of it works from stock OpenSSH too: