Wiki: Architecture/09-Controls

Architecture/09-Controls

Controls matrix

One row per control an auditor typically asks about. Status: in place (implemented and cited), partial (implemented with a stated limit), gap (not implemented; see 10). Categories follow the chapter names of OWASP ASVS 4.0 where one fits.

Architecture (V1)

Control Status Evidence
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
No server-side signing key in place internal/sig verifies only
Least functionality by default in place API, web accounts, git://, push and registration default off (internal/config/config.go)
No git library; git runs as a subprocess with built argv in place internal/gitutil

Authentication (V2) and session management (V3)

Control Status Evidence
No passwords anywhere in place SSH keys, emailed single-use links, bearer tokens
Credentials stored as hashes in place SHA-256 of 256-bit random values (internal/store/sessions.go)
Brute-force limit on SSH auth in place 10 failures a minute per IP (internal/sshd/ratelimit.go)
Account enumeration resistance at login in place uniform response (internal/control/loginlink.go)
Session cookie flags in place HttpOnly, SameSite=Lax, Secure with TLS (internal/httpd/accounts.go)
Session lifetime in place 12 hours idle, 7 days absolute (internal/store/sessions.go)
Credential expiry in place optional --ttl on API tokens, SSH and deploy keys; checked at auth and per exec
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)
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)

Access control (V4)

Control Status Evidence
Deny by default on private data in place CanRead requires owner, public or grant (internal/policy/access.go)
Private resources indistinguishable from missing in place resolveRepo (internal/control/repo.go), runGit, smart HTTP
Credential scopes narrow account rights in place key and token scopes (control.go, policy/access.go)
Server-side write protections in place pre-receive CheckPush, signed commits (internal/hookd/hookd.go)
Merge gates in place MergeGates; ci/* statuses written only by the build subsystem; required contexts
Admin functions isolated in place admin noun gated in Dispatch; audit admin-only
CSRF protection in place SameSite=Lax plus checkOrigin (accounts.go)
Typed confirmation for destructive web actions in place internal/httpd/confirm.go
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

Input handling and output encoding (V5)

Control Status Evidence
User markup sanitised in place ugcHTML with bluemonday (internal/httpd/web.go)
No script execution in pages in place CSP script-src 'none' (internal/httpd/routes.go)
Control characters stripped at the terminal in place termSafe (internal/control/term.go)
No shell in command execution in place protocol.Tokenize for SSH argv; git and podman with argv slices
Parsers fuzzed partial five fuzz targets run briefly by deploy/audit.sh

Cryptography (V6) and data protection (V8)

Control Status Evidence
TLS for all authenticated HTTP in place ACME or certificate files; HSTS
Secrets encrypted at rest in place AES-256-GCM, key file outside the database and the main backups (internal/seal)
Secrets kept out of argv, logs and output in place ReadsStdin, pruned audit argv, write-only secret commands
Local backups encrypted in place age to [backup] age_recipients (cmd/gitbayd/backup.go); offsite copy by restic
Data retention configurable in place [retention] (internal/config/config.go)
User data export in place account export

Logging (V7)

Control Status Evidence
Security-relevant writes audited in place every successful mutating command (control.go)
Authentication failures audited in place auth.failed, auth.throttled
Denied attempts audited in place refused mutating commands and pushes, ten a minute per actor, 600 in all (internal/control/auditrefusal.go)
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

Communications and integrations (V9, V10, V12)

Control Status Evidence
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)
Webhook payload integrity in place HMAC-SHA256 header
SMTP credentials protected in transit in place STARTTLS required for non-local relays, implicit TLS optional (internal/mail/mail.go)
Upload size limits in place per-owner storage quota at push (internal/sshd/sshd.go); API body 1 MiB

CI and build isolation

Control Status Evidence
Untrusted code runs isolated in place rootless podman, cgroup limits; untrusted builds get a disposable home (cmd/gitbay-runner/main.go)
No secrets for untrusted builds in place internal/control/build.go
Runner limited to attached repositories in place runnerMayBuild (build.go)
Build images fixed by the operator in place --pull=never
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)
Build results reused only across equal trust in place SuccessBuildForTree, SuccessBuildFor (internal/store/builds.go)

Availability and operations

Control Status Evidence
Rate limits on API and writes in place 5. Rate limits
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
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
Service hardening in place systemd sandboxing (3)
Backups offsite and append-only in place restic with append-only credentials (documented)
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
Migrations validated before commit in place PRAGMA foreign_key_check runs inside the migration transaction, before commit (internal/store/store.go)
Signed, reviewed changes to production in place signed commits, require-mr, ff-only merges, clean-tree deploys