Preserve directory traversal failure phases

This commit is contained in:
2026-10-25 21:33:36 +00:00
parent 877c497325
commit 8aba43da38
6 changed files with 287 additions and 24 deletions

View File

@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"testing/fstest"
)
@@ -181,3 +182,191 @@ func TestLoadingClosesNativeFilesAcrossPipelineOutcomes(t *testing.T) {
})
}
}
func TestDirectorySelectorsFailOnPostOpenNotExistErrors(t *testing.T) {
const canary = "DIRECTORY_FAILURE_CANARY"
readFailure := errors.Join(fs.ErrNotExist, errors.New(canary))
closeFailure := errors.Join(fs.ErrNotExist, errors.New(canary))
for _, test := range []struct {
name string
selector string
fallback bool
readError bool
noProgress bool
closeError bool
fallbackErr bool
}{
{name: "directory batched read with entries", selector: "directory", readError: true},
{name: "directory close", selector: "directory", closeError: true},
{name: "directory fallback with entries", selector: "directory", fallback: true, fallbackErr: true},
{name: "directory no progress", selector: "directory", noProgress: true},
{name: "glob batched read with entries", selector: "glob", readError: true},
{name: "glob close", selector: "glob", closeError: true},
{name: "glob fallback with entries", selector: "glob", fallback: true, fallbackErr: true},
{name: "glob no progress", selector: "glob", noProgress: true},
} {
t.Run(test.name, func(t *testing.T) {
main := "configkit:\n imports:\n"
if test.selector == "directory" {
main += " - dir: [broken, good]\n pattern: '*.yml'\n"
} else {
main += " - glob: 'broken/*.yml'\n - glob: 'good/*.yml'\n"
}
filesystem := &observedFilesystem{
files: fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte(main)},
"broken": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
"broken/bad.yml": &fstest.MapFile{Data: []byte("value: bad\n")},
"good": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
"good/good.yml": &fstest.MapFile{Data: []byte("value: good\n")},
},
fallback: test.fallback,
failReadDir: "broken",
readDirErr: readFailure,
failNoProgress: "broken",
failClose: "broken",
closeErr: closeFailure,
failFallbackDir: "broken",
fallbackErr: readFailure,
}
if !test.readError {
filesystem.failReadDir = ""
}
if !test.noProgress {
filesystem.failNoProgress = ""
}
if !test.closeError {
filesystem.failClose = ""
}
if !test.fallbackErr {
filesystem.failFallbackDir = ""
}
result, err := testLoad[map[string]any](filesystemFile(newNativeNamespace("bundle", filesystem), "main.yml"), Options[map[string]any]{Imports: true})
if result != nil {
t.Fatalf("load returned a result after directory failure: %#v", result)
}
var loadError *LoadError
if !errors.As(err, &loadError) {
t.Fatalf("load error = %#v, want structured LoadError", err)
}
diagnostics := loadError.Diagnostics()
if len(diagnostics) != 1 {
t.Fatalf("diagnostics = %#v, want one directory failure", diagnostics)
}
diagnostic := diagnostics[0]
wantCode := CodeImportInvalid
if test.selector == "glob" {
wantCode = CodeImportRead
}
if diagnostic.Code != wantCode || diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-1" {
t.Fatalf("directory diagnostic = %#v, want %q at source-1", diagnostic, wantCode)
}
var rendered strings.Builder
if err := WriteDiagnostics(&rendered, diagnostics); err != nil {
t.Fatal(err)
}
if strings.Contains(err.Error()+diagnostic.Message+rendered.String(), canary) {
t.Fatalf("backing error canary escaped: error=%q diagnostic=%#v rendered=%q", err, diagnostic, rendered.String())
}
if filesystem.fallbackBeforeClose {
t.Fatal("fallback directory enumeration began before the opened handle was closed")
}
filesystem.assertClosed(t)
})
}
}
func TestReadDirectoryPreservesReadErrorOverCloseError(t *testing.T) {
readFailure := errors.New("read failure")
closeFailure := errors.New("close failure")
filesystem := &observedFilesystem{
files: fstest.MapFS{
"broken": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
"broken/entry": &fstest.MapFile{Data: []byte("entry")},
},
failReadDir: "broken",
readDirErr: readFailure,
failClose: "broken",
closeErr: closeFailure,
}
access := newNativeAccess(filesystem, ".", &namespaceToken{})
visited := 0
err := access.readDirectory("broken", func(fs.DirEntry) bool {
visited++
return true
})
if !errors.Is(err, readFailure) || errors.Is(err, closeFailure) || visited != 1 {
t.Fatalf("directory read = %v, visited %d; want entries plus primary read failure", err, visited)
}
if missingDirectoryOpen(err) {
t.Fatal("post-open read failure was classified as missing-on-open")
}
filesystem.assertClosed(t)
}
func TestReadDirectoryVisitStopCloseNotExistIsPostOpen(t *testing.T) {
filesystem := &observedFilesystem{
files: fstest.MapFS{
"broken": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
"broken/entry": &fstest.MapFile{Data: []byte("entry")},
},
failClose: "broken",
}
access := newNativeAccess(filesystem, ".", &namespaceToken{})
err := access.readDirectory("broken", func(fs.DirEntry) bool { return false })
if !errors.Is(err, fs.ErrNotExist) || missingDirectoryOpen(err) {
t.Fatalf("visit-stop close error = %v, missing-on-open=%t", err, missingDirectoryOpen(err))
}
filesystem.assertClosed(t)
}
func TestDirectorySelectorsKeepMissingRootsOptional(t *testing.T) {
for _, selector := range []string{"directory", "glob"} {
t.Run(selector, func(t *testing.T) {
main := "configkit:\n imports:\n"
if selector == "directory" {
main += " - dir: [missing, good]\n pattern: '*.yml'\n required: true\n"
} else {
main += " - glob: 'missing/*.yml'\n - glob: 'good/*.yml'\n required: true\n"
}
filesystem := &observedFilesystem{files: fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte(main)},
"good": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
"good/good.yml": &fstest.MapFile{Data: []byte("value: good\n")},
}}
result, err := testLoad[map[string]any](filesystemFile(newNativeNamespace("bundle", filesystem), "main.yml"), Options[map[string]any]{Imports: true})
if err != nil || result == nil {
t.Fatalf("load with missing search root = %#v, %v", result, err)
}
filesystem.assertClosed(t)
})
}
}
func TestDirectorySelectorsTreatMissingOnOpenAsOptional(t *testing.T) {
for _, selector := range []string{"directory", "glob"} {
t.Run(selector, func(t *testing.T) {
main := "configkit:\n imports:\n"
if selector == "directory" {
main += " - dir: [broken, good]\n pattern: '*.yml'\n required: true\n"
} else {
main += " - glob: 'broken/*.yml'\n - glob: 'good/*.yml'\n required: true\n"
}
filesystem := &observedFilesystem{
files: fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte(main)},
"broken": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
"good": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
"good/good.yml": &fstest.MapFile{Data: []byte("value: good\n")},
},
failOpen: "broken",
}
result, err := testLoad[map[string]any](filesystemFile(newNativeNamespace("bundle", filesystem), "main.yml"), Options[map[string]any]{Imports: true})
if err != nil || result == nil {
t.Fatalf("load after missing-on-open root = %#v, %v", result, err)
}
filesystem.assertClosed(t)
})
}
}