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
1414** Tokens
1515
1616Tokens are minted wherever the registry is reached: over SSH, on the
17API, anywhere. A full-scope token can mint another, which is what full
18scope means; a read-scoped one cannot, because minting is a write. The
19controls here are scope, TTL and revocation, not which door a request
20arrived through (#234). Give a token the narrowest scope and shortest
17API, anywhere. =token create= makes a =read= token unless =--scope full=
18is given; a read token runs only commands marked read-only. A full-scope
19token can mint another, but a token with a =--ttl= cannot run any
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
2124TTL that does its job, and revoke it when the job is over.
2225
2326#+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]
2528gitbay auth token list
26gitbay auth token revoke ci
29gitbay auth token revoke ci [--created]
2730#+end_src
2831
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
2937The token (prefix =gb_=, shown exactly once) is presented as
3038=Authorization: Bearer gb_...=. Only a hash is stored server-side.
3139Scope =read= permits list/show/log/diff-style commands and refuses
.gitbay/wiki/Architecture/05-Identity-and-Access.org +1 −1
@@ -17,7 +17,7 @@
1717|--------------------+-------------------------------------------------+----------------------------------+--------------------------------------------+-------------------------------+-------------------------------------|
1818| SSH user key | user's public key | fingerprint and public blob | =full=, =git=, or =runner= | none | =keys remove= (own keys); closes its connections |
1919| 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]= |
2121| Web session | 32 random bytes hex, cookie =gitbay_session= | SHA-256 hash | full account | 7 days, no sliding renewal | logout, =web sessions revoke= |
2222| 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 |
2323| 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.
2626| Session lifetime | partial | 7 days absolute, no idle timeout (#276) |
2727| Credential expiry | partial | API tokens optional; SSH and deploy keys none (#277) |
2828| 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=) |
3030
3131** Access control (V4)
3232
.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.
1111| Issue | Area | Gap | Severity |
1212|-------+------------------+-----------------------------------------------------------------------+----------|
1313| #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 |
1514| #258 | CI integrity | Any writer can post a =ci/*= status; tree reuse ignores trust and image | high |
1615| #259 | Recovery | No restore has been exercised; verification does not check git connectivity | high |
1716| #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.
2827| #281 | TLS | No explicit minimum TLS version | low |
2928| #282 | Hook socket | Anything that can open =hook.sock= can act as any user | medium |
3029
31Decisions already taken on these: #257 refuses credential
32creation from expiring tokens, records which token created each
33credential, and makes =read= the default scope.
3430
3531* Questions an auditor will ask that have no answer yet
3632
.gitbay/wiki/Parity.org +1
@@ -356,6 +356,7 @@ client has no use for one (krz/gitbay#57).
356356| activity push on, off | yes | yes | yes |
357357| web colour scheme | yes | yes | n/a |
358358| API token mint | yes | no | no |
359| API token revoke with what it created | yes | no | no |
359360| account export bundle | yes | yes | n/a |
360361| profile set | yes | yes | yes |
361362| 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
5252 OpenSSH and fronted unchanged by the JSON API and the web. No command
5353 belongs to one surface (#234): what a caller may do is the account's
5454 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
5657 never runs over the API.
5758- *Anonymous surfaces* — HTTPS clone of public repos, =git://= where
5859 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
44automatically on daemon start; upgrade notes appear per release when
55anything beyond "replace the binary and restart" is needed.
66
7* unreleased
8
9- Removing an SSH key, removing a deploy key, or disabling or deleting an
10 account closes every open connection using an affected key, git
11 transports included; every command re-reads its key (#256).
12- A request whose credential has an expiry cannot run a command that
13 mints another one — tokens, keys, login links, invites, accounts,
14 verified addresses (#257).
15- Tokens and SSH keys record the token that created them; `token create`
16 defaults to =--scope read=; `token revoke --created` also revokes what
17 a token made, at any depth (#257).
7* Unreleased
8
9*Upgrade note.* =token create= makes a =read= token unless given
10=--scope full=. A script that mints a token and then writes with it
11must add =--scope full=. Existing tokens keep their scope.
12
13- A token with a =--ttl= is refused on every command that creates a
14 credential: tokens, keys, deploy keys, runner keys, login links,
15 invites, accounts and verified addresses (#257).
16- Tokens and SSH keys record the token they were created through.
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).
1821
1922* v1.36.0 — 2026-09-23
2023
internal/web/templates/account.html +1 −1
@@ -191,7 +191,7 @@ never included; a replayed bundle's emails arrive unverified.</p>
191191<section id="cli"><h2>On the command line</h2>
192192<p class="meta">No page here yet, and nothing refusing one: a credential is
193193easier 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
195195gitbay web sessions list # browser sessions
196196gitbay admin ... # instance administration</pre>
197197<p class="meta">All of it works from stock OpenSSH too: