Follow no link below a home as root, and judge more ways to root
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

An account could replace ~/.claude with a link to /etc and have the
node-engine chown, chmod or write through it on the next apply. Below a
person's or an agent's home every component is now opened without
following a link, and a link refuses the resource in words.

The root judge also reads doas and polkit rules, the container runtimes'
sockets with their ACLs, ACLs on the secrets, and setuid-root programs no
package owns, in the C locale; Judged and NotJudged write down exactly
what it covers (hq ADR 0266 review).
This commit is contained in:
jochen
2026-10-08 20:36:46 +02:00
parent 8390fab5cb
commit 5fc37b44a9
12 changed files with 1347 additions and 33 deletions
+30 -7
View File
@@ -302,7 +302,7 @@ func own(path, owner string) error {
if err != nil {
return fmt.Errorf("%s should belong to %q: %w", path, owner, err)
}
if err := os.Chown(path, uid, gid); err != nil {
if err := chownPath(path, uid, gid); err != nil {
return fmt.Errorf("cannot give %s to %q: %w", path, owner, err)
}
return nil
@@ -359,7 +359,7 @@ func ownedBy(path, owner string) (bool, error) {
if err != nil {
return false, nil
}
info, err := os.Stat(path)
info, err := statPath(path)
if err != nil {
return false, err
}
@@ -392,6 +392,15 @@ func makeDirs(dir string, mode os.FileMode, owner string) error {
// can take away on removal the parents it made to reach its directory (novox/hq issue 162).
func makeDirsSaying(dir string, mode os.FileMode, owner string) ([]string, error) {
var made []string
if below := homeAbove(dir); below != "" {
// Below a home, each directory is made in its parent's descriptor and none is reached through a link
// (novox/hq ADR 0266).
var err error
if made, err = mkdirAllUnder(below, dir, mode); err != nil {
return made, err
}
return made, giveMade(made, owner)
}
for d := filepath.Clean(dir); ; d = filepath.Dir(d) {
if _, err := os.Lstat(d); !errors.Is(err, os.ErrNotExist) {
break
@@ -404,14 +413,19 @@ func makeDirsSaying(dir string, mode os.FileMode, owner string) ([]string, error
if err := os.MkdirAll(dir, mode); err != nil {
return nil, err
}
return made, giveMade(made, owner)
}
// giveMade gives the directories the host made inside the owner's home to the owner.
func giveMade(made []string, owner string) error {
if owner == "" || len(made) == 0 {
return made, nil
return nil
}
home, err := homeOf(owner)
if err != nil || home == "" {
// A numeric owner — a container's user — has no home, and a name the machine does not
// know fails where the target is given to it. Either way nothing here is a home's.
return made, nil
return nil
}
home = filepath.Clean(home)
for _, d := range made {
@@ -419,10 +433,10 @@ func makeDirsSaying(dir string, mode os.FileMode, owner string) ([]string, error
continue
}
if err := ownMade(d, owner); err != nil {
return made, err
return err
}
}
return made, nil
return nil
}
// homeOf is an owner's home from the user database, and ownMade gives a directory the host made to
@@ -444,10 +458,19 @@ func ownAll(root, owner string) error {
if owner == "" {
return nil
}
return filepath.Walk(root, func(path string, _ os.FileInfo, err error) error {
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// A link in the tree is owned as a link: chown follows one, and root must never give away what it
// points at (novox/hq ADR 0266).
if info != nil && info.Mode()&os.ModeSymlink != 0 {
uid, gid, ierr := idsOf(owner)
if ierr != nil {
return fmt.Errorf("%s should belong to %q: %w", path, owner, ierr)
}
return os.Lchown(path, uid, gid)
}
return own(path, owner)
})
}