Refuse a bundle whose output directory leads out of the module through a link, so a repository cannot make the build write into or pack files it does not hold (issue 1609)
mesh/merge-gate head 0caa8c6844d9 merged into trunk 0205b8f7080a — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 0caa8c6844d9 merged into trunk 0205b8f7080a — pass: its merge-check.sh passed
mesh/delivery delivered

This commit is contained in:
2026-10-21 09:46:00 +00:00
parent fcca6ba9e0
commit 0caa8c6844
2 changed files with 84 additions and 0 deletions
+39
View File
@@ -1295,6 +1295,9 @@ func one(ctx context.Context, run Runner, publish Publisher,
}
}
say("bundle", "compiled, packing")
if err := outsideTheModule(tree, compiled); err != nil {
return catalogue.Built{}, fmt.Errorf("%s: packing %s failed: %w", module, a.Name, err)
}
body, err := pack(compiled)
if err != nil {
return catalogue.Built{}, fmt.Errorf("%s: packing %s failed: %w", module, a.Name, err)
@@ -1384,6 +1387,36 @@ func archiveRoot(tree string, a catalogue.Artifact) (string, error) {
return root, nil
}
// outsideTheModule refuses a path that, once every link in it is followed, is not inside the module's
// directory, naming where it really is and where the module is. For a path not there yet (an output
// directory about to be made) the deepest part that exists is the one followed. A repository commits
// its own links, so `.mesh-build` as a link would otherwise have a bundle's build write into, and pack,
// any directory the build account can read (issue 1609).
func outsideTheModule(tree, path string) error {
realTree, err := filepath.EvalSymlinks(tree)
if err != nil {
return err
}
real, rest := path, ""
for {
resolved, err := filepath.EvalSymlinks(real)
if err == nil {
real = filepath.Join(resolved, rest)
break
}
if !errors.Is(err, os.ErrNotExist) || filepath.Dir(real) == real {
return err
}
rest = filepath.Join(filepath.Base(real), rest)
real = filepath.Dir(real)
}
if rel, err := filepath.Rel(realTree, real); err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return fmt.Errorf("%s leads out of the module through a link: it resolves to %s, and the module is %s",
path, real, realTree)
}
return nil
}
// pack tars and gzips a directory.
//
// **Deterministically**: entries sorted, and no timestamps, uid, gid or original names carried
@@ -1687,6 +1720,9 @@ func compile(ctx context.Context, run Runner, tree string, chain Toolchain,
// a package in a third (ADR 0040). Compiling them all into one place would have them overwrite
// each other and then be packed together, so each bundle compiles and packs alone.
out := Out(a.Name)
if err := outsideTheModule(tree, filepath.Join(tree, out)); err != nil {
return "", err
}
// **The output directory exists before the compiler is told about it.** `tsc --outDir` makes
// one; `go build -o` writes a file into a directory and does not create it, failing with a
@@ -2013,6 +2049,9 @@ func bundled(ctx context.Context, run Runner, tree string, chain Toolchain, base
a catalogue.Artifact, launchers map[string]string) (string, error) {
const within = "/app/modules/module"
out, final := Out(a.Name), Out(a.Name)+bundledSuffix
if err := outsideTheModule(tree, filepath.Join(tree, final)); err != nil {
return "", err
}
if err := os.RemoveAll(filepath.Join(tree, final)); err != nil {
return "", err
}