vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Bound what a CI job can consume
6bfc42949be4d40b3b74de8cc17c6d903cfe531e · Keenan Nemetz · 12d ago · parent f5456470
Bound what a CI job can consume A job runs untrusted build code, and everything it produces is either stored by the instance or kept on the runner. None of those channels had a bound of its own. The attempt log was a strings.Builder: the whole run in memory, then written to a temp file, then read twice by the LFS transfer (once to hash, once to send). It is now a spool file on disk from the first byte, capped at 32 MiB, uploaded once at the end as an ordinary LFS object. A capped log reports the whole write as taken so the producing process is not killed, and gets a line saying it was truncated. Step offsets are computed from the spool's length, which never exceeds the cap, so a truncated log still has valid ranges. Nothing is streamed to the server: a log's content-addressed id cannot be known before its last byte, so streaming would only buy a second protocol. A run may publish at most 256 artifact files totalling 4 GiB. The workflow limit is on declarations, and a declaration is a pattern that can match a directory tree, so the bound on what it expands to has to be where the tree is walked. Contributor caches are now namespaced per account, not one shared "contrib" namespace: one contributor's untrusted branch must not be able to seed another contributor's cache. A contributor keeps their cache across their own branches. Container volume paths are checked before the argument list is built: a colon is the field separator, and a NUL or newline would split the argument, so a path carrying one is refused rather than passed to the runtime. RunArgs is unchanged; the refusal is in Start, and the closed-set test now also pins --cap-add, --device, --pid/--ipc host and the podman socket. Image builds run through the configured runtime, so a rootless Podman runner builds rootless. The definition is pinned by digest already; the docs now say how the build gets its network and recommend pinning the base image in from: by digest for a reproducible build. Tests: the spool keeps what fits, truncates and says so, and removes its file; artifact count and byte caps (the byte cap uses a sparse file so it does not write 4 GiB); contributor caches are separated by account while one account's branches share; volume paths with a colon, NUL or newline are refused.
ci/container/container.go +24
| @@ -141,6 +141,27 @@ type Spec struct { | |||
| 141 | 141 | Env map[string]string | |
| 142 | 142 | } | |
| 143 | 143 | ||
| 144 | // ValidatePaths refuses a spec whose paths cannot be passed as `--volume` | ||
| 145 | // arguments. The separator is a colon, so a path containing one would be read as | ||
| 146 | // an extra field; a NUL or a newline would end or split the argument the runtime | ||
| 147 | // sees. The runner builds these paths itself, so this is the check that keeps that | ||
| 148 | // assumption true rather than the check that discovers it. | ||
| 149 | func ValidatePaths(s Spec) error { | ||
| 150 | paths := []string{s.Work} | ||
| 151 | for _, m := range s.Mounts { | ||
| 152 | paths = append(paths, m.Host, m.Path) | ||
| 153 | } | ||
| 154 | for _, p := range paths { | ||
| 155 | if p == "" { | ||
| 156 | return fmt.Errorf("container path is empty") | ||
| 157 | } | ||
| 158 | if strings.ContainsAny(p, ":\x00\r\n") { | ||
| 159 | return fmt.Errorf("container path %q contains a character a volume argument cannot carry", p) | ||
| 160 | } | ||
| 161 | } | ||
| 162 | return nil | ||
| 163 | } | ||
| 164 | |||
| 144 | 165 | // RunArgs is the argument list for `docker run`. It is pure so it can be checked | |
| 145 | 166 | // without a daemon. | |
| 146 | 167 | func RunArgs(cfg Config, s Spec) []string { | |
| @@ -225,6 +246,9 @@ type Container struct { | |||
| 225 | 246 | // cannot start it is reported with the runtime's own message: a pull failure and a | |
| 226 | 247 | // bad flag look the same from here, and the message is what tells them apart. | |
| 227 | 248 | func Start(cfg Config, s Spec) (*Container, error) { | |
| 249 | if err := ValidatePaths(s); err != nil { | ||
| 250 | return nil, err | ||
| 251 | } | ||
| 228 | 252 | c := &Container{cfg: cfg, name: s.Name, work: s.Work} | |
| 229 | 253 | cmd := exec.Command(cfg.Runtime, RunArgs(cfg, s)...) | |
| 230 | 254 | var stderr strings.Builder | |