.gitbay/wiki/Architecture/09-Controls.org
106 lines · 11752 bytes
9 symbols in this file
1#+title: Controls matrix
2
3One row per control an auditor typically asks about. *Status*: =in
4place= (implemented and cited), =partial= (implemented with a stated
5limit), =gap= (not implemented; see [[file:10-Known-Gaps.org][10]]). Categories follow the
6chapter names of OWASP ASVS 4.0 where one fits.
7
8** Architecture (V1)
9
10| Control | Status | Evidence |
11|---------------------------------------------+----------+------------------------------------------------------------------|
12| One authorization path for every surface | in place | all surfaces call =control.Dispatch= (=internal/control/control.go=); web form handlers, including the pin, watch and mark-read toggles, dispatch commands; login and session bookkeeping are not commands |
13| No server-side signing key | in place | =internal/sig= verifies only |
14| Least functionality by default | in place | API, web accounts, git://, push and registration default off (=internal/config/config.go=) |
15| No git library; git runs as a subprocess with built argv | in place | =internal/gitutil= |
16
17** Authentication (V2) and session management (V3)
18
19| Control | Status | Evidence |
20|---------------------------------------------+----------+------------------------------------------------------------------|
21| No passwords anywhere | in place | SSH keys, emailed single-use links, bearer tokens |
22| Credentials stored as hashes | in place | SHA-256 of 256-bit random values (=internal/store/sessions.go=) |
23| Brute-force limit on SSH auth | in place | 10 failures a minute per IP (=internal/sshd/ratelimit.go=) |
24| Account enumeration resistance at login | in place | uniform response (=internal/control/loginlink.go=) |
25| Session cookie flags | in place | HttpOnly, SameSite=Lax, Secure with TLS (=internal/httpd/accounts.go=) |
26| Session lifetime | in place | 12 hours idle, 7 days absolute (=internal/store/sessions.go=) |
27| Credential expiry | in place | optional =--ttl= on API tokens, SSH and deploy keys; checked at auth and per exec |
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=); LFS transfer tokens are refused with their key (=internal/httpd/lfs.go=) |
29| Delegation bounded by the delegating credential | in place | expiring tokens refused on =MintsCredential= commands; credentials record their creating token; a browser session runs credential-minting and access-granting commands only within 15 minutes of signing in, and the refusal is audited (=internal/control/control.go=) |
30
31** Access control (V4)
32
33| Control | Status | Evidence |
34|---------------------------------------------+----------+------------------------------------------------------------------|
35| Deny by default on private data | in place | =CanRead= requires owner, public or grant (=internal/policy/access.go=) |
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=) |
38| Server-side write protections | in place | pre-receive =CheckPush=, signed commits (=internal/hookd/hookd.go=) |
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 |
41| CSRF protection | in place | SameSite=Lax plus =checkOrigin= (=accounts.go=) |
42| Typed confirmation for destructive web actions | in place | =internal/httpd/confirm.go= |
43| Deleted rows' ids never handed out again | in place | AUTOINCREMENT on accounts, orgs, repositories, keys, tokens and the delivery queues; build ids from a high-water mark (=internal/store/builds.go=), so a grant, deploy key, parked about text, token or claim naming a deleted row names nothing; deletes take the grants, deploy keys and about texts that name the row by id |
44
45** Input handling and output encoding (V5)
46
47| Control | Status | Evidence |
48|---------------------------------------------+----------+------------------------------------------------------------------|
49| User markup sanitised | in place | =ugcHTML= with bluemonday (=internal/httpd/web.go=) |
50| No script execution in pages | in place | CSP =script-src 'none'= (=internal/httpd/routes.go=) |
51| Control characters stripped at the terminal | in place | =termSafe= (=internal/control/term.go=) |
52| No shell in command execution | in place | =protocol.Tokenize= for SSH argv; git and podman with argv slices |
53| Parsers fuzzed | partial | five fuzz targets run briefly by =deploy/audit.sh= |
54
55** Cryptography (V6) and data protection (V8)
56
57| Control | Status | Evidence |
58|---------------------------------------------+----------+------------------------------------------------------------------|
59| TLS for all authenticated HTTP | in place | ACME or certificate files; HSTS |
60| Secrets encrypted at rest | in place | AES-256-GCM, key file outside the database and the main backups (=internal/seal=) |
61| Secrets kept out of argv, logs and output | in place | =ReadsStdin=, pruned audit argv, write-only secret commands |
62| Local backups encrypted | in place | age to =[backup] age_recipients= (=cmd/gitbayd/backup.go=); offsite copy by restic |
63| Data retention configurable | in place | =[retention]= (=internal/config/config.go=) |
64| User data export | in place | =account export= |
65
66** Logging (V7)
67
68| Control | Status | Evidence |
69|---------------------------------------------+----------+------------------------------------------------------------------|
70| Security-relevant writes audited | in place | every successful mutating command (=control.go=) |
71| Authentication failures audited | in place | =auth.failed=, =auth.throttled= |
72| Denied attempts audited | in place | refused mutating commands and pushes, ten a minute per actor, 600 in all (=internal/control/auditrefusal.go=) |
73| 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 |
74
75** Communications and integrations (V9, V10, V12)
76
77| Control | Status | Evidence |
78|---------------------------------------------+----------+------------------------------------------------------------------|
79| SSRF protection on user-supplied URLs | in place | webhooks at save and connect; mirrors at save and sync, =repo import= and =repo import-issues= before they fetch, git and the import API client pinned to the checked address (=internal/gitpin=) |
80| Webhook payload integrity | in place | HMAC-SHA256 header |
81| SMTP credentials protected in transit | in place | STARTTLS required for non-local relays, implicit TLS optional (=internal/mail/mail.go=) |
82| Upload size limits | in place | per-owner storage quota at push (=internal/sshd/sshd.go=); API body 1 MiB |
83
84** CI and build isolation
85
86| Control | Status | Evidence |
87|---------------------------------------------+----------+------------------------------------------------------------------|
88| Untrusted code runs isolated | in place | rootless podman, cgroup limits; untrusted builds get a disposable home (=cmd/gitbay-runner/main.go=) |
89| No secrets for untrusted builds | in place | =internal/control/build.go= |
90| Runner limited to attached repositories | in place | =runnerMayBuild= (=build.go=) |
91| Build images fixed by the operator | in place | =--pull=never= |
92| Build network egress restricted | in place | by build cgroup (=gitbay-runner-builds.nft=): host loopback (but DNS) and private ranges closed; trusted: host public 22/80/443, internet open by decision; untrusted: internet TCP 80/443 and DNS only. measured on bay1 2026-09-29 (CI page) |
93| Build results reused only across equal trust | in place | =SuccessBuildForTree=, =SuccessBuildFor= (=internal/store/builds.go=) |
94
95** Availability and operations
96
97| Control | Status | Evidence |
98|---------------------------------------------+----------+------------------------------------------------------------------|
99| Rate limits on API and writes | in place | [[file:05-Identity-and-Access.org][5. Rate limits]] |
100| Concurrency limit on git pack generation | in place | global, per-principal, bounded queue across SSH, HTTP and git://, =repo download= included (=internal/packlimit=); not in system SSH mode |
101| Concurrency limit on pushes | in place | receive-pack on its own global, per-principal, bounded-queue budget; a deploy key is its own principal; killed at =push_receive_timeout= before pre-receive, or after =push_idle= with nothing moving once the pack has begun (=internal/sshd/sshd.go=, =internal/packlimit=, =internal/hookd=); not in system SSH mode |
102| Service hardening | in place | systemd sandboxing ([[file:03-Deployment.org][3]]) |
103| Backups offsite and append-only | in place | restic with append-only credentials (documented) |
104| Restore tested | partial | drill 2026-09-29 from the offsite copy (Admin wiki "Restore drill"); secrets not checked in that drill; the off-host =secret.key= exists (#305) and is checked in the next |
105| Migrations validated before commit | in place | =PRAGMA foreign_key_check= runs inside the migration transaction, before commit (=internal/store/store.go=) |
106| Signed, reviewed changes to production | in place | signed commits, =require-mr=, ff-only merges, clean-tree deploys |