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 | 14 | ** Tokens |
| 15 | 15 | |
| 16 | 16 | Tokens are minted wherever the registry is reached: over SSH, on the |
| 17 | | API, anywhere. A full-scope token can mint another, which is what full |
| 18 | | scope means; a read-scoped one cannot, because minting is a write. The |
| 19 | | controls here are scope, TTL and revocation, not which door a request |
| 20 | | arrived through (#234). Give a token the narrowest scope and shortest |
| 17 | API, anywhere. =token create= makes a =read= token unless =--scope full= |
| 18 | is given; a read token runs only commands marked read-only. A full-scope |
| 19 | token can mint another, but a token with a =--ttl= cannot run any |
| 20 | command 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 |
| 23 | it made would outlive it. Give a token the narrowest scope and shortest |
| 21 | 24 | TTL that does its job, and revoke it when the job is over. |
| 22 | 25 | |
| 23 | 26 | #+begin_src sh |
| 24 | | gitbay auth token create --name ci [--scope full|read] [--ttl 30d] |
| 27 | gitbay auth token create --name ci [--scope read|full] [--ttl 30d] |
| 25 | 28 | gitbay auth token list |
| 26 | | gitbay auth token revoke ci |
| 29 | gitbay auth token revoke ci [--created] |
| 27 | 30 | #+end_src |
| 28 | 31 | |
| 32 | Tokens and keys record the token they were created through. =token |
| 33 | revoke= prints what the token created, at any depth; with =--created= |
| 34 | it revokes those too, and their SSH connections close. Without it they |
| 35 | stay and the link is dropped. |
| 36 | |
| 29 | 37 | The token (prefix =gb_=, shown exactly once) is presented as |
| 30 | 38 | =Authorization: Bearer gb_...=. Only a hash is stored server-side. |
| 31 | 39 | Scope =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 | 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 | 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 | 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 | 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 | 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 | 26 | | Session lifetime | partial | 7 days absolute, no idle timeout (#276) | |
| 27 | 27 | | Credential expiry | partial | API tokens optional; SSH and deploy keys none (#277) | |
| 28 | 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 | 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 | 11 | | Issue | Area | Gap | Severity | |
| 12 | 12 | |-------+------------------+-----------------------------------------------------------------------+----------| |
| 13 | 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 | 14 | | #258 | CI integrity | Any writer can post a =ci/*= status; tree reuse ignores trust and image | high | |
| 16 | 15 | | #259 | Recovery | No restore has been exercised; verification does not check git connectivity | high | |
| 17 | 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 | 27 | | #281 | TLS | No explicit minimum TLS version | low | |
| 29 | 28 | | #282 | Hook socket | Anything that can open =hook.sock= can act as any user | medium | |
| 30 | 29 | |
| 31 | | Decisions already taken on these: #257 refuses credential |
| 32 | | creation from expiring tokens, records which token created each |
| 33 | | credential, and makes =read= the default scope. |
| 34 | 30 | |
| 35 | 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 | 356 | | activity push on, off | yes | yes | yes | |
| 357 | 357 | | web colour scheme | yes | yes | n/a | |
| 358 | 358 | | API token mint | yes | no | no | |
| 359 | | API token revoke with what it created | yes | no | no | |
| 359 | 360 | | account export bundle | yes | yes | n/a | |
| 360 | 361 | | profile set | yes | yes | yes | |
| 361 | 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 | 52 | OpenSSH and fronted unchanged by the JSON API and the web. No command |
| 53 | 53 | belongs to one surface (#234): what a caller may do is the account's |
| 54 | 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 | 57 | never runs over the API. |
| 57 | 58 | - *Anonymous surfaces* — HTTPS clone of public repos, =git://= where |
| 58 | 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 |
| 4 | 4 | automatically on daemon start; upgrade notes appear per release when |
| 5 | 5 | anything beyond "replace the binary and restart" is needed. |
| 6 | 6 | |
| 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 |
| 11 | must 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). |
| 18 | 21 | |
| 19 | 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 | 191 | <section id="cli"><h2>On the command line</h2> |
| 192 | 192 | <p class="meta">No page here yet, and nothing refusing one: a credential is |
| 193 | 193 | easier 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 |
| 195 | 195 | gitbay web sessions list # browser sessions |
| 196 | 196 | gitbay admin ... # instance administration</pre> |
| 197 | 197 | <p class="meta">All of it works from stock OpenSSH too: |