# Route all settings-package file reads through confined os.Root

`41b7522`→[main](/content/gh/entireio/cli/commits/main/index.html)·
  
Soph·3w ago·2 files·+40 added/-39 removed

Follow-up to the checkpoints-loader TOCTOU fix: make the rest of the settings
package consistent. Promote readConfined to settings.go and route every
settings/preferences file read through it — loadFromFile (base settings),
loadMergedSettings (local override), LoadProjectRaw, LoadLocalRaw, and
loadClonePreferencesFromFile — instead of bare os.ReadFile of an absolute path.

Each read now opens an os.Root anchored at the file's parent directory, so a
swapped or symlinked path can't redirect the open outside that directory. The
helper wraps its errors, so callers classify "missing" with
errors.Is(err, fs.ErrNotExist) rather than os.IsNotExist. This removes the
earlier split-brain where a symlinked settings.json worked for Load but was
refused only by the checkpoints loader.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

## Sessions

50640125952dView transcript

## Changes

2

- cmd/entire/cli/settings

- Mcheckpoints.go-29  
  - Msettings.go+40/-10

```
5 unmodified lines
6
7
8
9
9
10
12
13
11
12
13
99 unmodified lines

113
114
115
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
116
117
118

5 unmodified lines

"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"log/slog"
	"os"
	"path/filepath"

"github.com/entireio/cli/cmd/entire/cli/logging"
}
// readConfined reads filePath through an os.Root anchored at its parent
directory. The root confines the open to that directory, so the read cannot
be redirected outside it by a swapped or symlinked path between resolution and
open (TOCTOU) — unlike a bare os.ReadFile of an absolute path. A symlink that
escapes the directory surfaces as a non-ENOENT error, which the caller treats
as fail-soft.
func readConfined(filePath string) ([]byte, error) {
	root, err := os.OpenRoot(filepath.Dir(filePath))
	if err != nil {
		return nil, fmt.Errorf("open settings dir: %w", err)
	}
	defer root.Close()

f, err := root.Open(filepath.Base(filePath))
	if err != nil {
		return nil, fmt.Errorf("open settings file: %w", err)
	}
	defer f.Close()

data, err := io.ReadAll(f)
	if err != nil {
		return nil, fmt.Errorf("read settings file: %w", err)
	}
	return data, nil
}

func (c *CheckpointsConfig) validate() error {
	if c.Primary.Type == "" {
		return fmt.Errorf("%w: checkpoints.primary.type is required", ErrInvalidCheckpointsConfig)
	}
}

Mcmd/entire/cli/settings/checkpoints.go-29

```
6 unmodified lines

7
8
9
10
11
12
13
14
15
16
443 unmodified lines

460
461
462
460
463
464
462
465
466
467
468
40 unmodified lines

509
510
511
509
512
513
511
514
515
516
517
18 unmodified lines

536
537
538
536
539
540
538
541
542
543
544
91 unmodified lines

636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
1 unmodified line

670
671
672
643
673
674
645
675
676
677
678
26 unmodified lines

705
706
707
678
708
709
680
710
711
712
713

6 unmodified lines

"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"log/slog"
	"os"
	"os/exec"

}
	
	// Apply local overrides if they exist
	localData, err := os.ReadFile(localSettingsFileAbs) //nolint:gosec // path is from AbsPath or constant
	localData, err := readConfined(localSettingsFileAbs)
	if err != nil {
		if !os.IsNotExist(err) {
			if !errors.Is(err, fs.ErrNotExist) {
				return nil, fmt.Errorf("reading local settings file: %w", err)
			}
			// Local file doesn't exist, continue without overrides
	}

if err != nil {
		path = EntireSettingsFile
	}
	data, readErr := os.ReadFile(path) //nolint:gosec // path is from AbsPath or a project-relative constant
	data, readErr := readConfined(path)
	if readErr != nil {
		if os.IsNotExist(readErr) {
			if errors.Is(readErr, fs.ErrNotExist) {
				return path, map[string]json.RawMessage{}, false, nil
			}
			return path, nil, false, fmt.Errorf("reading project settings: %w", readErr)
		}
	}

if err != nil {
		path = EntireSettingsLocalFile
	}
	data, readErr := os.ReadFile(path) //nolint:gosec // path is from AbsPath or a project-relative constant
	data, readErr := readConfined(path)
	if readErr != nil {
		if os.IsNotExist(readErr) {
			if errors.Is(readErr, fs.ErrNotExist) {
				return path, map[string]json.RawMessage{}, false, nil
			}
			return path, nil, false, fmt.Errorf("reading local settings: %w", readErr)
		}
	}

return s, nil
}

// readConfined reads filePath through an os.Root anchored at its parent
directory. The root confines the open to that directory, so the read cannot
be redirected outside it by a swapped or symlinked path between resolution and
open (TOCTOU) — unlike a bare os.ReadFile of an absolute path. A symlink that
escapes the directory surfaces as a non-ENOENT error. Callers must classify
"missing" with errors.Is(err, fs.ErrNotExist) rather than os.IsNotExist,
since the returned errors are wrapped.
func readConfined(filePath string) ([]byte, error) {
	root, err := os.OpenRoot(filepath.Dir(filePath))
	if err != nil {
		return nil, fmt.Errorf("open settings dir: %w", err)
	}
	defer root.Close()

f, err := root.Open(filepath.Base(filePath))
	if err != nil {
		return nil, fmt.Errorf("open settings file: %w", err)
	}
	defer f.Close()

data, err := io.ReadAll(f)
	if err != nil {
		return nil, fmt.Errorf("read settings file: %w", err)
	}
	return data, nil
}

// loadFromFile loads settings from a specific file path.
// Returns default settings if the file doesn't exist.
func loadFromFile(filePath string) (*EntireSettings, error) {
}`
