Commit 77ff123033
Verified · cmc
Layout: unified · split
.gitbay/wiki/Admin.org +18 −6
| @@ -218,15 +218,23 @@ push=. | ||
| 218 | 218 | at most =pack_concurrency= − 1 slots when it is above 1, so a signed-in |
| 219 | 219 | client (SSH key, bearer token or web session) can always get the last. |
| 220 | 220 | Past that an SSH client gets "the server is busy…" and exit 1, HTTP |
| 221 | gets 503 with =Retry-After: 30=, git:// an =ERR= line. A queued | |
| 222 | client that disconnects leaves the queue; a running clone whose | |
| 223 | client disconnects is killed. Ref listings (info/refs, protocol v2 | |
| 221 | gets 503 with =Retry-After: 30=, git:// an =ERR= line. An SSH client | |
| 222 | that disconnects while queued leaves the queue; an HTTP or git:// one | |
| 223 | keeps its place until the wait runs out. A running clone is killed | |
| 224 | when its client disconnects, or when no write to the client completes | |
| 225 | for two minutes: a client reading below about 550 B/s, or an HTTP | |
| 226 | request body that takes over two minutes with nothing written back, | |
| 227 | is cut. Ref listings (info/refs, protocol v2 | |
| 224 | 228 | =ls-refs=), pushes and =repo download= are outside the budget. For the |
| 225 | 229 | three counts 0 means the default and a negative value turns that |
| 226 | 230 | bound off. The defaults suit a four-core host; see [[Performance]]. |
| 227 | 231 | With =ssh.mode = "system"= each SSH session is its own process and |
| 228 | 232 | SSH clones are not counted. |
| 229 | - =max_pack_bytes=, =ssh_auth_rate= — reserved, not yet enforced. | |
| 233 | - =max_pack_bytes= (2 GiB) — the largest pack one push may send, | |
| 234 | enforced as =receive.maxInputSize= and lowered to what an owner's | |
| 235 | storage quota has left. | |
| 236 | - =ssh_auth_rate= (10) — SSH authentication failures per client address | |
| 237 | per minute on the embedded listener; see below. | |
| 230 | 238 | |
| 231 | 239 | ** [git_daemon] |
| 232 | 240 | - =enabled= (false), =port= (9418) — the anonymous =git://= listener. |
| @@ -294,7 +302,10 @@ Each row carries the SHA-256 of the row before it. =gitbayd admin audit | ||
| 294 | 302 | verify= opens the store as other admin commands do, applying pending |
| 295 | 303 | migrations, so run it with the binary that matches the daemon. It |
| 296 | 304 | recomputes the chain and exits 1 naming the first row that was |
| 297 | edited or whose predecessor was removed. Retention removing the oldest | |
| 305 | edited or whose predecessor was removed. The chain is unkeyed: whoever | |
| 306 | can write the database can recompute every hash after an edit, and | |
| 307 | verify then finds nothing. It catches an edit only when the later | |
| 308 | hashes were not recomputed. Retention removing the oldest | |
| 298 | 309 | rows is not a break. Rows written before the chain existed are counted |
| 299 | 310 | and skipped; when every row is such a row, verify warns and exits 1, |
| 300 | 311 | since clearing the hash columns looks the same. After an upgrade that |
| @@ -304,7 +315,8 @@ Removing the newest rows leaves no break, and neither do rows written | ||
| 304 | 315 | afterwards under the freed ids. The database cannot show either. The |
| 305 | 316 | daemon logs every row it writes to its journal, outside the database |
| 306 | 317 | (=journalctl -u gitbayd -g 'INFO audit '=), and verify prints the last |
| 307 | id and hash: compare them with the newest journal line. Rows written by | |
| 318 | id and hash: comparing them with the newest journal line is the check | |
| 319 | for any change, recomputed hashes included. Rows written by | |
| 308 | 320 | host =gitbayd admin= commands, and by =gitbayd shell= when =ssh.mode = |
| 309 | 321 | "system"=, are not copied to the journal. |
| 310 | 322 | |
.gitbay/wiki/Architecture/09-Controls.org +1 −1
| @@ -69,7 +69,7 @@ chapter names of OWASP ASVS 4.0 where one fits. | ||
| 69 | 69 | | Security-relevant writes audited | in place | every successful mutating command (=control.go=) | |
| 70 | 70 | | Authentication failures audited | in place | =auth.failed=, =auth.throttled= | |
| 71 | 71 | | Denied attempts audited | in place | refused mutating commands and pushes, ten a minute per actor, 600 in all (=internal/control/auditrefusal.go=) | |
| 72 | | Audit log tamper resistance | partial | hash chain checked by =gitbayd admin audit verify=; every row the daemon writes copied to its journal; the table is writable by the daemon user, and removing the newest rows (or reusing their ids) shows only by comparing verify's last id and hash with the journal | | |
| 72 | | Audit log tamper resistance | partial | unkeyed hash chain checked by =gitbayd admin audit verify=; every row the daemon writes copied to its journal; the table is writable by the daemon user, who can recompute the chain after an edit, so comparing verify's last id and hash with the journal is the check for any change | | |
| 73 | 73 | |
| 74 | 74 | ** Communications and integrations (V9, V10, V12) |
| 75 | 75 | |
.gitbay/wiki/Architecture/10-Known-Gaps.org +4 −1
| @@ -19,8 +19,11 @@ what the 2026-09-27 review found; remove a row when its issue closes. | ||
| 19 | 19 | |
| 20 | 20 | | Area | Gap | Severity | |
| 21 | 21 | |-------+-------------------------------------------------------------------------------------------------------------+----------| |
| 22 | | Audit | Removing the newest audit rows, or writing new rows under their freed ids, is not detectable from the database; only comparing =gitbayd admin audit verify='s last id and hash with the daemon's journal shows it. Rows written by =gitbayd shell= (=ssh.mode = "system"=) and host admin commands have no journal copy, and the refusal caps are per process, so under that mode each connection counts separately | low | | |
| 22 | | Audit | The hash chain is unkeyed, so whoever can write the database can edit a row and recompute every later hash; removing the newest audit rows, or writing new rows under their freed ids, needs no recomputing at all. Neither is detectable from the database; only comparing =gitbayd admin audit verify='s last id and hash with the daemon's journal shows it. Rows written by =gitbayd shell= (=ssh.mode = "system"=) and host admin commands have no journal copy, and the refusal caps are per process, so under that mode each connection counts separately | low | | |
| 23 | 23 | | Availability | Under =ssh.mode = "system"= each SSH session is a separate =gitbayd shell= process, so the pack-generation limit (=internal/packlimit=, #262) cannot count SSH clones across sessions; only HTTP and git:// share a budget there | low | |
| 24 | | Availability | Pushes have no concurrency limit; =max_pack_bytes= bounds each one, not how many run at once | medium | | |
| 25 | | Availability | =repo download= (SSH, API) runs =git archive= outside the pack limit; only its two-minute deadline and 512 MiB cap bound it | low | | |
| 26 | | Availability | An HTTP or git:// client that disconnects while queued for a pack slot keeps its place until =pack_queue_wait= runs out; only SSH notices the disconnect | low | | |
| 24 | 27 | |
| 25 | 28 | * Questions an auditor will ask that have no answer yet |
| 26 | 29 | |
.gitbay/wiki/Performance.org +9 −7
| @@ -47,13 +47,15 @@ The practical ceiling on this hardware is concurrent pack generation: | ||
| 47 | 47 | full clones of large repositories are CPU-bound in git itself (the 17s |
| 48 | 48 | clone ran git at ~156% CPU). =limits.pack_concurrency= bounds how many |
| 49 | 49 | run at once across SSH, HTTP and git://, with a queue behind it (see |
| 50 | [[Admin]], =[limits]=); the measurements below set its default. | |
| 50 | [[Admin]], =[limits]=). | |
| 51 | 51 | |
| 52 | 52 | * Concurrent clones |
| 53 | 53 | |
| 54 | Measured with =deploy/clonebench.sh https://gitbay.org/krz/gitbay.git <n>= | |
| 55 | from a machine outside bay1 (four cores), before and after the pack | |
| 56 | limit was deployed with its defaults (=pack_concurrency= 3, | |
| 57 | =pack_per_principal= 2, =pack_queue= 32, =pack_queue_wait= 60s). All | |
| 58 | clones in one run come from one address, so the per-principal cap | |
| 59 | applies to them; the "limit off" run sets the counts to -1. | |
| 54 | The defaults (=pack_concurrency= 3, =pack_per_principal= 2, | |
| 55 | =pack_queue= 32, =pack_queue_wait= 60s) are set for a four-core host | |
| 56 | from the single-clone figure above; no concurrent-clone measurement | |
| 57 | backs them yet. =deploy/clonebench.sh <clone-url> <n>= starts n full | |
| 58 | clones at once from a machine other than the server. Clones from one | |
| 59 | address count against one principal, so run it from two addresses, or | |
| 60 | against an instance with =pack_per_principal = -1=, or it measures the | |
| 61 | per-principal cap instead of the global one. | |
.gitbay/wiki/Threat-Model.org +13 −9
| @@ -249,8 +249,10 @@ is recorded here rather than in a closed issue: | ||
| 249 | 249 | escape what is there. #144 covers the missing isolation. |
| 250 | 250 | - *Timing and traffic analysis.* Token comparison is a hash index lookup |
| 251 | 251 | by design, but nothing has been measured. |
| 252 | - *Denial of service by resource exhaustion* beyond rate: large pushes, | |
| 253 | pathological diffs, deep histories, zip bombs in LFS. | |
| 252 | - *Denial of service by resource exhaustion* beyond rate. Concurrent | |
| 253 | clones, fetches and web archives are bounded by the pack limit | |
| 254 | (#262); pushes are not, beyond =max_pack_bytes= on each one. Nor are | |
| 255 | pathological diffs, deep histories, or zip bombs in LFS. | |
| 254 | 256 | |
| 255 | 257 | A sweep is a point in time. This section says what a reader should not |
| 256 | 258 | assume has been checked. |
| @@ -271,13 +273,15 @@ assume has been checked. | ||
| 271 | 273 | present as objects no archived ref names, and a repository's refs may |
| 272 | 274 | be newer than the database snapshot (see [[Admin]]). |
| 273 | 275 | - The audit log lives in the database the daemon writes, so anyone with |
| 274 | the daemon user's access can change it. The hash chain makes an edited | |
| 275 | or removed row show as a break under =gitbayd admin audit verify=, | |
| 276 | except at the end: removing the newest rows, and writing new rows | |
| 277 | under their freed ids, leaves a valid chain. Only comparing verify's | |
| 278 | last id and hash with the daemon's journal copy shows it, and rows | |
| 279 | written outside the daemon (=gitbayd shell= under =ssh.mode = | |
| 280 | "system"=, host =gitbayd admin= commands) have no journal copy. | |
| 276 | the daemon user's access can change it. The hash chain is unkeyed: | |
| 277 | whoever can write the database can edit a row and recompute every | |
| 278 | later hash. =gitbayd admin audit verify= catches an edited or removed | |
| 279 | row only when the later hashes were not recomputed, and never catches | |
| 280 | removing the newest rows or writing new rows under their freed ids. | |
| 281 | Comparing verify's last id and hash with the daemon's journal copy is | |
| 282 | the check for any change; rows written outside the daemon (=gitbayd | |
| 283 | shell= under =ssh.mode = "system"=, host =gitbayd admin= commands) | |
| 284 | have no journal copy. | |
| 281 | 285 | - A global signature-verification epoch over-invalidates the cache on any |
| 282 | 286 | trust-input change. Correct, not a leak; a performance tradeoff. |
| 283 | 287 | - A build's secrets are environment variables inside its container, so |
CHANGELOG.org +10 −4
| @@ -99,8 +99,11 @@ missing, =gitbayd admin backup --verify <archive>= names it, and | ||
| 99 | 99 | before it (migration 0064), and the daemon logs a copy of every row it |
| 100 | 100 | writes to its journal. =gitbayd admin audit verify= prints the row |
| 101 | 101 | count and the last id and hash, and exits 1 naming the first row that |
| 102 | was edited or whose predecessor was removed. Removing the newest rows | |
| 103 | shows only by comparing that last id and hash with the journal (#275). | |
| 102 | was edited or whose predecessor was removed. The chain is unkeyed: | |
| 103 | someone who can write the database can recompute the later hashes, | |
| 104 | and verify catches an edit only when they were not recomputed. | |
| 105 | Comparing verify's last id and hash with the journal is the check for | |
| 106 | any change, including removing the newest rows (#275). | |
| 104 | 107 | - Refused mutating commands (exit 3 or 4) are audited as =refused |
| 105 | 108 | <command>=, and refused pushes as =refused git-receive-pack=, keeping |
| 106 | 109 | flag names and the target but no values; ten a minute per account and |
| @@ -228,8 +231,11 @@ missing, =gitbayd admin backup --verify <archive>= names it, and | ||
| 228 | 231 | "the server is busy…" and exits 1, HTTP gets 503 with |
| 229 | 232 | =Retry-After: 30=, and git:// gets an =ERR= line. *Operators:* the |
| 230 | 233 | defaults are tuned for a four-core host; set the three counts to -1 |
| 231 | to turn the limit off. Ref listings, pushes and =repo download= are | |
| 232 | unaffected. Under =ssh.mode = "system"= SSH clones are not counted, | |
| 234 | to turn the limit off. A running clone is killed when no write to | |
| 235 | its client completes for two minutes: a client reading below about | |
| 236 | 550 B/s, or an HTTP request body that takes over two minutes with | |
| 237 | nothing written back, is cut. Ref listings, pushes and =repo | |
| 238 | download= are unaffected. Under =ssh.mode = "system"= SSH clones are not counted, | |
| 233 | 239 | since each session is its own process (#262). |
| 234 | 240 | - Anonymous clones are counted per IPv4 address or IPv6 /64, and |
| 235 | 241 | together hold at most =pack_concurrency= − 1 slots, so a signed-in |