Validate access to derived host roots

This commit is contained in:
2026-11-09 21:20:02 +00:00
parent 367fe4120f
commit f0198f865d
4 changed files with 71 additions and 6 deletions

View File

@@ -133,6 +133,8 @@ and downstream consumers. Findings 1–4 remain open.
## Finding 5 — Derived host roots skip standalone access validation
**Status** — Complete. Derived and explicit standalone contexts share the host root open and close checks. The permission regression passed for both paths.
**Priority P2.** When a companion binds to the main context with imports
disabled, `acquireStandaloneInput` checks the resolved directories with
`Stat`. An equivalent explicit context goes through

View File

@@ -1,6 +1,7 @@
package configkit_test
import (
"errors"
"os"
"path/filepath"
"strings"
@@ -9,6 +10,61 @@ import (
configkit "go.maximumdirect.net/configkit"
)
func TestMainRelativeStandaloneRejectsInaccessibleHostRoot(t *testing.T) {
root := t.TempDir()
mainPath := filepath.Join(root, "main.yml")
if err := os.WriteFile(mainPath, []byte("count: 1\n"), 0600); err != nil {
t.Fatal(err)
}
if err := os.Chmod(root, 0100); err != nil {
t.Skipf("cannot set search-only permissions: %v", err)
}
t.Cleanup(func() {
if err := os.Chmod(root, 0700); err != nil {
t.Error(err)
}
})
handle, err := os.OpenRoot(root)
if err == nil {
_ = handle.Close()
t.Skip("host allows opening this search-only directory")
}
if !errors.Is(err, os.ErrPermission) {
t.Fatalf("open root = %v, want permission denied", err)
}
if _, err := os.ReadFile(mainPath); err != nil {
t.Skipf("main must remain readable by name: %v", err)
}
main := configkit.Input{Source: configkit.File(mainPath)}
for _, tc := range []struct {
name string
filesystem *configkit.FilesystemContext
}{
{name: "derived"},
{name: "explicit", filesystem: &configkit.FilesystemContext{Root: root, BaseDir: root}},
} {
t.Run(tc.name, func(t *testing.T) {
underlay := configkit.Input{
Source: configkit.Bytes("companion", []byte("{}\n")),
MainRelative: &configkit.MainRelativeContext{RequireBase: true},
Filesystem: tc.filesystem,
}
_, err := configkit.LoadLayered(main, configkit.LayeredOptions[layeredFacadeConfig]{
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{underlay}}},
})
var failure *configkit.LoadError
if !errors.As(err, &failure) {
t.Fatalf("error = %v, want a load error", err)
}
diagnostics := failure.Diagnostics()
if len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeImportPermission {
t.Fatalf("diagnostics = %#v, want import.permission", diagnostics)
}
})
}
}
func TestMainRelativeDiscoveryAndEvidence(t *testing.T) {
root := t.TempDir()
first, second := filepath.Join(root, "first"), filepath.Join(root, "second")

View File

@@ -137,7 +137,7 @@ func acquireStandaloneInput(input assembledInput, request inputAcquisitionReques
input.document = processing.payload
}
if request.resolved != nil {
if diagnostic := validateResolvedFilesystemDirectories(*request.resolved); diagnostic != nil {
if diagnostic := validateResolvedFilesystemContext(*request.resolved); diagnostic != nil {
return input, []Diagnostic{*diagnostic}
}
input.resolvedFilesystem = request.resolved

View File

@@ -17,23 +17,30 @@ func validateFilesystemContext(source Source, context *FilesystemContext, main p
}
return resolvedFilesystemContext{}, diagnostic
}
if diagnostic := validateResolvedFilesystemDirectories(resolved); diagnostic != nil {
if diagnostic := validateResolvedFilesystemContext(resolved); diagnostic != nil {
return resolvedFilesystemContext{}, diagnostic
}
return resolved, nil
}
func validateResolvedFilesystemContext(resolved resolvedFilesystemContext) *Diagnostic {
if diagnostic := validateResolvedFilesystemDirectories(resolved); diagnostic != nil {
return diagnostic
}
if resolved.native != nil {
return resolved, nil
return nil
}
root, err := os.OpenRoot(resolved.rootPath)
if err != nil {
value := globFilesystemDiagnostic(err, "configuration root could not be opened", Location{})
value.Primary = nil
return resolvedFilesystemContext{}, &value
return &value
}
if err := root.Close(); err != nil {
value := importDiagnostic(CodeImportRead, "configuration root could not be closed", nil)
return resolvedFilesystemContext{}, &value
return &value
}
return resolved, nil
return nil
}
func filesystemOptionDiagnostics(source Source, context *FilesystemContext) []Diagnostic {