Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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